@assistant-ui/store 0.0.1 → 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 +10 -0
  3. package/dist/AssistantIf.d.ts.map +1 -0
  4. package/dist/AssistantIf.js +13 -0
  5. package/dist/AssistantIf.js.map +1 -0
  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 +11 -14
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +19 -16
  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 -41
  38. package/dist/useAssistantClient.d.ts.map +1 -1
  39. package/dist/useAssistantClient.js +185 -130
  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 +5 -6
  82. package/src/AssistantIf.tsx +17 -0
  83. package/src/Derived.ts +46 -0
  84. package/src/attachDefaultPeers.ts +78 -0
  85. package/src/index.ts +31 -25
  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 +259 -217
  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 -18
  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 -65
  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 -88
  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 -21
  146. package/src/EventContext.ts +0 -184
  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 -129
  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,279 +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 for a single root scope
34
- * Returns a tuple of [scopeName, {scopeFunction, subscribe, flushSync}]
35
- */
36
- const RootScopeResource = resource(
37
- <K extends keyof AssistantScopes>({
38
- scopeName,
48
+ const RootClientResource = resource(
49
+ <K extends ClientNames>({
39
50
  element,
51
+ emit,
52
+ clientRef,
40
53
  }: {
41
- scopeName: K;
42
- element: ScopeInput<AssistantScopes[K]>;
54
+ element: ClientElement<K>;
55
+ emit: NotificationManager["emit"];
56
+ clientRef: { parent: AssistantClient; current: AssistantClient | null };
43
57
  }) => {
44
- const store = tapResource(asStore(element));
58
+ const { methods, state } = withAssistantTapContextProvider(
59
+ { clientRef, emit },
60
+ () => tapClientResource(element),
61
+ );
62
+ return tapMemo(() => ({ methods }), [state]);
63
+ },
64
+ );
45
65
 
46
- return tapMemo(() => {
47
- const scopeFunction = (() => store.getState().api) as ScopeField<
48
- AssistantScopes[K]
49
- >;
50
- scopeFunction.source = "root";
51
- scopeFunction.query = {} as AssistantScopes[K]["query"];
66
+ const RootClientAccessorResource = resource(
67
+ <K extends ClientNames>({
68
+ element,
69
+ notifications,
70
+ clientRef,
71
+ }: {
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
+ ),
80
+ );
52
81
 
53
- return [
54
- scopeName,
55
- {
56
- scopeFunction,
57
- subscribe: store.subscribe,
58
- flushSync: store.flushSync,
59
- },
60
- ] as const;
61
- }, [scopeName, store]);
82
+ tapEffect(() => {
83
+ return store.subscribe(notifications.notifySubscribers);
84
+ }, [store, notifications]);
85
+
86
+ return tapMemo(() => {
87
+ const clientFunction = () => store.getState().methods;
88
+ clientFunction.source = "root" as const;
89
+ clientFunction.query = {};
90
+ return clientFunction;
91
+ }, [store]);
62
92
  },
63
93
  );
64
94
 
65
- /**
66
- * Resource for all root scopes
67
- * Mounts each root scope and returns an object mapping scope names to their stores
68
- */
69
- const RootScopesResource = resource(
70
- ({ scopes, parent }: { scopes: ScopesInput; parent: AssistantClient }) => {
71
- const events = tapInlineResource(EventManager());
95
+ const NoOpRootClientsAccessorsResource = resource(() => {
96
+ return tapMemo(
97
+ () => ({ clients: {}, subscribe: undefined, on: undefined }),
98
+ [],
99
+ );
100
+ });
72
101
 
73
- const resultEntries = withStoreContextProvider({ events, parent }, () =>
74
- tapResources(
75
- Object.entries(scopes).map(([scopeName, element]) =>
76
- RootScopeResource(
77
- {
78
- scopeName: scopeName as keyof AssistantScopes,
79
- element: element as ScopeInput<
80
- AssistantScopes[keyof AssistantScopes]
81
- >,
82
- },
83
- { key: scopeName },
84
- ),
85
- ),
86
- ),
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],
87
115
  );
88
116
 
89
- const on = <TEvent extends AssistantEvent>(
90
- selector: AssistantEventSelector<TEvent>,
91
- callback: AssistantEventCallback<TEvent>,
92
- ) => {
93
- const { event } = normalizeEventSelector(selector);
94
- return events.on(event, callback);
95
- };
117
+ const results = tapResources(
118
+ inputClients,
119
+ (element) =>
120
+ RootClientAccessorResource({
121
+ element: element!,
122
+ notifications,
123
+ clientRef,
124
+ }),
125
+ [notifications, clientRef],
126
+ );
96
127
 
97
128
  return tapMemo(() => {
98
- if (resultEntries.length === 0) {
99
- return {
100
- scopes: {},
101
- on,
102
- };
103
- }
104
-
105
129
  return {
106
- scopes: Object.fromEntries(
107
- resultEntries.map(([scopeName, { scopeFunction }]) => [
108
- scopeName,
109
- scopeFunction,
110
- ]),
111
- ) as {
112
- [K in keyof typeof scopes]: ScopeField<AssistantScopes[K]>;
113
- },
114
- subscribe: (callback: () => void) => {
115
- const unsubscribes = resultEntries.map(([, { subscribe }]) => {
116
- return subscribe(() => {
117
- console.log("Callback called for");
118
- callback();
119
- });
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
+ }
120
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
+
121
174
  return () => {
122
- unsubscribes.forEach((unsubscribe) => unsubscribe());
175
+ localUnsub();
176
+ parentUnsub();
123
177
  };
124
178
  },
125
- flushSync: () => {
126
- resultEntries.forEach(([, { flushSync }]) => {
127
- flushSync();
128
- });
129
- },
130
- on,
131
179
  };
132
- }, [...resultEntries, events]);
180
+ }, [results, notifications, clientRef]);
133
181
  },
134
182
  );
