@assistant-ui/store 0.0.2 → 0.0.3

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 (155) hide show
  1. package/README.md +59 -262
  2. package/dist/AssistantIf.d.ts +4 -6
  3. package/dist/AssistantIf.d.ts.map +1 -1
  4. package/dist/AssistantIf.js +1 -4
  5. package/dist/AssistantIf.js.map +1 -1
  6. package/dist/Derived.d.ts +34 -0
  7. package/dist/Derived.d.ts.map +1 -0
  8. package/dist/Derived.js +11 -0
  9. package/dist/Derived.js.map +1 -0
  10. package/dist/attachDefaultPeers.d.ts +56 -0
  11. package/dist/attachDefaultPeers.d.ts.map +1 -0
  12. package/dist/attachDefaultPeers.js +22 -0
  13. package/dist/attachDefaultPeers.js.map +1 -0
  14. package/dist/index.d.ts +10 -9
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +17 -13
  17. package/dist/index.js.map +1 -1
  18. package/dist/tapClientList.d.ts +24 -0
  19. package/dist/tapClientList.d.ts.map +1 -0
  20. package/dist/tapClientList.js +72 -0
  21. package/dist/tapClientList.js.map +1 -0
  22. package/dist/tapClientLookup.d.ts +11 -0
  23. package/dist/tapClientLookup.d.ts.map +1 -0
  24. package/dist/tapClientLookup.js +42 -0
  25. package/dist/tapClientLookup.js.map +1 -0
  26. package/dist/tapClientResource.d.ts +24 -0
  27. package/dist/tapClientResource.d.ts.map +1 -0
  28. package/dist/tapClientResource.js +100 -0
  29. package/dist/tapClientResource.js.map +1 -0
  30. package/dist/types/client.d.ts +117 -0
  31. package/dist/types/client.d.ts.map +1 -0
  32. package/dist/types/client.js +1 -0
  33. package/dist/types/events.d.ts +33 -0
  34. package/dist/types/events.d.ts.map +1 -0
  35. package/dist/types/events.js +12 -0
  36. package/dist/types/events.js.map +1 -0
  37. package/dist/useAssistantClient.d.ts +13 -46
  38. package/dist/useAssistantClient.d.ts.map +1 -1
  39. package/dist/useAssistantClient.js +177 -137
  40. package/dist/useAssistantClient.js.map +1 -1
  41. package/dist/useAssistantEvent.d.ts +2 -2
  42. package/dist/useAssistantEvent.d.ts.map +1 -1
  43. package/dist/useAssistantEvent.js +3 -6
  44. package/dist/useAssistantEvent.js.map +1 -1
  45. package/dist/useAssistantState.d.ts +2 -2
  46. package/dist/useAssistantState.d.ts.map +1 -1
  47. package/dist/useAssistantState.js +7 -36
  48. package/dist/useAssistantState.js.map +1 -1
  49. package/dist/utils/BaseProxyHandler.d.ts +23 -0
  50. package/dist/utils/BaseProxyHandler.d.ts.map +1 -0
  51. package/dist/utils/BaseProxyHandler.js +41 -0
  52. package/dist/utils/BaseProxyHandler.js.map +1 -0
  53. package/dist/utils/NotificationManager.d.ts +11 -0
  54. package/dist/utils/NotificationManager.d.ts.map +1 -0
  55. package/dist/utils/NotificationManager.js +81 -0
  56. package/dist/utils/NotificationManager.js.map +1 -0
  57. package/dist/utils/StoreResource.d.ts +14 -0
  58. package/dist/utils/StoreResource.d.ts.map +1 -0
  59. package/dist/utils/StoreResource.js +23 -0
  60. package/dist/utils/StoreResource.js.map +1 -0
  61. package/dist/utils/proxied-assistant-state.d.ts +8 -0
  62. package/dist/utils/proxied-assistant-state.d.ts.map +1 -0
  63. package/dist/utils/proxied-assistant-state.js +41 -0
  64. package/dist/utils/proxied-assistant-state.js.map +1 -0
  65. package/dist/{AssistantContext.d.ts → utils/react-assistant-context.d.ts} +6 -6
  66. package/dist/utils/react-assistant-context.d.ts.map +1 -0
  67. package/dist/utils/react-assistant-context.js +73 -0
  68. package/dist/utils/react-assistant-context.js.map +1 -0
  69. package/dist/utils/splitClients.d.ts +28 -0
  70. package/dist/utils/splitClients.d.ts.map +1 -0
  71. package/dist/utils/splitClients.js +37 -0
  72. package/dist/utils/splitClients.js.map +1 -0
  73. package/dist/utils/tap-assistant-context.d.ts +19 -0
  74. package/dist/utils/tap-assistant-context.d.ts.map +1 -0
  75. package/dist/utils/tap-assistant-context.js +37 -0
  76. package/dist/utils/tap-assistant-context.js.map +1 -0
  77. package/dist/utils/tap-client-stack-context.d.ts +23 -0
  78. package/dist/utils/tap-client-stack-context.d.ts.map +1 -0
  79. package/dist/utils/tap-client-stack-context.js +30 -0
  80. package/dist/utils/tap-client-stack-context.js.map +1 -0
  81. package/package.json +3 -3
  82. package/src/AssistantIf.tsx +3 -11
  83. package/src/Derived.ts +46 -0
  84. package/src/attachDefaultPeers.ts +78 -0
  85. package/src/index.ts +19 -22
  86. package/src/tapClientList.ts +105 -0
  87. package/src/tapClientLookup.ts +56 -0
  88. package/src/tapClientResource.ts +152 -0
  89. package/src/types/client.ts +186 -0
  90. package/src/types/events.ts +77 -0
  91. package/src/useAssistantClient.tsx +252 -234
  92. package/src/useAssistantEvent.ts +6 -9
  93. package/src/useAssistantState.tsx +10 -48
  94. package/src/utils/BaseProxyHandler.ts +50 -0
  95. package/src/utils/NotificationManager.ts +110 -0
  96. package/src/utils/StoreResource.ts +36 -0
  97. package/src/utils/proxied-assistant-state.tsx +53 -0
  98. package/src/utils/react-assistant-context.tsx +107 -0
  99. package/src/utils/splitClients.ts +85 -0
  100. package/src/utils/tap-assistant-context.ts +59 -0
  101. package/src/utils/tap-client-stack-context.ts +51 -0
  102. package/dist/AssistantContext.d.ts.map +0 -1
  103. package/dist/AssistantContext.js +0 -45
  104. package/dist/AssistantContext.js.map +0 -1
  105. package/dist/DerivedScope.d.ts +0 -20
  106. package/dist/DerivedScope.d.ts.map +0 -1
  107. package/dist/DerivedScope.js +0 -11
  108. package/dist/DerivedScope.js.map +0 -1
  109. package/dist/EventContext.d.ts +0 -61
  110. package/dist/EventContext.d.ts.map +0 -1
  111. package/dist/EventContext.js +0 -62
  112. package/dist/EventContext.js.map +0 -1
  113. package/dist/ScopeRegistry.d.ts +0 -41
  114. package/dist/ScopeRegistry.d.ts.map +0 -1
  115. package/dist/ScopeRegistry.js +0 -17
  116. package/dist/ScopeRegistry.js.map +0 -1
  117. package/dist/StoreContext.d.ts +0 -9
  118. package/dist/StoreContext.d.ts.map +0 -1
  119. package/dist/StoreContext.js +0 -20
  120. package/dist/StoreContext.js.map +0 -1
  121. package/dist/asStore.d.ts +0 -20
  122. package/dist/asStore.d.ts.map +0 -1
  123. package/dist/asStore.js +0 -23
  124. package/dist/asStore.js.map +0 -1
  125. package/dist/tapApi.d.ts +0 -36
  126. package/dist/tapApi.d.ts.map +0 -1
  127. package/dist/tapApi.js +0 -52
  128. package/dist/tapApi.js.map +0 -1
  129. package/dist/tapLookupResources.d.ts +0 -44
  130. package/dist/tapLookupResources.d.ts.map +0 -1
  131. package/dist/tapLookupResources.js +0 -21
  132. package/dist/tapLookupResources.js.map +0 -1
  133. package/dist/tapStoreList.d.ts +0 -76
  134. package/dist/tapStoreList.d.ts.map +0 -1
  135. package/dist/tapStoreList.js +0 -46
  136. package/dist/tapStoreList.js.map +0 -1
  137. package/dist/types.d.ts +0 -84
  138. package/dist/types.d.ts.map +0 -1
  139. package/dist/types.js +0 -1
  140. package/dist/utils/splitScopes.d.ts +0 -24
  141. package/dist/utils/splitScopes.d.ts.map +0 -1
  142. package/dist/utils/splitScopes.js +0 -18
  143. package/dist/utils/splitScopes.js.map +0 -1
  144. package/src/AssistantContext.tsx +0 -64
  145. package/src/DerivedScope.ts +0 -23
  146. package/src/EventContext.ts +0 -187
  147. package/src/ScopeRegistry.ts +0 -58
  148. package/src/StoreContext.ts +0 -28
  149. package/src/asStore.ts +0 -40
  150. package/src/tapApi.ts +0 -91
  151. package/src/tapLookupResources.ts +0 -62
  152. package/src/tapStoreList.ts +0 -133
  153. package/src/types.ts +0 -119
  154. package/src/utils/splitScopes.ts +0 -38
  155. /package/dist/{types.js.map → types/client.js.map} +0 -0
