@assistant-ui/store 0.2.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (110) hide show
  1. package/dist/AuiIf.d.ts +1 -1
  2. package/dist/AuiIf.d.ts.map +1 -1
  3. package/dist/Derived.d.ts +34 -0
  4. package/dist/Derived.d.ts.map +1 -0
  5. package/dist/Derived.js +24 -0
  6. package/dist/Derived.js.map +1 -0
  7. package/dist/attachTransformScopes.d.ts +11 -0
  8. package/dist/attachTransformScopes.d.ts.map +1 -0
  9. package/dist/attachTransformScopes.js +12 -0
  10. package/dist/attachTransformScopes.js.map +1 -0
  11. package/dist/index.d.ts +9 -3
  12. package/dist/index.d.ts.map +1 -1
  13. package/dist/index.js +11 -4
  14. package/dist/index.js.map +1 -1
  15. package/dist/tapClientList.d.ts +28 -0
  16. package/dist/tapClientList.d.ts.map +1 -0
  17. package/dist/tapClientList.js +68 -0
  18. package/dist/tapClientList.js.map +1 -0
  19. package/dist/tapClientLookup.d.ts +15 -0
  20. package/dist/tapClientLookup.d.ts.map +1 -0
  21. package/dist/tapClientLookup.js +42 -0
  22. package/dist/tapClientLookup.js.map +1 -0
  23. package/dist/tapClientResource.d.ts +13 -0
  24. package/dist/tapClientResource.d.ts.map +1 -0
  25. package/dist/tapClientResource.js +114 -0
  26. package/dist/tapClientResource.js.map +1 -0
  27. package/dist/types/client.d.ts +115 -0
  28. package/dist/types/client.d.ts.map +1 -0
  29. package/dist/types/client.js +2 -0
  30. package/dist/types/client.js.map +1 -0
  31. package/dist/types/events.d.ts +33 -0
  32. package/dist/types/events.d.ts.map +1 -0
  33. package/dist/types/events.js +8 -0
  34. package/dist/types/events.js.map +1 -0
  35. package/dist/useAui.d.ts +5 -3
  36. package/dist/useAui.d.ts.map +1 -1
  37. package/dist/useAui.js +7 -2
  38. package/dist/useAui.js.map +1 -1
  39. package/dist/useAuiEvent.d.ts +1 -1
  40. package/dist/useAuiEvent.d.ts.map +1 -1
  41. package/dist/useAuiEvent.js +1 -1
  42. package/dist/useAuiEvent.js.map +1 -1
  43. package/dist/useAuiState.d.ts +2 -2
  44. package/dist/useAuiState.d.ts.map +1 -1
  45. package/dist/useAuiState.js +2 -2
  46. package/dist/useAuiState.js.map +1 -1
  47. package/dist/utils/BaseProxyHandler.d.ts +23 -0
  48. package/dist/utils/BaseProxyHandler.d.ts.map +1 -0
  49. package/dist/utils/BaseProxyHandler.js +46 -0
  50. package/dist/utils/BaseProxyHandler.js.map +1 -0
  51. package/dist/utils/NotificationManager.d.ts +11 -0
  52. package/dist/utils/NotificationManager.d.ts.map +1 -0
  53. package/dist/utils/NotificationManager.js +84 -0
  54. package/dist/utils/NotificationManager.js.map +1 -0
  55. package/dist/utils/proxied-assistant-state.d.ts +8 -0
  56. package/dist/utils/proxied-assistant-state.d.ts.map +1 -0
  57. package/dist/utils/proxied-assistant-state.js +34 -0
  58. package/dist/utils/proxied-assistant-state.js.map +1 -0
  59. package/dist/utils/react-assistant-context.d.ts +1 -1
  60. package/dist/utils/react-assistant-context.d.ts.map +1 -1
  61. package/dist/utils/react-assistant-context.js +2 -1
  62. package/dist/utils/react-assistant-context.js.map +1 -1
  63. package/dist/utils/splitClients.d.ts +10 -0
  64. package/dist/utils/splitClients.d.ts.map +1 -0
  65. package/dist/utils/splitClients.js +53 -0
  66. package/dist/utils/splitClients.js.map +1 -0
  67. package/dist/utils/tap-assistant-context.d.ts +19 -0
  68. package/dist/utils/tap-assistant-context.d.ts.map +1 -0
  69. package/dist/utils/tap-assistant-context.js +23 -0
  70. package/dist/utils/tap-assistant-context.js.map +1 -0
  71. package/dist/utils/tap-client-stack-context.d.ts +23 -0
  72. package/dist/utils/tap-client-stack-context.d.ts.map +1 -0
  73. package/dist/utils/tap-client-stack-context.js +28 -0
  74. package/dist/utils/tap-client-stack-context.js.map +1 -0
  75. package/dist/wrapperResource.d.ts +3 -0
  76. package/dist/wrapperResource.d.ts.map +1 -0
  77. package/dist/wrapperResource.js +11 -0
  78. package/dist/wrapperResource.js.map +1 -0
  79. package/package.json +5 -5
  80. package/src/AuiIf.ts +1 -1
  81. package/src/Derived.ts +46 -0
  82. package/src/__tests__/hooks.test.tsx +1 -1
  83. package/src/attachTransformScopes.ts +38 -0
  84. package/src/index.ts +37 -21
  85. package/src/tapClientList.ts +121 -0
  86. package/src/tapClientLookup.ts +79 -0
  87. package/src/tapClientResource.ts +187 -0
  88. package/src/types/client.ts +180 -0
  89. package/src/types/events.ts +77 -0
  90. package/src/useAui.ts +21 -19
  91. package/src/useAuiEvent.ts +2 -2
  92. package/src/useAuiState.ts +3 -3
  93. package/src/utils/BaseProxyHandler.ts +50 -0
  94. package/src/utils/NotificationManager.ts +114 -0
  95. package/src/utils/proxied-assistant-state.ts +53 -0
  96. package/src/utils/react-assistant-context.tsx +3 -7
  97. package/src/utils/splitClients.ts +80 -0
  98. package/src/utils/tap-assistant-context.ts +58 -0
  99. package/src/utils/tap-client-stack-context.ts +51 -0
  100. package/src/wrapperResource.ts +17 -0
  101. package/dist/scope-registry-forward.d.ts +0 -6
  102. package/dist/scope-registry-forward.d.ts.map +0 -1
  103. package/dist/scope-registry-forward.js +0 -2
  104. package/dist/scope-registry-forward.js.map +0 -1
  105. package/dist/scope-registry.d.ts +0 -17
  106. package/dist/scope-registry.d.ts.map +0 -1
  107. package/dist/scope-registry.js +0 -2
  108. package/dist/scope-registry.js.map +0 -1
  109. package/src/scope-registry-forward.ts +0 -6
  110. package/src/scope-registry.ts +0 -15
