SkillAgentSearch skills...

Ccstate

No description available

Install / Use

/learn @e7h4n/Ccstate
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

<img src="https://github.com/user-attachments/assets/590797c8-6edf-45cc-8eae-028aef0b2cb3" width="240" >

Ask DeepWiki Coverage Status NPM Type Definitions NPM Version npm package minimized gzipped size CI License: MIT

CCState is a modern signals-based state management library that elegantly implements async computed and read-write capability isolation based on signal features, making it suitable for medium to large web applications.

The name of CCState comes from three basic types: Computed, Command, and State.

Quick Features

  • ✈️ Intuitive async computation: using async/await or try/catch to process async flow as regular JavaScript without any additional concept
  • 💯 Simple & Intuitive: Crystal-clear API design with just three types and two operations
  • ✅ Rock-solid Reliability: Comprehensive test coverage reaching 100% branch coverage
  • 💡 Framework Agnostic: Seamlessly works with React, Vue, Solid.js, Vanilla, or any UI framework

Getting Started

Installation

# npm
npm i ccstate

# pnpm
pnpm add ccstate

# yarn
yarn add ccstate

Create Signals

Use state to store a simple value unit, and use computed to create a derived computation logic:

// signals.js
import { state, computed } from 'ccstate';

// a simple value unit which supports read/write
export const userId$ = state('');

// intuitive async computation logic
export const user$ = computed(async (get) => {
  const userId = get(userId$);
  if (!userId) return null;

  const resp = await fetch(`https://api.github.com/users/${userId}`);
  return resp.json();
});

Use signals in React

Use useGet and useSet hooks in React to get/set signals, and use useResolved to get Promise value.

// App.jsx
import { useGet, useSet, useResolved } from 'ccstate-react';
import { userId$, user$ } from './signals';

export default function App() {
  const userId = useGet(userId$);
  const setUserId = useSet(userId$);
  const user = useResolved(user$);

  return (
    <div>
      <div>
        <input type="text" value={userId} onChange={(e) => setUserId(e.target.value)} placeholder="github username" />
      </div>
      <div>
        <img src={user?.avatar_url} width="48" />
        <div>
          {user?.name}
          {user?.company}
        </div>
      </div>
    </div>
  );
}

That's it! Click here to see the full example.

Through these examples, you should have understood the basic usage of CCState. Next, you can read to learn about CCState's core APIs.

Core APIs

CCState provides several simple concepts to help developers better manage application states. And it can be used as an external store to drive UI frameworks like React.

State

State is the most basic value unit in CCState. A State can store any type of value, which can be accessed or modified through the store's get/set methods. Before explaining why it's designed this way, let's first look at the basic capabilities of State.

import { store, state } from 'ccstate';

const store = createStore();

const userId$ = state(0);
store.get(userId$); // 0
store.set(userId$, 100);
store.get(userId$); // 100

