Files
wehub-resource-sync e30e75b5d4
Changesets / Create Version PR (push) Has been cancelled
Deploy Shadcn Registry / Deploy Production (push) Has been cancelled
Template Metrics / LOC + Bundle Size (push) Has been cancelled
Code Quality / Oxlint + Oxfmt (push) Has been cancelled
Code Quality / Template Sync (push) Has been cancelled
Code Quality / Build Changed Packages (push) Has been cancelled
Code Quality / Test Changed Packages (push) Has been cancelled
Deploy Expo Example / Deploy Production (push) Has been cancelled
Deploy Ink Example / Deploy Production (push) Has been cancelled
Python Tests / pytest (assistant-stream, 3.10) (push) Has been cancelled
Python Tests / pytest (assistant-stream, 3.12) (push) Has been cancelled
Python Tests / pytest (assistant-ui-sync-server-api, 3.10) (push) Has been cancelled
Python Tests / pytest (assistant-ui-sync-server-api, 3.12) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:40:13 +08:00

5.9 KiB

@assistant-ui/store Specification

React integration for tap. Type-safe client-based state via module augmentation.

Types

ScopeRegistry

declare module "@assistant-ui/store" {
  interface ScopeRegistry {
    name: {
      methods: MethodsType; // must include getState(): StateType
      meta?: { source: ClientNames; query: QueryType };
      events?: { "name.event": PayloadType };
    };
  }
}

Core Types

type ClientOutput<K> = ClientSchemas[K]["methods"] & ClientMethods;
type ClientMethods = { [key: string]: (...args: any[]) => any };
type AssistantClientAccessor<K> = (() => Methods<K>) & ({ source; query } | { source: "root"; query: {} } | { source: null; query: null });
type AssistantClient = { [K]: AssistantClientAccessor<K>; subscribe(cb): Unsubscribe; on(selector, cb): Unsubscribe };
type AssistantState = { [K]: ReturnType<ClientSchemas[K]["methods"]["getState"]> };

API

useAui

useAui(): AssistantClient;
useAui(clients: { [K]?: ClientElement<K> | DerivedElement<K> }): AssistantClient;

Flow: splitClients → apply transformScopes → mount root clients → create derived accessors → merge with parent.

useAuiState

useAuiState<T>(selector: (state: AssistantState) => T): T;

useSyncExternalStore with proxied state. Throws if selector returns proxy (must return specific value).

useAuiEvent

useAuiEvent<E>(selector: E | { scope: EventScope<E>; event: E }, callback: (payload) => void): void;

Selectors: "client.event" | { scope: "parent", event } | { scope: "*", event }. Wildcard "*" receives all.

AuiProvider / AuiIf

<AuiProvider value={aui}>{children}</AuiProvider>
<AuiIf condition={(s) => boolean}>{children}</AuiIf>

Derived

Derived<K>({ source, query, get: (client) => methods });

Returns marker element. Meta (source, query) keys the derived scope's resource fiber — a different meta yields a new client function in the same render pass, so consumers see the new derivation immediately rather than after the next commit.

attachTransformScopes

attachTransformScopes(resource, (scopes, parent) => newScopes): void;

Attaches a function that receives the current scopes config and the parent AssistantClient, and returns a new scopes config. The transform can inspect parent[key].source to check whether a scope exists in parent context (null = not provided). Transforms are collected from root elements and run iteratively (new root elements added by transforms are also processed). Single transform per resource; throws on duplicate attach.

useAssistantClientRef / useAssistantEmit

useAssistantClientRef(): { current: AssistantClient };
useAssistantEmit(): <E>(event: E, payload) => void;  // Stable via useEffectEvent

useClientResource

useClientResource(element: ResourceElement<TMethods>): { state: InferClientState<TMethods>; methods: TMethods; key: string | number | undefined };

Wraps resource element to create stable client proxy. Adds client to stack for event scoping. Use for 1:1 client mappings. State is inferred from the getState() method if present.

useClientLookup

useClientLookup<TMethods extends ClientMethods>(
  getElements: () => readonly ResourceElement<TMethods>[],
  getElementsDeps: readonly unknown[]
): { state: InferClientState<TMethods>[]; get: (lookup: { index: number } | { key: string }) => TMethods };

Wraps each element with useClientResource. Throws on lookup miss.

useClientList

useClientList<TData, TMethods extends ClientMethods>({
  initialValues: TData[];
  getKey: (data: TData) => string;
  resource: ContravariantResource<TMethods, ResourceProps<TData>>;
}): { state: InferClientState<TMethods>[]; get: (lookup: { index: number } | { key: string }) => TMethods; add: (data: TData) => void };

type ResourceProps<TData> = { key: string; getInitialData: () => TData; remove: () => void };

Wraps useClientLookup. getInitialData may only be called once. Throws on duplicate key add.

Events

type AssistantEventName = keyof ClientEventMap | "*";
type AssistantEventScope<E> = "*" | EventSource<E> | AncestorsOf<EventSource<E>>;
type AssistantEventSelector<E> = E | { scope: Scope<E>; event: E };

Flow: useAssistantEmit captures client stack → emit queues via microtask → NotificationManager notifies → scope filtering.

Implementation

Component Behavior
useClientResource Mounts element → stable proxy via useMemo → delegates to ref → SYMBOL_GET_OUTPUT for internal access
ProxiedState Proxy intercepts state.fooaui.foo()SYMBOL_GET_OUTPUT
Client Stack Context stack per level. Emit captures stack. Listeners filter by matching stack
NotificationManager Handles events (on/emit) and state subscriptions (subscribe/notifySubscribers)
splitClients Separate root/derived → collect and run transformScopes iteratively → filter parent-provided scopes

Design

Audience API Surface
Users useAui, useAuiState, useAuiEvent, AuiProvider, AuiIf, Derived
Authors Above + use*, attachTransformScopes, ClientOutput, ScopeRegistry
Internal utils/*

Terminology: Client (React Query pattern), methods (not actions), meta (optional source/query), events (optional).

Invariants

  1. ScopeRegistry must have ≥1 client (compile error otherwise)
  2. Resources return methods object matching ClientOutput<K> (with getState() for state access)
  3. Events: "clientName.eventName" format
  4. meta.source must be valid ClientNames
  5. useAuiState selector cannot return whole state
  6. Single transformScopes per resource; transform receives (scopes, parent) to inspect parent context