@@ -0,0 +1,187 @@
1
+ import {
2
+ tapEffect,
3
+ tapMemo,
4
+ tapRef,
5
+ type ResourceElement,
6
+ tapResource,
7
+ } from "@assistant-ui/tap";
8
+ import type { ClientMethods } from "./types/client";
9
+ import {
10
+ tapClientStack,
11
+ tapWithClientStack,
12
+ SYMBOL_CLIENT_INDEX,
13
+ } from "./utils/tap-client-stack-context";
14
+ import {
15
+ BaseProxyHandler,
16
+ handleIntrospectionProp,
17
+ } from "./utils/BaseProxyHandler";
18
+ import { wrapperResource } from "./wrapperResource";
19
+
20
+ /**
21
+ * Symbol used internally to get state from ClientProxy.
22
+ * This allows getState() to be optional in the user-facing client.
23
+ */
24
+ const SYMBOL_GET_OUTPUT = Symbol("assistant-ui.store.getValue");
25
+
26
+ type ClientInternal = {
27
+ [SYMBOL_GET_OUTPUT]: ClientMethods;
28
+ };
29
+
30
+ export const getClientState = (client: ClientMethods) => {
31
+ const output = (client as unknown as ClientInternal)[SYMBOL_GET_OUTPUT];
32
+ if (!output) {
33
+ throw new Error(
34
+ "Client scope contains a non-client resource. " +
35
+ "Ensure your Derived get() returns a client created with tapClientResource(), not a plain resource.",
36
+ );
37
+ }
38
+ return (output as any).getState?.();
39
+ };
40
+
41
+ // Global cache for function templates by field name
42
+ const fieldAccessFns = new Map<
43
+ string | symbol,
44
+ (this: unknown, ...args: unknown[]) => unknown
45
+ >();
46
+
47
+ function getOrCreateProxyFn(prop: string | symbol) {
48
+ let template = fieldAccessFns.get(prop);
49
+ if (!template) {
50
+ template = function (this: unknown, ...args: unknown[]) {
51
+ if (!this || typeof this !== "object") {
52
+ throw new Error(
53
+ `Method "${String(prop)}" called without proper context. ` +
54
+ `This may indicate the function was called incorrectly.`,
55
+ );
56
+ }
57
+
58
+ const output = (this as ClientInternal)[SYMBOL_GET_OUTPUT];
59
+ if (!output) {
60
+ throw new Error(
61
+ `Method "${String(prop)}" called on invalid client proxy. ` +
62
+ `Ensure you are calling this method on a valid client instance.`,
63
+ );
64
+ }
65
+
66
+ const method = output[prop];
67
+ if (!method)
68
+ throw new Error(`Method "${String(prop)}" is not implemented.`);
69
+ if (typeof method !== "function")
70
+ throw new Error(`"${String(prop)}" is not a function.`);
71
+ return method(...args);
72
+ };
73
+ fieldAccessFns.set(prop, template);
74
+ }
75
+ return template;
76
+ }
77
+
78
+ class ClientProxyHandler
79
+ extends BaseProxyHandler
80
+ implements ProxyHandler<object>
81
+ {
82
+ private boundFns: Map<string | symbol, Function> | undefined;
83
+ private cachedReceiver: unknown;
84
+
85
+ constructor(
86
+ private readonly outputRef: {
87
+ current: ClientMethods;
88
+ },
89
+ private readonly index: number,
90
+ ) {
91
+ super();
92
+ }
93
+
94
+ get(_: unknown, prop: string | symbol, receiver: unknown) {
95
+ if (prop === SYMBOL_GET_OUTPUT) return this.outputRef.current;
96
+ if (prop === SYMBOL_CLIENT_INDEX) return this.index;
97
+ const introspection = handleIntrospectionProp(prop, "ClientProxy");
98
+ if (introspection !== false) return introspection;
99
+ const value = this.outputRef.current[prop];
100
+ if (typeof value === "function") {
101
+ if (this.cachedReceiver !== receiver) {
102
+ this.boundFns = new Map();
103
+ this.cachedReceiver = receiver;
104
+ }
105
+ let bound = this.boundFns!.get(prop);
106
+ if (!bound) {
107
+ bound = getOrCreateProxyFn(prop).bind(receiver);
108
+ this.boundFns!.set(prop, bound);
109
+ }
110
+ return bound;
111
+ }
112
+ return value;
113
+ }
114
+
115
+ ownKeys(): ArrayLike<string | symbol> {
116
+ return Object.keys(this.outputRef.current);
117
+ }
118
+
119
+ has(_: unknown, prop: string | symbol) {
120
+ if (prop === SYMBOL_GET_OUTPUT) return true;
121
+ if (prop === SYMBOL_CLIENT_INDEX) return true;
122
+ return prop in this.outputRef.current;
123
+ }
124
+ }
125
+
126
+ /**
127
+ * Resource that wraps a plain resource element to create a stable client proxy.
128
+ *
129
+ * Takes a ResourceElement that returns methods (with optional getState()) and
130
+ * wraps it to produce a stable client proxy. This adds the client to the
131
+ * client stack, enabling event scoping.
132
+ *
133
+ * @internal
134
+ */
135
+ export const ClientResource = wrapperResource(
136
+ <TMethods extends ClientMethods>(
137
+ element: ResourceElement<TMethods>,
138
+ ): {
139
+ methods: TMethods;
140
+ state: unknown;
141
+ key: string | number | undefined;
142
+ } => {
143
+ const valueRef = tapRef(null as unknown as TMethods);
144
+
145
+ const index = tapClientStack().length;
146
+ const methods = tapMemo(
147
+ () =>
148
+ new Proxy<TMethods>(
149
+ {} as TMethods,
150
+ new ClientProxyHandler(valueRef, index),
151
+ ),
152
+ [index],
153
+ );
154
+
155
+ const value = tapWithClientStack(methods, () => tapResource(element));
156
+ if (!valueRef.current) {
157
+ valueRef.current = value;
158
+ }
159
+
160
+ tapEffect(() => {
161
+ valueRef.current = value;
162
+ });
163
+
164
+ const state = (value as any).getState?.();
165
+ return { methods, state, key: element.key };
166
+ },
167
+ );
168
+
169
+ type InferClientState<TMethods> = TMethods extends {
170
+ getState: () => infer S;
171
+ }
172
+ ? S
173
+ : undefined;
174
+
175
+ export const tapClientResource = <TMethods extends ClientMethods>(
176
+ element: ResourceElement<TMethods>,
177
+ ): {
178
+ state: InferClientState<TMethods>;
179
+ methods: TMethods;
180
+ key: string | number | undefined;
181
+ } => {
182
+ return tapResource(ClientResource(element)) as {
183
+ state: InferClientState<TMethods>;
184
+ methods: TMethods;
185
+ key: string | number | undefined;
186
+ };
187
+ };
@@ -0,0 +1,180 @@
1
+ import type { ResourceElement } from "@assistant-ui/tap";
2
+ import type {
3
+ AssistantEventName,
4
+ AssistantEventCallback,
5
+ AssistantEventSelector,
6
+ } from "./events";
7
+
8
+ /**
9
+ * Base type for methods that can be called on a client.
10
+ */
11
+ export interface ClientMethods {
12
+ [key: string | symbol]: (...args: any[]) => any;
13
+ }
14
+
15
+ type ClientMetaType = { source: ClientNames; query: Record<string, unknown> };
16
+
17
+ /**
18
+ * Schema of a client in the assistant system (internal type).
19
+ * @template TState - The state type for this client
20
+ * @template TMethods - The methods available on this client
21
+ * @template TMeta - Source/query metadata (optional)
22
+ * @template TEvents - Events that this client can emit (optional)
23
+ * @internal
24
+ */
25
+ export type ClientSchema<
26
+ TMethods extends ClientMethods = ClientMethods,
27
+ TMeta extends ClientMetaType = never,
28
+ TEvents extends Record<string, unknown> = never,
29
+ > = {
30
+ methods: TMethods;
31
+ meta?: TMeta;
32
+ events?: TEvents;
33
+ };
34
+
35
+ /**
36
+ * Module augmentation interface for assistant-ui store type extensions.
37
+ *
38
+ * @example
39
+ * ```typescript
40
+ * declare module "@assistant-ui/store" {
41
+ * interface ScopeRegistry {
42
+ * // Simple client (meta and events are optional)
43
+ * foo: {
44
+ * methods: {
45
+ * getState: () => { bar: string };
46
+ * updateBar: (bar: string) => void;
47
+ * };
48
+ * };
49
+ * // Full client with meta and events
50
+ * bar: {
51
+ * methods: {
52
+ * getState: () => { id: string };
53
+ * update: () => void;
54
+ * };
55
+ * meta: { source: "fooList"; query: { index: number } };
56
+ * events: {
57
+ * "bar.updated": { id: string };
58
+ * };
59
+ * };
60
+ * }
61
+ * }
62
+ * ```
63
+ */
64
+ export interface ScopeRegistry {}
65
+
66
+ type ClientEventsType<K extends ClientNames> = Record<
67
+ `${K}.${string}`,
68
+ unknown
69
+ >;
70
+
71
+ type ClientError<E extends string> = {
72
+ methods: Record<E, () => E>;
73
+ meta: { source: ClientNames; query: Record<E, E> };
74
+ events: Record<`${E}.`, E>;
75
+ };
76
+
77
+ type ValidateClient<K extends keyof ScopeRegistry> = ScopeRegistry[K] extends {
78
+ methods: ClientMethods;
79
+ }
80
+ ? "meta" extends keyof ScopeRegistry[K]
81
+ ? ScopeRegistry[K]["meta"] extends ClientMetaType
82
+ ? "events" extends keyof ScopeRegistry[K]
83
+ ? ScopeRegistry[K]["events"] extends ClientEventsType<K>
84
+ ? ScopeRegistry[K]
85
+ : ClientError<`ERROR: ${K & string} has invalid events type`>
86
+ : ScopeRegistry[K]
87
+ : ClientError<`ERROR: ${K & string} has invalid meta type`>
88
+ : "events" extends keyof ScopeRegistry[K]
89
+ ? ScopeRegistry[K]["events"] extends ClientEventsType<K>
90
+ ? ScopeRegistry[K]
91
+ : ClientError<`ERROR: ${K & string} has invalid events type`>
92
+ : ScopeRegistry[K]
93
+ : ClientError<`ERROR: ${K & string} has invalid methods type`>;
94
+
95
+ type ClientSchemas = keyof ScopeRegistry extends never
96
+ ? {
97
+ "ERROR: No clients were defined": ClientError<"ERROR: No clients were defined">;
98
+ }
99
+ : { [K in keyof ScopeRegistry]: ValidateClient<K> };
100
+
101
+ /**
102
+ * Output type that client resources return (just methods).
103
+ *
104
+ * @example
105
+ * ```typescript
106
+ * const FooResource = resource((): ClientResourceOutput<"foo"> => {
107
+ * const [state, setState] = tapState({ bar: "hello" });
108
+ * return {
109
+ * getState: () => state,
110
+ * updateBar: (b) => setState({ bar: b }),
111
+ * };
112
+ * });
113
+ * ```
114
+ */
115
+ export type ClientOutput<K extends ClientNames> = ClientSchemas[K]["methods"] &
116
+ ClientMethods;
117
+
118
+ export type ClientNames = keyof ClientSchemas extends infer U ? U : never;
119
+
120
+ export type ClientEvents<K extends ClientNames> =
121
+ "events" extends keyof ClientSchemas[K]
122
+ ? ClientSchemas[K]["events"] extends ClientEventsType<K>
123
+ ? ClientSchemas[K]["events"]
124
+ : never
125
+ : never;
126
+
127
+ export type ClientMeta<K extends ClientNames> =
128
+ "meta" extends keyof ClientSchemas[K]
129
+ ? Pick<
130
+ ClientSchemas[K]["meta"] extends ClientMetaType
131
+ ? ClientSchemas[K]["meta"]
132
+ : never,
133
+ "source" | "query"
134
+ >
135
+ : never;
136
+
137
+ export type ClientElement<K extends ClientNames> = ResourceElement<
138
+ ClientOutput<K>
139
+ >;
140
+
141
+ /**
142
+ * Unsubscribe function type.
143
+ */
144
+ export type Unsubscribe = () => void;
145
+
146
+ /**
147
+ * State type extracted from all clients via their getState() methods.
148
+ */
149
+ export type AssistantState = {
150
+ [K in ClientNames]: ClientSchemas[K]["methods"] extends {
151
+ getState: () => infer S;
152
+ }
153
+ ? S
154
+ : never;
155
+ };
156
+
157
+ /**
158
+ * Type for a client accessor - a function that returns the methods,
159
+ * with source/query metadata attached (derived from meta).
160
+ */
161
+ export type AssistantClientAccessor<K extends ClientNames> =
162
+ (() => ClientSchemas[K]["methods"]) &
163
+ (
164
+ | ClientMeta<K>
165
+ | { source: "root"; query: Record<string, never> }
166
+ | { source: null; query: null }
167
+ ) & { name: K };
168
+
169
+ /**
170
+ * The assistant client type with all registered clients.
171
+ */
172
+ export type AssistantClient = {
173
+ [K in ClientNames]: AssistantClientAccessor<K>;
174
+ } & {
175
+ subscribe(listener: () => void): Unsubscribe;
176
+ on<TEvent extends AssistantEventName>(
177
+ selector: AssistantEventSelector<TEvent>,
178
+ callback: AssistantEventCallback<TEvent>,
179
+ ): Unsubscribe;
180
+ };
@@ -0,0 +1,77 @@
1
+ import type {
2
+ AssistantClientAccessor,
3
+ ClientEvents,
4
+ ClientNames,
5
+ } from "./client";
6
+
7
+ // --- Event Map Construction ---
8
+ type UnionToIntersection<U> = (
9
+ U extends unknown
10
+ ? (x: U) => void
11
+ : never
12
+ ) extends (x: infer I) => void
13
+ ? I
14
+ : never;
15
+
16
+ type ClientEventMap = UnionToIntersection<
17
+ { [K in ClientNames]: ClientEvents<K> }[ClientNames]
18
+ >;
19
+
20
+ // --- Core Types ---
21
+
22
+ type WildcardPayload = {
23
+ [K in keyof ClientEventMap]: { event: K; payload: ClientEventMap[K] };
24
+ }[keyof ClientEventMap];
25
+
26
+ export type AssistantEventPayload = ClientEventMap & { "*": WildcardPayload };
27
+
28
+ export type AssistantEventName = keyof AssistantEventPayload;
29
+
30
+ type EventSource<T extends AssistantEventName> =
31
+ T extends `${infer Source}.${string}` ? Source : never;
32
+
33
+ // --- Scoping ---
34
+
35
+ type ParentOf<K extends ClientNames> =
36
+ AssistantClientAccessor<K> extends { source: infer S }
37
+ ? S extends ClientNames
38
+ ? S
39
+ : never
40
+ : never;
41
+
42
+ type AncestorsOf<
43
+ K extends ClientNames,
44
+ Seen extends ClientNames = never,
45
+ > = K extends Seen
46
+ ? never
47
+ : ParentOf<K> extends never
48
+ ? never
49
+ : ParentOf<K> | AncestorsOf<ParentOf<K>, Seen | K>;
50
+
51
+ /** Valid scopes: `"*"` | event source | ancestors of event source */
52
+ export type AssistantEventScope<TEvent extends AssistantEventName> =
53
+ | "*"
54
+ | EventSource<TEvent>
55
+ | (EventSource<TEvent> extends ClientNames
56
+ ? AncestorsOf<EventSource<TEvent>>
57
+ : never);
58
+
59
+ // --- Selection & Callbacks ---
60
+
61
+ export type AssistantEventSelector<TEvent extends AssistantEventName> =
62
+ | TEvent
63
+ | { scope: AssistantEventScope<TEvent>; event: TEvent };
64
+
65
+ export const normalizeEventSelector = <TEvent extends AssistantEventName>(
66
+ selector: AssistantEventSelector<TEvent>,
67
+ ) => {
68
+ if (typeof selector === "string") {
69
+ const source = selector.split(".")[0] as AssistantEventScope<TEvent>;
70
+ return { scope: source, event: selector };
71
+ }
72
+ return { scope: selector.scope, event: selector.event };
73
+ };
74
+
75
+ export type AssistantEventCallback<TEvent extends AssistantEventName> = (
76
+ payload: AssistantEventPayload[TEvent],
77
+ ) => void;
package/src/useAui.ts CHANGED
@@ -18,32 +18,32 @@ import type {
18
18
  ClientNames,
19
19
  ClientElement,
20
20
  ClientMeta,
21
- } from "@assistant-ui/core/store";
21
+ } from "./types/client";
22
+ import { Derived, DerivedElement } from "./Derived";
23
+ import {
24
+ useAssistantContextValue,
25
+ DefaultAssistantClient,
26
+ createRootAssistantClient,
27
+ } from "./utils/react-assistant-context";
28
+ import {
29
+ DerivedClients,
30
+ RootClients,
31
+ tapSplitClients,
32
+ } from "./utils/splitClients";
22
33
  import {
23
- Derived,
24
- type DerivedElement,
25
- type ScopesConfig,
26
34
  normalizeEventSelector,
27
35
  type AssistantEventName,
28
36
  type AssistantEventCallback,
29
37
  type AssistantEventSelector,
30
- tapClientResource,
31
- } from "@assistant-ui/core/store";
38
+ } from "./types/events";
39
+ import { NotificationManager } from "./utils/NotificationManager";
40
+ import { withAssistantTapContextProvider } from "./utils/tap-assistant-context";
41
+ import { tapClientResource } from "./tapClientResource";
42
+ import { getClientIndex } from "./utils/tap-client-stack-context";
32
43
  import {
33
- NotificationManager,
34
- withAssistantTapContextProvider,
35
- getClientIndex,
36
44
  PROXIED_ASSISTANT_STATE_SYMBOL,
37
45
  createProxiedAssistantState,
38
- type RootClients,
39
- type DerivedClients,
40
- tapSplitClients,
41
- } from "@assistant-ui/core/store/internal";
42
- import {
43
- useAssistantContextValue,
44
- DefaultAssistantClient,
45
- createRootAssistantClient,
46
- } from "./utils/react-assistant-context";
46
+ } from "./utils/proxied-assistant-state";
47
47
 
