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

138 lines
5.9 KiB
Markdown

# @assistant-ui/store Specification
React integration for tap. Type-safe client-based state via module augmentation.
## Types
### ScopeRegistry
```typescript
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
```typescript
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
```typescript
useAui(): AssistantClient;
useAui(clients: { [K]?: ClientElement<K> | DerivedElement<K> }): AssistantClient;
```
Flow: splitClients → apply transformScopes → mount root clients → create derived accessors → merge with parent.
### useAuiState
```typescript
useAuiState<T>(selector: (state: AssistantState) => T): T;
```
`useSyncExternalStore` with proxied state. **Throws** if selector returns proxy (must return specific value).
### useAuiEvent
```typescript
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
```typescript
<AuiProvider value={aui}>{children}</AuiProvider>
<AuiIf condition={(s) => boolean}>{children}</AuiIf>
```
### Derived
```typescript
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
```typescript
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
```typescript
useAssistantClientRef(): { current: AssistantClient };
useAssistantEmit(): <E>(event: E, payload) => void; // Stable via useEffectEvent
```
### useClientResource
```typescript
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
```typescript
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
```typescript
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
```typescript
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.foo``aui.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