@@ -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;
@@ -1,303 +1,321 @@
1
- import { useMemo } from "react";
1
+ "use client";
2
+
2
3
  import { useResource } from "@assistant-ui/tap/react";
3
4
  import {
4
5
  resource,
5
6
  tapMemo,
6
- tapResource,
7
7
  tapResources,
8
8
  tapEffectEvent,
9
9
  tapInlineResource,
10
- ResourceElement,
10
+ tapEffect,
11
+ tapRef,
12
+ tapResource,
11
13
  } from "@assistant-ui/tap";
12
14
  import type {
13
15
  AssistantClient,
14
- AssistantScopes,
15
- ScopesInput,
16
- ScopeField,
17
- ScopeInput,
18
- DerivedScopeProps,
19
- } from "./types";
20
- import { asStore } from "./asStore";
21
- import { useAssistantContextValue } from "./AssistantContext";
22
- import { splitScopes } from "./utils/splitScopes";
16
+ AssistantClientAccessor,
17
+ ClientNames,
18
+ ClientElement,
19
+ ClientMeta,
20
+ } from "./types/client";
21
+ import { Derived, DerivedElement } from "./Derived";
22
+ import { StoreResource } from "./utils/StoreResource";
23
+ import {
24
+ useAssistantContextValue,
25
+ DefaultAssistantClient,
26
+ createRootAssistantClient,
27
+ } from "./utils/react-assistant-context";
28
+ import {
29
+ DerivedClients,
30
+ RootClients,
31
+ splitClients,
32
+ } from "./utils/splitClients";
23
33
  import {
24
- EventManager,
25
34
  normalizeEventSelector,
26
- type AssistantEvent,
35
+ type AssistantEventName,
27
36
  type AssistantEventCallback,
28
37
  type AssistantEventSelector,
29
- } from "./EventContext";
30
- import { withStoreContextProvider } from "./StoreContext";
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";
43
+ import {
44
+ PROXIED_ASSISTANT_STATE_SYMBOL,
45
+ createProxiedAssistantState,
46
+ } from "./utils/proxied-assistant-state";
31
47
 
