@assistant-ui/store 0.0.0 → 0.0.2
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.
- package/dist/AssistantContext.js +1 -0
- package/dist/AssistantContext.js.map +1 -1
- package/dist/AssistantIf.d.ts +12 -0
- package/dist/AssistantIf.d.ts.map +1 -0
- package/dist/AssistantIf.js +16 -0
- package/dist/AssistantIf.js.map +1 -0
- package/dist/DerivedScope.d.ts +8 -6
- package/dist/DerivedScope.d.ts.map +1 -1
- package/dist/DerivedScope.js +2 -2
- package/dist/DerivedScope.js.map +1 -1
- package/dist/EventContext.d.ts +61 -0
- package/dist/EventContext.d.ts.map +1 -0
- package/dist/EventContext.js +62 -0
- package/dist/EventContext.js.map +1 -0
- package/dist/StoreContext.d.ts +9 -0
- package/dist/StoreContext.d.ts.map +1 -0
- package/dist/StoreContext.js +20 -0
- package/dist/StoreContext.js.map +1 -0
- package/dist/index.d.ts +7 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +21 -23
- package/dist/types.d.ts.map +1 -1
- package/dist/useAssistantClient.d.ts +10 -2
- package/dist/useAssistantClient.d.ts.map +1 -1
- package/dist/useAssistantClient.js +82 -48
- package/dist/useAssistantClient.js.map +1 -1
- package/dist/useAssistantEvent.d.ts +3 -0
- package/dist/useAssistantEvent.d.ts.map +1 -0
- package/dist/useAssistantEvent.js +17 -0
- package/dist/useAssistantEvent.js.map +1 -0
- package/package.json +5 -6
- package/src/AssistantContext.tsx +1 -1
- package/src/AssistantIf.tsx +25 -0
- package/src/DerivedScope.ts +9 -7
- package/src/EventContext.ts +187 -0
- package/src/StoreContext.ts +28 -0
- package/src/index.ts +33 -6
- package/src/types.ts +41 -42
- package/src/useAssistantClient.tsx +106 -53
- package/src/useAssistantEvent.ts +22 -0
package/src/types.ts
CHANGED
|
@@ -1,20 +1,30 @@
|
|
|
1
1
|
import type { ResourceElement } from "@assistant-ui/tap";
|
|
2
|
+
import type {
|
|
3
|
+
AssistantEvent,
|
|
4
|
+
AssistantEventCallback,
|
|
5
|
+
AssistantEventSelector,
|
|
6
|
+
} from "./EventContext";
|
|
7
|
+
|
|
8
|
+
type ScopeValueType = Record<string, unknown> & {
|
|
9
|
+
getState: () => Record<string, unknown>;
|
|
10
|
+
};
|
|
11
|
+
type ScopeMetaType = { source: string; query: Record<string, unknown> };
|
|
2
12
|
|
|
3
13
|
/**
|
|
4
14
|
* Definition of a scope in the assistant client (internal type)
|
|
5
15
|
* @template TValue - The API type (must include getState() and any actions)
|
|
6
|
-
* @template
|
|
7
|
-
* @template
|
|
16
|
+
* @template TMeta - Source/query metadata (use ScopeMeta or discriminated union)
|
|
17
|
+
* @template TEvents - Optional events that this scope can emit
|
|
8
18
|
* @internal
|
|
9
19
|
*/
|
|
10
20
|
export type ScopeDefinition<
|
|
11
|
-
TValue =
|
|
12
|
-
|
|
13
|
-
|
|
21
|
+
TValue extends ScopeValueType = ScopeValueType,
|
|
22
|
+
TMeta extends ScopeMetaType = ScopeMetaType,
|
|
23
|
+
TEvents extends Record<string, unknown> = Record<string, unknown>,
|
|
14
24
|
> = {
|
|
15
25
|
value: TValue;
|
|
16
|
-
|
|
17
|
-
|
|
26
|
+
meta: TMeta;
|
|
27
|
+
events: TEvents;
|
|
18
28
|
};
|
|
19
29
|
|
|
20
30
|
/**
|
|
@@ -26,58 +36,43 @@ export type ScopeDefinition<
|
|
|
26
36
|
* interface AssistantScopeRegistry {
|
|
27
37
|
* foo: {
|
|
28
38
|
* value: { getState: () => { bar: string }; updateBar: (bar: string) => void };
|
|
29
|
-
* source: "root";
|
|
30
|
-
*
|
|
39
|
+
* meta: { source: "root"; query: Record<string, never> };
|
|
40
|
+
* events: {
|
|
41
|
+
* "foo.updated": { id: string; newValue: string };
|
|
42
|
+
* "foo.deleted": { id: string };
|
|
43
|
+
* };
|
|
44
|
+
* };
|
|
45
|
+
* // Example with multiple sources (discriminated union):
|
|
46
|
+
* bar: {
|
|
47
|
+
* value: { getState: () => { id: string } };
|
|
48
|
+
* meta:
|
|
49
|
+
* | { source: "fooList"; query: { index: number } }
|
|
50
|
+
* | { source: "barList"; query: { id: string } };
|
|
51
|
+
* events: Record<string, never>;
|
|
31
52
|
* };
|
|
32
53
|
* }
|
|
33
54
|
* }
|
|
34
55
|
* ```
|
|
35
56
|
*/
|
|
36
|
-
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
|
37
57
|
export interface AssistantScopeRegistry {}
|
|
38
58
|
|
|
39
59
|
export type AssistantScopes = keyof AssistantScopeRegistry extends never
|
|
40
60
|
? Record<"ERROR: No scopes were defined", ScopeDefinition>
|
|
41
61
|
: { [K in keyof AssistantScopeRegistry]: AssistantScopeRegistry[K] };
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Helper type to extract the value type from a scope definition
|
|
45
|
-
*/
|
|
46
|
-
export type ScopeValue<T extends ScopeDefinition> = T["value"];
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Helper type to extract the source type from a scope definition
|
|
50
|
-
*/
|
|
51
|
-
export type ScopeSource<T extends ScopeDefinition> = T["source"];
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Helper type to extract the query type from a scope definition
|
|
55
|
-
*/
|
|
56
|
-
export type ScopeQuery<T extends ScopeDefinition> = T["query"];
|
|
57
|
-
|
|
58
62
|
/**
|
|
59
63
|
* Type for a scope field - a function that returns the current API value,
|
|
60
|
-
* with source
|
|
64
|
+
* with source/query metadata attached (derived from meta)
|
|
61
65
|
*/
|
|
62
|
-
export type ScopeField<T extends ScopeDefinition> = (() =>
|
|
63
|
-
(
|
|
64
|
-
| {
|
|
65
|
-
source: ScopeSource<T>;
|
|
66
|
-
query: ScopeQuery<T>;
|
|
67
|
-
}
|
|
68
|
-
| {
|
|
69
|
-
source: null;
|
|
70
|
-
query: null;
|
|
71
|
-
}
|
|
72
|
-
);
|
|
66
|
+
export type ScopeField<T extends ScopeDefinition> = (() => T["value"]) &
|
|
67
|
+
(T["meta"] | { source: null; query: null });
|
|
73
68
|
|
|
74
69
|
/**
|
|
75
70
|
* Props passed to a derived scope resource element
|
|
76
71
|
*/
|
|
77
72
|
export type DerivedScopeProps<T extends ScopeDefinition> = {
|
|
78
|
-
get: (parent: AssistantClient) =>
|
|
79
|
-
source:
|
|
80
|
-
query:
|
|
73
|
+
get: (parent: AssistantClient) => T["value"];
|
|
74
|
+
source: T["meta"]["source"];
|
|
75
|
+
query: T["meta"]["query"];
|
|
81
76
|
};
|
|
82
77
|
|
|
83
78
|
/**
|
|
@@ -85,7 +80,7 @@ export type DerivedScopeProps<T extends ScopeDefinition> = {
|
|
|
85
80
|
* Can optionally include source/query metadata via DerivedScope
|
|
86
81
|
*/
|
|
87
82
|
export type ScopeInput<T extends ScopeDefinition> = ResourceElement<{
|
|
88
|
-
api:
|
|
83
|
+
api: T["value"];
|
|
89
84
|
}>;
|
|
90
85
|
|
|
91
86
|
/**
|
|
@@ -117,4 +112,8 @@ export type AssistantClient = {
|
|
|
117
112
|
} & {
|
|
118
113
|
subscribe(listener: () => void): Unsubscribe;
|
|
119
114
|
flushSync(): void;
|
|
115
|
+
on<TEvent extends AssistantEvent>(
|
|
116
|
+
selector: AssistantEventSelector<TEvent>,
|
|
117
|
+
callback: AssistantEventCallback<TEvent>,
|
|
118
|
+
): Unsubscribe;
|
|
120
119
|
};
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
tapResource,
|
|
7
7
|
tapResources,
|
|
8
8
|
tapEffectEvent,
|
|
9
|
+
tapInlineResource,
|
|
9
10
|
ResourceElement,
|
|
10
11
|
} from "@assistant-ui/tap";
|
|
11
12
|
import type {
|
|
@@ -19,6 +20,34 @@ import type {
|
|
|
19
20
|
import { asStore } from "./asStore";
|
|
20
21
|
import { useAssistantContextValue } from "./AssistantContext";
|
|
21
22
|
import { splitScopes } from "./utils/splitScopes";
|
|
23
|
+
import {
|
|
24
|
+
EventManager,
|
|
25
|
+
normalizeEventSelector,
|
|
26
|
+
type AssistantEvent,
|
|
27
|
+
type AssistantEventCallback,
|
|
28
|
+
type AssistantEventSelector,
|
|
29
|
+
} from "./EventContext";
|
|
30
|
+
import { withStoreContextProvider } from "./StoreContext";
|
|
31
|
+
|
|
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>({
|
|
38
|
+
element,
|
|
39
|
+
events,
|
|
40
|
+
parent,
|
|
41
|
+
}: {
|
|
42
|
+
element: ScopeInput<AssistantScopes[K]>;
|
|
43
|
+
events: EventManager;
|
|
44
|
+
parent: AssistantClient;
|
|
45
|
+
}) => {
|
|
46
|
+
return withStoreContextProvider({ events, parent }, () =>
|
|
47
|
+
tapInlineResource(element),
|
|
48
|
+
);
|
|
49
|
+
},
|
|
50
|
+
);
|
|
22
51
|
|
|
23
52
|
/**
|
|
24
53
|
* Resource for a single root scope
|
|
@@ -28,18 +57,24 @@ const RootScopeResource = resource(
|
|
|
28
57
|
<K extends keyof AssistantScopes>({
|
|
29
58
|
scopeName,
|
|
30
59
|
element,
|
|
60
|
+
events,
|
|
61
|
+
parent,
|
|
31
62
|
}: {
|
|
32
63
|
scopeName: K;
|
|
33
64
|
element: ScopeInput<AssistantScopes[K]>;
|
|
65
|
+
events: EventManager;
|
|
66
|
+
parent: AssistantClient;
|
|
34
67
|
}) => {
|
|
35
|
-
const store = tapResource(
|
|
68
|
+
const store = tapResource(
|
|
69
|
+
asStore(RootScopeStoreResource({ element, events, parent })),
|
|
70
|
+
);
|
|
36
71
|
|
|
37
72
|
return tapMemo(() => {
|
|
38
73
|
const scopeFunction = (() => store.getState().api) as ScopeField<
|
|
39
74
|
AssistantScopes[K]
|
|
40
75
|
>;
|
|
41
76
|
scopeFunction.source = "root";
|
|
42
|
-
scopeFunction.query = {}
|
|
77
|
+
scopeFunction.query = {};
|
|
43
78
|
|
|
44
79
|
return [
|
|
45
80
|
scopeName,
|
|
@@ -57,62 +92,81 @@ const RootScopeResource = resource(
|
|
|
57
92
|
* Resource for all root scopes
|
|
58
93
|
* Mounts each root scope and returns an object mapping scope names to their stores
|
|
59
94
|
*/
|
|
60
|
-
const RootScopesResource = resource(
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
95
|
+
const RootScopesResource = resource(
|
|
96
|
+
({ scopes, parent }: { scopes: ScopesInput; parent: AssistantClient }) => {
|
|
97
|
+
const events = tapInlineResource(EventManager());
|
|
98
|
+
|
|
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
|
+
),
|
|
71
112
|
),
|
|
72
|
-
)
|
|
73
|
-
);
|
|
113
|
+
);
|
|
74
114
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
};
|
|
80
|
-
|
|
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
|
+
};
|
|
81
122
|
|
|
82
|
-
return {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
]),
|
|
88
|
-
) as {
|
|
89
|
-
[K in keyof typeof scopes]: ScopeField<AssistantScopes[K]>;
|
|
90
|
-
},
|
|
91
|
-
subscribe: (callback: () => void) => {
|
|
92
|
-
const unsubscribes = resultEntries.map(([, { subscribe }]) => {
|
|
93
|
-
return subscribe(() => {
|
|
94
|
-
console.log("Callback called for");
|
|
95
|
-
callback();
|
|
96
|
-
});
|
|
97
|
-
});
|
|
98
|
-
return () => {
|
|
99
|
-
unsubscribes.forEach((unsubscribe) => unsubscribe());
|
|
123
|
+
return tapMemo(() => {
|
|
124
|
+
if (resultEntries.length === 0) {
|
|
125
|
+
return {
|
|
126
|
+
scopes: {},
|
|
127
|
+
on,
|
|
100
128
|
};
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
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
|
+
});
|
|
146
|
+
});
|
|
147
|
+
return () => {
|
|
148
|
+
unsubscribes.forEach((unsubscribe) => unsubscribe());
|
|
149
|
+
};
|
|
150
|
+
},
|
|
151
|
+
flushSync: () => {
|
|
152
|
+
resultEntries.forEach(([, { flushSync }]) => {
|
|
153
|
+
flushSync();
|
|
154
|
+
});
|
|
155
|
+
},
|
|
156
|
+
on,
|
|
157
|
+
};
|
|
158
|
+
}, [...resultEntries, events]);
|
|
159
|
+
},
|
|
160
|
+
);
|
|
110
161
|
|
|
111
162
|
/**
|
|
112
163
|
* Hook to mount and access root scopes
|
|
113
164
|
*/
|
|
114
|
-
export const useRootScopes = (
|
|
115
|
-
|
|
165
|
+
export const useRootScopes = (
|
|
166
|
+
rootScopes: ScopesInput,
|
|
167
|
+
parent: AssistantClient,
|
|
168
|
+
) => {
|
|
169
|
+
return useResource(RootScopesResource({ scopes: rootScopes, parent }));
|
|
116
170
|
};
|
|
117
171
|
|
|
118
172
|
/**
|
|
@@ -201,7 +255,7 @@ const useExtendedAssistantClientImpl = (
|
|
|
201
255
|
const { rootScopes, derivedScopes } = splitScopes(scopes);
|
|
202
256
|
|
|
203
257
|
// Mount the scopes to keep them alive
|
|
204
|
-
const rootFields = useRootScopes(rootScopes);
|
|
258
|
+
const rootFields = useRootScopes(rootScopes, baseClient);
|
|
205
259
|
const derivedFields = useDerivedScopes(derivedScopes, baseClient);
|
|
206
260
|
|
|
207
261
|
return useMemo(() => {
|
|
@@ -213,6 +267,7 @@ const useExtendedAssistantClientImpl = (
|
|
|
213
267
|
...derivedFields,
|
|
214
268
|
subscribe: rootFields.subscribe ?? baseClient.subscribe,
|
|
215
269
|
flushSync: rootFields.flushSync ?? baseClient.flushSync,
|
|
270
|
+
on: rootFields.on ?? baseClient.on,
|
|
216
271
|
} as AssistantClient;
|
|
217
272
|
}, [baseClient, rootFields, derivedFields]);
|
|
218
273
|
};
|
|
@@ -241,10 +296,8 @@ export function useAssistantClient(): AssistantClient;
|
|
|
241
296
|
export function useAssistantClient(scopes: ScopesInput): AssistantClient;
|
|
242
297
|
export function useAssistantClient(scopes?: ScopesInput): AssistantClient {
|
|
243
298
|
if (scopes) {
|
|
244
|
-
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
245
299
|
return useExtendedAssistantClientImpl(scopes);
|
|
246
300
|
} else {
|
|
247
|
-
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
248
301
|
return useAssistantContextValue();
|
|
249
302
|
}
|
|
250
303
|
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { useEffect, useEffectEvent } from "react";
|
|
2
|
+
import { useAssistantClient } from "./useAssistantClient";
|
|
3
|
+
import type {
|
|
4
|
+
AssistantEvent,
|
|
5
|
+
AssistantEventCallback,
|
|
6
|
+
AssistantEventSelector,
|
|
7
|
+
} from "./EventContext";
|
|
8
|
+
import { normalizeEventSelector } from "./EventContext";
|
|
9
|
+
|
|
10
|
+
export const useAssistantEvent = <TEvent extends AssistantEvent>(
|
|
11
|
+
selector: AssistantEventSelector<TEvent>,
|
|
12
|
+
callback: AssistantEventCallback<TEvent>,
|
|
13
|
+
) => {
|
|
14
|
+
const client = useAssistantClient();
|
|
15
|
+
const callbackRef = useEffectEvent(callback);
|
|
16
|
+
|
|
17
|
+
const { scope, event } = normalizeEventSelector(selector);
|
|
18
|
+
useEffect(
|
|
19
|
+
() => client.on({ scope, event }, callbackRef),
|
|
20
|
+
[client, scope, event],
|
|
21
|
+
);
|
|
22
|
+
};
|