const user$ = state<({
  name: 'e7h4n',
  avatar: 'https://avatars.githubusercontent.com/u/813596',
} | undefined>(undefined);

store.set({
  name: 'yc-kanyun',
  avatar: 'https://avatars.githubusercontent.com/u/168416598'
});

These examples should be very easy to understand. You might notice a detail in the examples: all variables returned by state have a $ suffix. This is a naming convention used to distinguish an CCState signal type from other regular types. CCState signal types must be accessed through the store's get/set methods, and since it's common to convert an CCState signal type to a regular type using get, the $ suffix helps avoid naming conflicts.

Store

In CCState, declaring a State doesn't mean the value will be stored within the State itself. In fact, a State acts like a key in a Map, and CCState needs to create a Map to store the corresponding value for each State - this Map is the Store.

const count$ = state(0); // count$: { init: 0 }

const store = createStore(); // imagine this as new Map()
store.set(count$, 10); // simply imagine as map[count$] = 10

const otherStore = createStore(); // another new Map()
otherStore.get(count$); // anotherMap[$count] ?? $count.init, returns 0

This should be easy to understand. If Store only needed to support State types, a simple Map would be sufficient. However, CCState needs to support another signal type. Next, let's introduce Computed, CCState's reactive computation unit.

Computed

Computed is CCState's reactive computation unit. You can write derived computation logic in Computed, such as sending HTTP requests, data transformation, data aggregation, etc.

import { computed, createStore } from 'ccstate';

const userId$ = state(0);
const user$ = computed(async (get) => {
  const userId = get(userId$);
  const resp = await fetch('/api/users/' + userId);
  return resp.json();
});

const store = createStore();
const user = await store.get(user$);

Does this example seem less intuitive than State? Here's a mental model that might help you better understand what's happening:

  • computed(fn) returns an object {read: fn}, which is assigned to user$
  • When store.get(user$) encounters an object which has a read function, it calls that function: user$.read(store.get)

This way, Computed receives a get accessor that can access other signal in the store. This get accessor is similar to store.get and can be used to read both State and Computed. The reason CCState specifically passes a get method to Computed, rather than allowing direct access to the store within Computed, is to shield the logic within Computed from other store methods like store.set. The key characteristic of Computed is that it can only read states from the store but cannot modify them. In other words, Computed is side-effect free.

In most cases, side-effect free computation logic is extremely useful. They can be executed any number of times and have few requirements regarding execution timing. Computed is one of the most powerful features in CCState, and you should try to write your logic as Computed whenever possible, unless you need to perform set operations on the Store.

Command

Command is CCState's logic unit for organizing side effects. It has both set and get accessors from the store, allowing it to not only read other signal types but also modify State or call other Command.

import { command, createStore } from 'ccstate';

const user$ = state<UserInfo | undefined>(undefined);
const updateUser$ = command(async ({ set }, userId) => {
  const user = await fetch('/api/users/' + userId).then((resp) => resp.json());
  set(user$, user);
});

const store = createStore();
store.set(updateUser$, 10); // fetchUserInfo(userId=10) and set to user$

Similarly, we can imagine the set operation like this:

  • command(fn) returns an object {write: fn} which is assigned to updateUser$
  • When store.set(updateUser$) encounters an object which has a write function, it calls that function: updateUser$.write({set: store.set, get: store.get}, userId)

Since Command can call the set method, it produces side effects on the Store. Therefore, its execution timing must be explicitly specified through one of these ways:

  • Calling a Command through store.set
  • Being called by the set method within other Commands

Watching to Changes

CCState provides a watch method on the store to observe state change.

import { createStore, state, computed, command } from 'ccstate';

const base$ = state(0);
const double$ = computed((get) => get(base$) * 2);

const store = createStore();
store.watch((get) => {
  console.log('double', get(double$));
});

store.set(base$, 10); // will log to console 'double 20'

Using an AbortSignal to cancel any watcher.

const ctrl = new AbortController();
store.watch(
  (get) => {
    console.log('double', get(double$));
  },
  {
    signal: ctrl.signal,
  },
);

ctrl.abort(); // will cancel watch

Watchers intentionally do not have access to the store's set method. CCState encourages implementing derived computation logic through Computed rather than modifying data through change callbacks.

// 🙅 use watch & store.set
const user$ = state(undefined);
const userId$ = state(0);
store.watch((get) => {
  const userId = get(userId$);
  const user = fetch('/api/users/' + userId).then((resp) => resp.json());
  store.set(user$, user);
});

// ✅ use computed
const userId$ = state(0);
const user$ = computed(async (get) => {
  return await fetch('/api/users/' + get(userId$)).then((resp) => resp.json());
});

Using Computed to write reactive logic has several advantages:

  • No need to manage unsubscription
  • No need to worry about it modifying other States or calling other Command

Here's a simple rule of thumb:

if some logic can be written as a Computed, it should be written as a Computed.

Comprasion

| Type | get | set | | -------- | --- | --- | | S

View on GitHub
GitHub Stars48
CategoryDevelopment
Updated1d ago
Forks5

Languages

TypeScript

Security Score

85/100

Audited on Apr 2, 2026

No findings