48
48
  const tapShallowMemoArray = <T>(array: readonly T[]) => {
49
49
  // biome-ignore lint/correctness/useExhaustiveDependencies: shallow memo
@@ -354,7 +354,9 @@ export const AssistantClientResource = resource(
354
354
  );
355
355
 
356
356
  export namespace useAui {
357
- export type Props = ScopesConfig;
357
+ export type Props = {
358
+ [K in ClientNames]?: ClientElement<K> | DerivedElement<K>;
359
+ };
358
360
  }
359
361
 
360
362
  export function useAui(): AssistantClient;
@@ -5,8 +5,8 @@ import type {
5
5
  AssistantEventName,
6
6
  AssistantEventCallback,
7
7
  AssistantEventSelector,
8
- } from "@assistant-ui/core/store";
9
- import { normalizeEventSelector } from "@assistant-ui/core/store";
8
+ } from "./types/events";
9
+ import { normalizeEventSelector } from "./types/events";
10
10
 
11
11
  export const useAuiEvent = <TEvent extends AssistantEventName>(
12
12
  selector: AssistantEventSelector<TEvent>,
@@ -1,7 +1,7 @@
1
1
  import { useSyncExternalStore, useDebugValue } from "react";
2
- import type { AssistantState } from "@assistant-ui/core/store";
3
- import { getProxiedAssistantState } from "@assistant-ui/core/store/internal";
2
+ import type { AssistantState } from "./types/client";
4
3
  import { useAui } from "./useAui";
4
+ import { getProxiedAssistantState } from "./utils/proxied-assistant-state";
5
5
 
6
6
  /**
7
7
  * Hook to access a slice of the assistant state with automatic subscription
@@ -15,7 +15,7 @@ import { useAui } from "./useAui";
15
15
  * foo: RootScope({ ... }),
16
16
  * });
17
17
  *
18
- * const bar = useAuiState((state) => state.foo.bar);
18
+ * const bar = useAuiState((s) => s.foo.bar);
19
19
  * ```
20
20
  */
21
21
  export const useAuiState = <T>(selector: (state: AssistantState) => T): T => {
@@ -0,0 +1,50 @@
1
+ const INTROSPECTION_PROPS = new Set(["$$typeof", "nodeType", "then"]);
2
+
3
+ /**
4
+ * Handles common proxy introspection properties.
5
+ * Returns the appropriate value for toStringTag, toJSON, and props that should return undefined.
6
+ * Returns `false` if the prop should be handled by the subclass.
7
+ */
8
+ export const handleIntrospectionProp = (
9
+ prop: string | symbol,
10
+ name: string,
11
+ ): unknown | false => {
12
+ if (prop === Symbol.toStringTag) return name;
13
+ if (typeof prop === "symbol") return undefined;
14
+ if (prop === "toJSON") return () => name;
15
+ if (INTROSPECTION_PROPS.has(prop)) return undefined;
16
+ return false;
17
+ };
18
+
19
+ export abstract class BaseProxyHandler implements ProxyHandler<object> {
20
+ abstract get(_: unknown, prop: string | symbol, receiver?: unknown): unknown;
21
+ abstract ownKeys(): ArrayLike<string | symbol>;
22
+ abstract has(_: unknown, prop: string | symbol): boolean;
23
+
24
+ getOwnPropertyDescriptor(_: unknown, prop: string | symbol) {
25
+ const value = this.get(_, prop);
26
+ if (value === undefined) return undefined;
27
+ return {
28
+ value,
29
+ writable: false,
30
+ enumerable: true,
31
+ configurable: false,
32
+ };
33
+ }
34
+
35
+ set() {
36
+ return false;
37
+ }
38
+ setPrototypeOf() {
39
+ return false;
40
+ }
41
+ defineProperty() {
42
+ return false;
43
+ }
44
+ deleteProperty() {
45
+ return false;
46
+ }
47
+ preventExtensions(): boolean {
48
+ return false;
49
+ }
50
+ }
@@ -0,0 +1,114 @@
1
+ import { resource } from "@assistant-ui/tap";
2
+ import type { ClientStack } from "./tap-client-stack-context";
3
+ import type {
4
+ AssistantEventName,
5
+ AssistantEventPayload,
6
+ } from "../types/events";
7
+ import { Unsubscribe } from "../types/client";
8
+ import { tapConst } from "@assistant-ui/tap";
9
+
10
+ type InternalCallback = (payload: unknown, clientStack: ClientStack) => void;
11
+
12
+ export type NotificationManager = {
13
+ on<TEvent extends AssistantEventName>(
14
+ event: TEvent,
15
+ callback: (
16
+ payload: AssistantEventPayload[TEvent],
17
+ clientStack: ClientStack,
18
+ ) => void,
19
+ ): Unsubscribe;
20
+ emit<TEvent extends Exclude<AssistantEventName, "*">>(
21
+ event: TEvent,
22
+ payload: AssistantEventPayload[TEvent],
23
+ clientStack: ClientStack,
24
+ ): void;
25
+ subscribe(callback: () => void): Unsubscribe;
26
+ notifySubscribers(): void;
27
+ };
28
+
29
+ export const NotificationManager = resource((): NotificationManager => {
30
+ return tapConst(() => {
31
+ const listeners = new Map<string, Set<InternalCallback>>();
32
+ const wildcardListeners = new Set<InternalCallback>();
33
+ const subscribers = new Set<() => void>();
34
+
35
+ return {
36
+ on(event, callback) {
37
+ const cb = callback as InternalCallback;
38
+ if (event === "*") {
39
+ wildcardListeners.add(cb);
40
+ return () => wildcardListeners.delete(cb);
41
+ }
42
+
43
+ let set = listeners.get(event);
44
+ if (!set) {
45
+ set = new Set();
46
+ listeners.set(event, set);
47
+ }
48
+ set.add(cb);
49
+
50
+ return () => {
51
+ set!.delete(cb);
52
+ if (set!.size === 0) listeners.delete(event);
53
+ };
54
+ },
55
+
56
+ emit(event, payload, clientStack) {
57
+ const eventListeners = listeners.get(event);
58
+ if (!eventListeners && wildcardListeners.size === 0) return;
59
+
60
+ queueMicrotask(() => {
61
+ const errors = [];
62
+ if (eventListeners) {
63
+ for (const cb of eventListeners) {
64
+ try {
65
+ cb(payload, clientStack);
66
+ } catch (e) {
67
+ errors.push(e);
68
+ }
69
+ }
70
+ }
71
+ if (wildcardListeners.size > 0) {
72
+ const wrapped = { event, payload };
73
+ for (const cb of wildcardListeners) {
74
+ try {
75
+ cb(wrapped, clientStack);
76
+ } catch (e) {
77
+ errors.push(e);
78
+ }
79
+ }
80
+ }
81
+
82
+ if (errors.length > 0) {
83
+ if (errors.length === 1) {
84
+ throw errors[0];
85
+ } else {
86
+ for (const error of errors) {
87
+ console.error(error);
88
+ }
89
+ throw new AggregateError(
90
+ errors,
91
+ "Errors occurred during event emission",
92
+ );
93
+ }
94
+ }
95
+ });
96
+ },
97
+
98
+ subscribe(callback) {
99
+ subscribers.add(callback);
100
+ return () => subscribers.delete(callback);
101
+ },
102
+
103
+ notifySubscribers() {
104
+ for (const cb of subscribers) {
105
+ try {
106
+ cb();
107
+ } catch (e) {
108
+ console.error("NotificationManager: subscriber callback error", e);
109
+ }
110
+ }
111
+ },
112
+ };
113
+ }, []);
114
+ });