32
- /**
33
- * Resource that renders a store with the store context provider.
34
- * This ensures the context is re-established on every re-render.
35
- */
36
- const RootScopeStoreResource = resource(
37
- <K extends keyof AssistantScopes>({
48
+ const RootClientResource = resource(
49
+ <K extends ClientNames>({
38
50
  element,
39
- events,
40
- parent,
51
+ emit,
52
+ clientRef,
41
53
  }: {
42
- element: ScopeInput<AssistantScopes[K]>;
43
- events: EventManager;
44
- parent: AssistantClient;
54
+ element: ClientElement<K>;
55
+ emit: NotificationManager["emit"];
56
+ clientRef: { parent: AssistantClient; current: AssistantClient | null };
45
57
  }) => {
46
- return withStoreContextProvider({ events, parent }, () =>
47
- tapInlineResource(element),
58
+ const { methods, state } = withAssistantTapContextProvider(
59
+ { clientRef, emit },
60
+ () => tapClientResource(element),
48
61
  );
62
+ return tapMemo(() => ({ methods }), [state]);
49
63
  },
50
64
  );
51
65
 
52
- /**
53
- * Resource for a single root scope
54
- * Returns a tuple of [scopeName, {scopeFunction, subscribe, flushSync}]
55
- */
56
- const RootScopeResource = resource(
57
- <K extends keyof AssistantScopes>({
58
- scopeName,
66
+ const RootClientAccessorResource = resource(
67
+ <K extends ClientNames>({
59
68
  element,
60
- events,
61
- parent,
69
+ notifications,
70
+ clientRef,
62
71
  }: {
63
- scopeName: K;
64
- element: ScopeInput<AssistantScopes[K]>;
65
- events: EventManager;
66
- parent: AssistantClient;
67
- }) => {
68
- const store = tapResource(
69
- asStore(RootScopeStoreResource({ element, events, parent })),
72
+ element: ClientElement<K>;
73
+ notifications: NotificationManager;
74
+ clientRef: { parent: AssistantClient; current: AssistantClient | null };
75
+ }): AssistantClientAccessor<K> => {
76
+ const store = tapInlineResource(
77
+ StoreResource(
78
+ RootClientResource({ element, emit: notifications.emit, clientRef }),
79
+ ),
70
80
  );
71
81
 
72
- return tapMemo(() => {
73
- const scopeFunction = (() => store.getState().api) as ScopeField<
74
- AssistantScopes[K]
75
- >;
76
- scopeFunction.source = "root";
77
- scopeFunction.query = {};
82
+ tapEffect(() => {
83
+ return store.subscribe(notifications.notifySubscribers);
84
+ }, [store, notifications]);
78
85
 
79
- return [
80
- scopeName,
81
- {
82
- scopeFunction,
83
- subscribe: store.subscribe,
84
- flushSync: store.flushSync,
85
- },
86
- ] as const;
87
- }, [scopeName, store]);
86
+ return tapMemo(() => {
87
+ const clientFunction = () => store.getState().methods;
88
+ clientFunction.source = "root" as const;
89
+ clientFunction.query = {};
90
+ return clientFunction;
91
+ }, [store]);
88
92
  },
89
93
  );
90
94
 
91
- /**
92
- * Resource for all root scopes
93
- * Mounts each root scope and returns an object mapping scope names to their stores
94
- */
95
- const RootScopesResource = resource(
96
- ({ scopes, parent }: { scopes: ScopesInput; parent: AssistantClient }) => {
97
- const events = tapInlineResource(EventManager());
95
+ const NoOpRootClientsAccessorsResource = resource(() => {
96
+ return tapMemo(
97
+ () => ({ clients: {}, subscribe: undefined, on: undefined }),
98
+ [],
99
+ );
100
+ });
98
101
 
99
- const resultEntries = tapResources(
100
- Object.entries(scopes).map(([scopeName, element]) =>
101
- RootScopeResource(
102
- {
103
- scopeName: scopeName as keyof AssistantScopes,
104
- element: element as ScopeInput<
105
- AssistantScopes[keyof AssistantScopes]
106
- >,
107
- events,
108
- parent,
109
- },
110
- { key: scopeName },
111
- ),
112
- ),
102
+ const RootClientsAccessorsResource = resource(
103
+ ({
104
+ clients: inputClients,
105
+ clientRef,
106
+ }: {
107
+ clients: RootClients;
108
+ clientRef: { parent: AssistantClient; current: AssistantClient | null };
109
+ }) => {
110
+ const notifications = tapInlineResource(NotificationManager());
111
+
112
+ tapEffect(
113
+ () => clientRef.parent.subscribe(notifications.notifySubscribers),
114
+ [clientRef, notifications],
113
115
  );
114
116
 
115
- const on = <TEvent extends AssistantEvent>(
116
- selector: AssistantEventSelector<TEvent>,
117
- callback: AssistantEventCallback<TEvent>,
118
- ) => {
119
- const { event } = normalizeEventSelector(selector);
120
- return events.on(event, callback);
121
- };
117
+ const results = tapResources(
118
+ inputClients,
119
+ (element) =>
120
+ RootClientAccessorResource({
121
+ element: element!,
122
+ notifications,
123
+ clientRef,
124
+ }),
125
+ [notifications, clientRef],
126
+ );
122
127
 
123
128
  return tapMemo(() => {
124
- if (resultEntries.length === 0) {
125
- return {
126
- scopes: {},
127
- on,
128
- };
129
- }
130
-
131
129
  return {
132
- scopes: Object.fromEntries(
133
- resultEntries.map(([scopeName, { scopeFunction }]) => [
134
- scopeName,
135
- scopeFunction,
136
- ]),
137
- ) as {
138
- [K in keyof typeof scopes]: ScopeField<AssistantScopes[K]>;
139
- },
140
- subscribe: (callback: () => void) => {
141
- const unsubscribes = resultEntries.map(([, { subscribe }]) => {
142
- return subscribe(() => {
143
- console.log("Callback called for");
144
- callback();
145
- });
130
+ clients: results,
131
+ subscribe: notifications.subscribe,
132
+ on: function <TEvent extends AssistantEventName>(
133
+ this: AssistantClient,
134
+ selector: AssistantEventSelector<TEvent>,
135
+ callback: AssistantEventCallback<TEvent>,
136
+ ) {
137
+ if (!this) {
138
+ throw new Error(
139
+ "const { on } = useAssistantClient() is not supported. Use aui.on() instead.",
140
+ );
141
+ }
142
+
143
+ const { scope, event } = normalizeEventSelector(selector);
144
+
145
+ if (scope !== "*") {
146
+ const source = this[scope as ClientNames].source;
147
+ if (source === null) {
148
+ throw new Error(
149
+ `Scope "${scope}" is not available. Use { scope: "*", event: "${event}" } to listen globally.`,
150
+ );
151
+ }
152
+ }
153
+
154
+ const localUnsub = notifications.on(event, (payload, clientStack) => {
155
+ if (scope === "*") {
156
+ callback(payload);
157
+ return;
158
+ }
159
+
160
+ const scopeClient = this[scope as ClientNames]();
161
+ const index = getClientIndex(scopeClient);
162
+ if (scopeClient === clientStack[index]) {
163
+ callback(payload);
164
+ }
146
165
  });
166
+ if (
167
+ scope !== "*" &&
168
+ clientRef.parent[scope as ClientNames].source === null
169
+ )
170
+ return localUnsub;
171
+
172
+ const parentUnsub = clientRef.parent.on(selector, callback);
173
+
147
174
  return () => {
148
- unsubscribes.forEach((unsubscribe) => unsubscribe());
175
+ localUnsub();
176
+ parentUnsub();
149
177
  };
150
178
  },
151
- flushSync: () => {
152
- resultEntries.forEach(([, { flushSync }]) => {
153
- flushSync();
154
- });
155
- },
156
- on,
157
179
  };
158
- }, [...resultEntries, events]);
180
+ }, [results, notifications, clientRef]);
159
181
  },
160
182
  );
161
183
 
162
- /**
163
- * Hook to mount and access root scopes
164
- */
165
- export const useRootScopes = (
166
- rootScopes: ScopesInput,
167
- parent: AssistantClient,
168
- ) => {
169
- return useResource(RootScopesResource({ scopes: rootScopes, parent }));
184
+ type MetaMemo<K extends ClientNames> = {
185
+ meta?: ClientMeta<K>;
186
+ dep?: unknown;
170
187
  };
171
188
 
172
- /**
173
- * Resource for a single derived scope
174
- * Returns a tuple of [scopeName, scopeFunction] where scopeFunction has source and query
175
- */
176
- const DerivedScopeResource = resource(
177
- <K extends keyof AssistantScopes>({
178
- scopeName,
189
+ const getMeta = <K extends ClientNames>(
190
+ props: Derived.Props<K>,
191
+ clientRef: { parent: AssistantClient; current: AssistantClient | null },
192
+ memo: MetaMemo<K>,
193
+ ): ClientMeta<K> => {
194
+ if ("source" in props && "query" in props) return props;
195
+ if (memo.dep === props) return memo.meta!;
196
+ const meta = props.getMeta(clientRef.current!);
197
+ memo.meta = meta;
198
+ memo.dep = props;
199
+ return meta;
200
+ };
201
+
202
+ const DerivedClientAccessorResource = resource(
203
+ <K extends ClientNames>({
179
204
  element,
180
- parentClient,
205
+ clientRef,
181
206
  }: {
182
- scopeName: K;
183
- element: ResourceElement<
184
- AssistantScopes[K],
185
- DerivedScopeProps<AssistantScopes[K]>
186
- >;
187
- parentClient: AssistantClient;
207
+ element: DerivedElement<K>;
208
+ clientRef: { parent: AssistantClient; current: AssistantClient | null };
188
209
  }) => {
189
- const get = tapEffectEvent(element.props.get);
190
- const source = element.props.source;
191
- const query = element.props.query;
192
- return tapMemo(() => {
193
- const scopeFunction = (() => get(parentClient)) as ScopeField<
194
- AssistantScopes[K]
195
- >;
196
- scopeFunction.source = source;
197
- scopeFunction.query = query;
210
+ const get = tapEffectEvent(() => element.props);
198
211
 
199
- return [scopeName, scopeFunction] as const;
200
- }, [scopeName, get, source, JSON.stringify(query), parentClient]);
212
+ return tapMemo(() => {
213
+ const clientFunction = () => get().get(clientRef.current!);
214
+ const metaMemo = {};
215
+ Object.defineProperties(clientFunction, {
216
+ source: {
217
+ get: () => getMeta(get(), clientRef, metaMemo).source,
218
+ },
219
+ query: {
220
+ get: () => getMeta(get(), clientRef, metaMemo).query,
221
+ },
222
+ });
223
+ return clientFunction;
224
+ }, [clientRef]);
201
225
  },
202
226
  );
203
227
 
204
- /**
205
- * Resource for all derived scopes
206
- * Builds stable scope functions with source and query metadata
207
- */
208
- const DerivedScopesResource = resource(
228
+ const DerivedClientsAccessorsResource = resource(
209
229
  ({
210
- scopes,
211
- parentClient,
230
+ clients,
231
+ clientRef,
212
232
  }: {
213
- scopes: ScopesInput;
214
- parentClient: AssistantClient;
233
+ clients: DerivedClients;
234
+ clientRef: { parent: AssistantClient; current: AssistantClient | null };
215
235
  }) => {
216
- const resultEntries = tapResources(
217
- Object.entries(scopes).map(([scopeName, element]) =>
218
- DerivedScopeResource(
219
- {
220
- scopeName: scopeName as keyof AssistantScopes,
221
- element: element as ScopeInput<
222
- AssistantScopes[keyof AssistantScopes]
223
- >,
224
- parentClient,
225
- },
226
- { key: scopeName },
227
- ),
228
- ),
236
+ return tapResources(
237
+ clients,
238
+ (element) =>
239
+ DerivedClientAccessorResource({
240
+ element: element!,
241
+ clientRef,
242
+ }),
243
+ [clientRef],
229
244
  );
230
-
231
- return tapMemo(() => {
232
- return Object.fromEntries(resultEntries) as {
233
- [K in keyof typeof scopes]: ScopeField<AssistantScopes[K]>;
234
- };
235
- }, [...resultEntries]);
236
245
  },
237
246
  );
238
247
 
239
248
  /**
240
- * Hook to mount and access derived scopes
249
+ * Resource that creates an extended AssistantClient.
241
250
  */
242
- export const useDerivedScopes = (
243
- derivedScopes: ScopesInput,
244
- parentClient: AssistantClient,
245
- ) => {
246
- return useResource(
247
- DerivedScopesResource({ scopes: derivedScopes, parentClient }),
248
- );
249
- };
251
+ export const AssistantClientResource = resource(
252
+ ({
253
+ baseClient,
254
+ clients,
255
+ }: {
256
+ baseClient: AssistantClient;
257
+ clients: useAssistantClient.Props;
258
+ }): AssistantClient => {
259
+ const { rootClients, derivedClients } = splitClients(clients, baseClient);
250
260
 
251
- const useExtendedAssistantClientImpl = (
252
- scopes: ScopesInput,
253
- ): AssistantClient => {
254
- const baseClient = useAssistantContextValue();
255
- const { rootScopes, derivedScopes } = splitScopes(scopes);
261
+ const clientRef = tapRef({
262
+ parent: baseClient,
263
+ current: null as AssistantClient | null,
264
+ }).current;
256
265
 
257
- // Mount the scopes to keep them alive
258
- const rootFields = useRootScopes(rootScopes, baseClient);
259
- const derivedFields = useDerivedScopes(derivedScopes, baseClient);
266
+ const rootFields = tapResource(
267
+ Object.keys(rootClients).length > 0
268
+ ? RootClientsAccessorsResource({ clients: rootClients, clientRef })
269
+ : NoOpRootClientsAccessorsResource(),
270
+ );
260
271
 
261
- return useMemo(() => {
262
- // Merge base client with extended client
263
- // If baseClient is the default proxy, spreading it will be a no-op
264
- return {
265
- ...baseClient,
266
- ...rootFields.scopes,
267
- ...derivedFields,
268
- subscribe: rootFields.subscribe ?? baseClient.subscribe,
269
- flushSync: rootFields.flushSync ?? baseClient.flushSync,
270
- on: rootFields.on ?? baseClient.on,
271
- } as AssistantClient;
272
- }, [baseClient, rootFields, derivedFields]);
273
- };
272
+ const derivedFields = tapInlineResource(
273
+ DerivedClientsAccessorsResource({ clients: derivedClients, clientRef }),
274
+ );
275
+
276
+ const client = tapMemo(() => {
277
+ // Swap DefaultAssistantClient -> createRootAssistantClient at root to change error message
278
+ const proto =
279
+ baseClient === DefaultAssistantClient
280
+ ? createRootAssistantClient()
281
+ : baseClient;
282
+ const client = Object.create(proto) as AssistantClient;
283
+ Object.assign(client, rootFields.clients, derivedFields, {
284
+ subscribe: rootFields.subscribe ?? baseClient.subscribe,
285
+ on: rootFields.on ?? baseClient.on,
286
+ [PROXIED_ASSISTANT_STATE_SYMBOL]: createProxiedAssistantState(client),
287
+ });
288
+ return client;
289
+ }, [baseClient, rootFields, derivedFields]);
290
+
291
+ if (clientRef.current === null) {
292
+ clientRef.current = client;
293
+ }
294
+
295
+ tapEffect(() => {
296
+ clientRef.current = client;
297
+ });
298
+
299
+ return client;
300
+ },
301
+ );
302
+
303
+ export namespace useAssistantClient {
304
+ export type Props = {
305
+ [K in ClientNames]?: ClientElement<K> | DerivedElement<K>;
306
+ };
307
+ }
274
308
 
275
- /**
276
- * Hook to access or extend the AssistantClient
277
- *
278
- * @example Without config - returns the client from context:
279
- * ```typescript
280
- * const client = useAssistantClient();
281
- * const fooState = client.foo.getState();
282
- * ```
283
- *
284
- * @example With config - creates a new client with additional scopes:
285
- * ```typescript
286
- * const client = useAssistantClient({
287
- * message: DerivedScope({
288
- * source: "thread",
289
- * query: { type: "index", index: 0 },
290
- * get: () => messageApi,
291
- * }),
292
- * });
293
- * ```
294
- */
295
309
  export function useAssistantClient(): AssistantClient;
296
- export function useAssistantClient(scopes: ScopesInput): AssistantClient;
297
- export function useAssistantClient(scopes?: ScopesInput): AssistantClient {
298
- if (scopes) {
299
- return useExtendedAssistantClientImpl(scopes);
300
- } else {
301
- return useAssistantContextValue();
310
+ export function useAssistantClient(
311
+ clients: useAssistantClient.Props,
312
+ ): AssistantClient;
313
+ export function useAssistantClient(
314
+ clients?: useAssistantClient.Props,
315
+ ): AssistantClient {
316
+ const baseClient = useAssistantContextValue();
317
+ if (clients) {
318
+ return useResource(AssistantClientResource({ baseClient, clients }));
302
319
  }
320
+ return baseClient;
303
321
  }
@@ -1,22 +1,19 @@
1
1
  import { useEffect, useEffectEvent } from "react";
2
2
  import { useAssistantClient } from "./useAssistantClient";
3
3
  import type {
4
- AssistantEvent,
4
+ AssistantEventName,
5
5
  AssistantEventCallback,
6
6
  AssistantEventSelector,
7
- } from "./EventContext";
8
- import { normalizeEventSelector } from "./EventContext";
7
+ } from "./types/events";
8
+ import { normalizeEventSelector } from "./types/events";
9
9
 
10
- export const useAssistantEvent = <TEvent extends AssistantEvent>(
10
+ export const useAssistantEvent = <TEvent extends AssistantEventName>(
11
11
  selector: AssistantEventSelector<TEvent>,
12
12
  callback: AssistantEventCallback<TEvent>,
13
13
  ) => {
14
- const client = useAssistantClient();
14
+ const aui = useAssistantClient();
15
15
  const callbackRef = useEffectEvent(callback);
16
16
 
17
17
  const { scope, event } = normalizeEventSelector(selector);
18
- useEffect(
19
- () => client.on({ scope, event }, callbackRef),
20
- [client, scope, event],
21
- );
18
+ useEffect(() => aui.on({ scope, event }, callbackRef), [aui, scope, event]);
22
19
  };