135
183
 
136
- /**
137
- * Hook to mount and access root scopes
138
- */
139
- export const useRootScopes = (
140
- rootScopes: ScopesInput,
141
- parent: AssistantClient,
142
- ) => {
143
- return useResource(RootScopesResource({ scopes: rootScopes, parent }));
184
+ type MetaMemo<K extends ClientNames> = {
185
+ meta?: ClientMeta<K>;
186
+ dep?: unknown;
144
187
  };
145
188
 
146
- /**
147
- * Resource for a single derived scope
148
- * Returns a tuple of [scopeName, scopeFunction] where scopeFunction has source and query
149
- */
150
- const DerivedScopeResource = resource(
151
- <K extends keyof AssistantScopes>({
152
- 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>({
153
204
  element,
154
- parentClient,
205
+ clientRef,
155
206
  }: {
156
- scopeName: K;
157
- element: ResourceElement<
158
- AssistantScopes[K],
159
- DerivedScopeProps<AssistantScopes[K]>
160
- >;
161
- parentClient: AssistantClient;
207
+ element: DerivedElement<K>;
208
+ clientRef: { parent: AssistantClient; current: AssistantClient | null };
162
209
  }) => {
163
- const get = tapEffectEvent(element.props.get);
164
- const source = element.props.source;
165
- const query = element.props.query;
166
- return tapMemo(() => {
167
- const scopeFunction = (() => get(parentClient)) as ScopeField<
168
- AssistantScopes[K]
169
- >;
170
- scopeFunction.source = source;
171
- scopeFunction.query = query;
210
+ const get = tapEffectEvent(() => element.props);
172
211
 
173
- return [scopeName, scopeFunction] as const;
174
- }, [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]);
175
225
  },
176
226
  );
177
227
 
178
- /**
179
- * Resource for all derived scopes
180
- * Builds stable scope functions with source and query metadata
181
- */
182
- const DerivedScopesResource = resource(
228
+ const DerivedClientsAccessorsResource = resource(
183
229
  ({
184
- scopes,
185
- parentClient,
230
+ clients,
231
+ clientRef,
186
232
  }: {
187
- scopes: ScopesInput;
188
- parentClient: AssistantClient;
233
+ clients: DerivedClients;
234
+ clientRef: { parent: AssistantClient; current: AssistantClient | null };
189
235
  }) => {
190
- const resultEntries = tapResources(
191
- Object.entries(scopes).map(([scopeName, element]) =>
192
- DerivedScopeResource(
193
- {
194
- scopeName: scopeName as keyof AssistantScopes,
195
- element: element as ScopeInput<
196
- AssistantScopes[keyof AssistantScopes]
197
- >,
198
- parentClient,
199
- },
200
- { key: scopeName },
201
- ),
202
- ),
236
+ return tapResources(
237
+ clients,
238
+ (element) =>
239
+ DerivedClientAccessorResource({
240
+ element: element!,
241
+ clientRef,
242
+ }),
243
+ [clientRef],
203
244
  );
204
-
205
- return tapMemo(() => {
206
- return Object.fromEntries(resultEntries) as {
207
- [K in keyof typeof scopes]: ScopeField<AssistantScopes[K]>;
208
- };
209
- }, [...resultEntries]);
210
245
  },
211
246
  );
212
247
 
213
248
  /**
214
- * Hook to mount and access derived scopes
249
+ * Resource that creates an extended AssistantClient.
215
250
  */
216
- export const useDerivedScopes = (
217
- derivedScopes: ScopesInput,
218
- parentClient: AssistantClient,
219
- ) => {
220
- return useResource(
221
- DerivedScopesResource({ scopes: derivedScopes, parentClient }),
222
- );
223
- };
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);
224
260
 
225
- const useExtendedAssistantClientImpl = (
226
- scopes: ScopesInput,
227
- ): AssistantClient => {
228
- const baseClient = useAssistantContextValue();
229
- const { rootScopes, derivedScopes } = splitScopes(scopes);
261
+ const clientRef = tapRef({
262
+ parent: baseClient,
263
+ current: null as AssistantClient | null,
264
+ }).current;
230
265
 
231
- // Mount the scopes to keep them alive
232
- const rootFields = useRootScopes(rootScopes, baseClient);
233
- const derivedFields = useDerivedScopes(derivedScopes, baseClient);
266
+ const rootFields = tapResource(
267
+ Object.keys(rootClients).length > 0
268
+ ? RootClientsAccessorsResource({ clients: rootClients, clientRef })
269
+ : NoOpRootClientsAccessorsResource(),
270
+ );
234
271
 
235
- return useMemo(() => {
236
- // Merge base client with extended client
237
- // If baseClient is the default proxy, spreading it will be a no-op
238
- return {
239
- ...baseClient,
240
- ...rootFields.scopes,
241
- ...derivedFields,
242
- subscribe: rootFields.subscribe ?? baseClient.subscribe,
243
- flushSync: rootFields.flushSync ?? baseClient.flushSync,
244
- on: rootFields.on ?? baseClient.on,
245
- } as AssistantClient;
246
- }, [baseClient, rootFields, derivedFields]);
247
- };
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
+ }
248
308
 
249
- /**
250
- * Hook to access or extend the AssistantClient
251
- *
252
- * @example Without config - returns the client from context:
253
- * ```typescript
254
- * const client = useAssistantClient();
255
- * const fooState = client.foo.getState();
256
- * ```
257
- *
258
- * @example With config - creates a new client with additional scopes:
259
- * ```typescript
260
- * const client = useAssistantClient({
261
- * message: DerivedScope({
262
- * source: "thread",
263
- * query: { type: "index", index: 0 },
264
- * get: () => messageApi,
265
- * }),
266
- * });
267
- * ```
268
- */
269
309
  export function useAssistantClient(): AssistantClient;
270
- export function useAssistantClient(scopes: ScopesInput): AssistantClient;
271
- export function useAssistantClient(scopes?: ScopesInput): AssistantClient {
272
- if (scopes) {
273
- // eslint-disable-next-line react-hooks/rules-of-hooks
274
- return useExtendedAssistantClientImpl(scopes);
275
- } else {
276
- // eslint-disable-next-line react-hooks/rules-of-hooks
277
- 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 }));
278
319
  }
320
+ return baseClient;
279
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
  };