@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
|
@@ -6,17 +6,39 @@ import {
|
|
|
6
6
|
tapMemo,
|
|
7
7
|
tapResource,
|
|
8
8
|
tapResources,
|
|
9
|
-
tapEffectEvent
|
|
9
|
+
tapEffectEvent,
|
|
10
|
+
tapInlineResource
|
|
10
11
|
} from "@assistant-ui/tap";
|
|
11
12
|
import { asStore } from "./asStore.js";
|
|
12
13
|
import { useAssistantContextValue } from "./AssistantContext.js";
|
|
13
14
|
import { splitScopes } from "./utils/splitScopes.js";
|
|
15
|
+
import {
|
|
16
|
+
EventManager,
|
|
17
|
+
normalizeEventSelector
|
|
18
|
+
} from "./EventContext.js";
|
|
19
|
+
import { withStoreContextProvider } from "./StoreContext.js";
|
|
20
|
+
var RootScopeStoreResource = resource(
|
|
21
|
+
({
|
|
22
|
+
element,
|
|
23
|
+
events,
|
|
24
|
+
parent
|
|
25
|
+
}) => {
|
|
26
|
+
return withStoreContextProvider(
|
|
27
|
+
{ events, parent },
|
|
28
|
+
() => tapInlineResource(element)
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
);
|
|
14
32
|
var RootScopeResource = resource(
|
|
15
33
|
({
|
|
16
34
|
scopeName,
|
|
17
|
-
element
|
|
35
|
+
element,
|
|
36
|
+
events,
|
|
37
|
+
parent
|
|
18
38
|
}) => {
|
|
19
|
-
const store = tapResource(
|
|
39
|
+
const store = tapResource(
|
|
40
|
+
asStore(RootScopeStoreResource({ element, events, parent }))
|
|
41
|
+
);
|
|
20
42
|
return tapMemo(() => {
|
|
21
43
|
const scopeFunction = (() => store.getState().api);
|
|
22
44
|
scopeFunction.source = "root";
|
|
@@ -32,52 +54,63 @@ var RootScopeResource = resource(
|
|
|
32
54
|
}, [scopeName, store]);
|
|
33
55
|
}
|
|
34
56
|
);
|
|
35
|
-
var RootScopesResource = resource(
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
57
|
+
var RootScopesResource = resource(
|
|
58
|
+
({ scopes, parent }) => {
|
|
59
|
+
const events = tapInlineResource(EventManager());
|
|
60
|
+
const resultEntries = tapResources(
|
|
61
|
+
Object.entries(scopes).map(
|
|
62
|
+
([scopeName, element]) => RootScopeResource(
|
|
63
|
+
{
|
|
64
|
+
scopeName,
|
|
65
|
+
element,
|
|
66
|
+
events,
|
|
67
|
+
parent
|
|
68
|
+
},
|
|
69
|
+
{ key: scopeName }
|
|
70
|
+
)
|
|
44
71
|
)
|
|
45
|
-
)
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
resultEntries.map(([scopeName, { scopeFunction }]) => [
|
|
56
|
-
scopeName,
|
|
57
|
-
scopeFunction
|
|
58
|
-
])
|
|
59
|
-
),
|
|
60
|
-
subscribe: (callback) => {
|
|
61
|
-
const unsubscribes = resultEntries.map(([, { subscribe }]) => {
|
|
62
|
-
return subscribe(() => {
|
|
63
|
-
console.log("Callback called for");
|
|
64
|
-
callback();
|
|
65
|
-
});
|
|
66
|
-
});
|
|
67
|
-
return () => {
|
|
68
|
-
unsubscribes.forEach((unsubscribe) => unsubscribe());
|
|
72
|
+
);
|
|
73
|
+
const on = (selector, callback) => {
|
|
74
|
+
const { event } = normalizeEventSelector(selector);
|
|
75
|
+
return events.on(event, callback);
|
|
76
|
+
};
|
|
77
|
+
return tapMemo(() => {
|
|
78
|
+
if (resultEntries.length === 0) {
|
|
79
|
+
return {
|
|
80
|
+
scopes: {},
|
|
81
|
+
on
|
|
69
82
|
};
|
|
70
|
-
},
|
|
71
|
-
flushSync: () => {
|
|
72
|
-
resultEntries.forEach(([, { flushSync }]) => {
|
|
73
|
-
flushSync();
|
|
74
|
-
});
|
|
75
83
|
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
})
|
|
79
|
-
|
|
80
|
-
|
|
84
|
+
return {
|
|
85
|
+
scopes: Object.fromEntries(
|
|
86
|
+
resultEntries.map(([scopeName, { scopeFunction }]) => [
|
|
87
|
+
scopeName,
|
|
88
|
+
scopeFunction
|
|
89
|
+
])
|
|
90
|
+
),
|
|
91
|
+
subscribe: (callback) => {
|
|
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());
|
|
100
|
+
};
|
|
101
|
+
},
|
|
102
|
+
flushSync: () => {
|
|
103
|
+
resultEntries.forEach(([, { flushSync }]) => {
|
|
104
|
+
flushSync();
|
|
105
|
+
});
|
|
106
|
+
},
|
|
107
|
+
on
|
|
108
|
+
};
|
|
109
|
+
}, [...resultEntries, events]);
|
|
110
|
+
}
|
|
111
|
+
);
|
|
112
|
+
var useRootScopes = (rootScopes, parent) => {
|
|
113
|
+
return useResource(RootScopesResource({ scopes: rootScopes, parent }));
|
|
81
114
|
};
|
|
82
115
|
var DerivedScopeResource = resource(
|
|
83
116
|
({
|
|
@@ -126,7 +159,7 @@ var useDerivedScopes = (derivedScopes, parentClient) => {
|
|
|
126
159
|
var useExtendedAssistantClientImpl = (scopes) => {
|
|
127
160
|
const baseClient = useAssistantContextValue();
|
|
128
161
|
const { rootScopes, derivedScopes } = splitScopes(scopes);
|
|
129
|
-
const rootFields = useRootScopes(rootScopes);
|
|
162
|
+
const rootFields = useRootScopes(rootScopes, baseClient);
|
|
130
163
|
const derivedFields = useDerivedScopes(derivedScopes, baseClient);
|
|
131
164
|
return useMemo(() => {
|
|
132
165
|
return {
|
|
@@ -134,7 +167,8 @@ var useExtendedAssistantClientImpl = (scopes) => {
|
|
|
134
167
|
...rootFields.scopes,
|
|
135
168
|
...derivedFields,
|
|
136
169
|
subscribe: rootFields.subscribe ?? baseClient.subscribe,
|
|
137
|
-
flushSync: rootFields.flushSync ?? baseClient.flushSync
|
|
170
|
+
flushSync: rootFields.flushSync ?? baseClient.flushSync,
|
|
171
|
+
on: rootFields.on ?? baseClient.on
|
|
138
172
|
};
|
|
139
173
|
}, [baseClient, rootFields, derivedFields]);
|
|
140
174
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/useAssistantClient.tsx"],"sourcesContent":["import { useMemo } from \"react\";\nimport { useResource } from \"@assistant-ui/tap/react\";\nimport {\n resource,\n tapMemo,\n tapResource,\n tapResources,\n tapEffectEvent,\n ResourceElement,\n} from \"@assistant-ui/tap\";\nimport type {\n AssistantClient,\n AssistantScopes,\n ScopesInput,\n ScopeField,\n ScopeInput,\n DerivedScopeProps,\n} from \"./types\";\nimport { asStore } from \"./asStore\";\nimport { useAssistantContextValue } from \"./AssistantContext\";\nimport { splitScopes } from \"./utils/splitScopes\";\n\n/**\n * Resource for a single root scope\n * Returns a tuple of [scopeName, {scopeFunction, subscribe, flushSync}]\n */\nconst RootScopeResource = resource(\n <K extends keyof AssistantScopes>({\n scopeName,\n element,\n }: {\n scopeName: K;\n element: ScopeInput<AssistantScopes[K]>;\n }) => {\n const store = tapResource(asStore(element));\n\n return tapMemo(() => {\n const scopeFunction = (() => store.getState().api) as ScopeField<\n AssistantScopes[K]\n >;\n scopeFunction.source = \"root\";\n scopeFunction.query = {} as AssistantScopes[K][\"query\"];\n\n return [\n scopeName,\n {\n scopeFunction,\n subscribe: store.subscribe,\n flushSync: store.flushSync,\n },\n ] as const;\n }, [scopeName, store]);\n },\n);\n\n/**\n * Resource for all root scopes\n * Mounts each root scope and returns an object mapping scope names to their stores\n */\nconst RootScopesResource = resource((scopes: ScopesInput) => {\n const resultEntries = tapResources(\n Object.entries(scopes).map(([scopeName, element]) =>\n RootScopeResource(\n {\n scopeName: scopeName as keyof AssistantScopes,\n element: element as ScopeInput<\n AssistantScopes[keyof AssistantScopes]\n >,\n },\n { key: scopeName },\n ),\n ),\n );\n\n return tapMemo(() => {\n if (resultEntries.length === 0) {\n return {\n scopes: {},\n };\n }\n\n return {\n scopes: Object.fromEntries(\n resultEntries.map(([scopeName, { scopeFunction }]) => [\n scopeName,\n scopeFunction,\n ]),\n ) as {\n [K in keyof typeof scopes]: ScopeField<AssistantScopes[K]>;\n },\n subscribe: (callback: () => void) => {\n const unsubscribes = resultEntries.map(([, { subscribe }]) => {\n return subscribe(() => {\n console.log(\"Callback called for\");\n callback();\n });\n });\n return () => {\n unsubscribes.forEach((unsubscribe) => unsubscribe());\n };\n },\n flushSync: () => {\n resultEntries.forEach(([, { flushSync }]) => {\n flushSync();\n });\n },\n };\n }, [...resultEntries]);\n});\n\n/**\n * Hook to mount and access root scopes\n */\nexport const useRootScopes = (rootScopes: ScopesInput) => {\n return useResource(RootScopesResource(rootScopes));\n};\n\n/**\n * Resource for a single derived scope\n * Returns a tuple of [scopeName, scopeFunction] where scopeFunction has source and query\n */\nconst DerivedScopeResource = resource(\n <K extends keyof AssistantScopes>({\n scopeName,\n element,\n parentClient,\n }: {\n scopeName: K;\n element: ResourceElement<\n AssistantScopes[K],\n DerivedScopeProps<AssistantScopes[K]>\n >;\n parentClient: AssistantClient;\n }) => {\n const get = tapEffectEvent(element.props.get);\n const source = element.props.source;\n const query = element.props.query;\n return tapMemo(() => {\n const scopeFunction = (() => get(parentClient)) as ScopeField<\n AssistantScopes[K]\n >;\n scopeFunction.source = source;\n scopeFunction.query = query;\n\n return [scopeName, scopeFunction] as const;\n }, [scopeName, get, source, JSON.stringify(query), parentClient]);\n },\n);\n\n/**\n * Resource for all derived scopes\n * Builds stable scope functions with source and query metadata\n */\nconst DerivedScopesResource = resource(\n ({\n scopes,\n parentClient,\n }: {\n scopes: ScopesInput;\n parentClient: AssistantClient;\n }) => {\n const resultEntries = tapResources(\n Object.entries(scopes).map(([scopeName, element]) =>\n DerivedScopeResource(\n {\n scopeName: scopeName as keyof AssistantScopes,\n element: element as ScopeInput<\n AssistantScopes[keyof AssistantScopes]\n >,\n parentClient,\n },\n { key: scopeName },\n ),\n ),\n );\n\n return tapMemo(() => {\n return Object.fromEntries(resultEntries) as {\n [K in keyof typeof scopes]: ScopeField<AssistantScopes[K]>;\n };\n }, [...resultEntries]);\n },\n);\n\n/**\n * Hook to mount and access derived scopes\n */\nexport const useDerivedScopes = (\n derivedScopes: ScopesInput,\n parentClient: AssistantClient,\n) => {\n return useResource(\n DerivedScopesResource({ scopes: derivedScopes, parentClient }),\n );\n};\n\nconst useExtendedAssistantClientImpl = (\n scopes: ScopesInput,\n): AssistantClient => {\n const baseClient = useAssistantContextValue();\n const { rootScopes, derivedScopes } = splitScopes(scopes);\n\n // Mount the scopes to keep them alive\n const rootFields = useRootScopes(rootScopes);\n const derivedFields = useDerivedScopes(derivedScopes, baseClient);\n\n return useMemo(() => {\n // Merge base client with extended client\n // If baseClient is the default proxy, spreading it will be a no-op\n return {\n ...baseClient,\n ...rootFields.scopes,\n ...derivedFields,\n subscribe: rootFields.subscribe ?? baseClient.subscribe,\n flushSync: rootFields.flushSync ?? baseClient.flushSync,\n } as AssistantClient;\n }, [baseClient, rootFields, derivedFields]);\n};\n\n/**\n * Hook to access or extend the AssistantClient\n *\n * @example Without config - returns the client from context:\n * ```typescript\n * const client = useAssistantClient();\n * const fooState = client.foo.getState();\n * ```\n *\n * @example With config - creates a new client with additional scopes:\n * ```typescript\n * const client = useAssistantClient({\n * message: DerivedScope({\n * source: \"thread\",\n * query: { type: \"index\", index: 0 },\n * get: () => messageApi,\n * }),\n * });\n * ```\n */\nexport function useAssistantClient(): AssistantClient;\nexport function useAssistantClient(scopes: ScopesInput): AssistantClient;\nexport function useAssistantClient(scopes?: ScopesInput): AssistantClient {\n if (scopes) {\n // eslint-disable-next-line react-hooks/rules-of-hooks\n return useExtendedAssistantClientImpl(scopes);\n } else {\n // eslint-disable-next-line react-hooks/rules-of-hooks\n return useAssistantContextValue();\n }\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/useAssistantClient.tsx"],"sourcesContent":["import { useMemo } from \"react\";\nimport { useResource } from \"@assistant-ui/tap/react\";\nimport {\n resource,\n tapMemo,\n tapResource,\n tapResources,\n tapEffectEvent,\n tapInlineResource,\n ResourceElement,\n} from \"@assistant-ui/tap\";\nimport type {\n AssistantClient,\n AssistantScopes,\n ScopesInput,\n ScopeField,\n ScopeInput,\n DerivedScopeProps,\n} from \"./types\";\nimport { asStore } from \"./asStore\";\nimport { useAssistantContextValue } from \"./AssistantContext\";\nimport { splitScopes } from \"./utils/splitScopes\";\nimport {\n EventManager,\n normalizeEventSelector,\n type AssistantEvent,\n type AssistantEventCallback,\n type AssistantEventSelector,\n} from \"./EventContext\";\nimport { withStoreContextProvider } from \"./StoreContext\";\n\n/**\n * Resource that renders a store with the store context provider.\n * This ensures the context is re-established on every re-render.\n */\nconst RootScopeStoreResource = resource(\n <K extends keyof AssistantScopes>({\n element,\n events,\n parent,\n }: {\n element: ScopeInput<AssistantScopes[K]>;\n events: EventManager;\n parent: AssistantClient;\n }) => {\n return withStoreContextProvider({ events, parent }, () =>\n tapInlineResource(element),\n );\n },\n);\n\n/**\n * Resource for a single root scope\n * Returns a tuple of [scopeName, {scopeFunction, subscribe, flushSync}]\n */\nconst RootScopeResource = resource(\n <K extends keyof AssistantScopes>({\n scopeName,\n element,\n events,\n parent,\n }: {\n scopeName: K;\n element: ScopeInput<AssistantScopes[K]>;\n events: EventManager;\n parent: AssistantClient;\n }) => {\n const store = tapResource(\n asStore(RootScopeStoreResource({ element, events, parent })),\n );\n\n return tapMemo(() => {\n const scopeFunction = (() => store.getState().api) as ScopeField<\n AssistantScopes[K]\n >;\n scopeFunction.source = \"root\";\n scopeFunction.query = {};\n\n return [\n scopeName,\n {\n scopeFunction,\n subscribe: store.subscribe,\n flushSync: store.flushSync,\n },\n ] as const;\n }, [scopeName, store]);\n },\n);\n\n/**\n * Resource for all root scopes\n * Mounts each root scope and returns an object mapping scope names to their stores\n */\nconst RootScopesResource = resource(\n ({ scopes, parent }: { scopes: ScopesInput; parent: AssistantClient }) => {\n const events = tapInlineResource(EventManager());\n\n const resultEntries = tapResources(\n Object.entries(scopes).map(([scopeName, element]) =>\n RootScopeResource(\n {\n scopeName: scopeName as keyof AssistantScopes,\n element: element as ScopeInput<\n AssistantScopes[keyof AssistantScopes]\n >,\n events,\n parent,\n },\n { key: scopeName },\n ),\n ),\n );\n\n const on = <TEvent extends AssistantEvent>(\n selector: AssistantEventSelector<TEvent>,\n callback: AssistantEventCallback<TEvent>,\n ) => {\n const { event } = normalizeEventSelector(selector);\n return events.on(event, callback);\n };\n\n return tapMemo(() => {\n if (resultEntries.length === 0) {\n return {\n scopes: {},\n on,\n };\n }\n\n return {\n scopes: Object.fromEntries(\n resultEntries.map(([scopeName, { scopeFunction }]) => [\n scopeName,\n scopeFunction,\n ]),\n ) as {\n [K in keyof typeof scopes]: ScopeField<AssistantScopes[K]>;\n },\n subscribe: (callback: () => void) => {\n const unsubscribes = resultEntries.map(([, { subscribe }]) => {\n return subscribe(() => {\n console.log(\"Callback called for\");\n callback();\n });\n });\n return () => {\n unsubscribes.forEach((unsubscribe) => unsubscribe());\n };\n },\n flushSync: () => {\n resultEntries.forEach(([, { flushSync }]) => {\n flushSync();\n });\n },\n on,\n };\n }, [...resultEntries, events]);\n },\n);\n\n/**\n * Hook to mount and access root scopes\n */\nexport const useRootScopes = (\n rootScopes: ScopesInput,\n parent: AssistantClient,\n) => {\n return useResource(RootScopesResource({ scopes: rootScopes, parent }));\n};\n\n/**\n * Resource for a single derived scope\n * Returns a tuple of [scopeName, scopeFunction] where scopeFunction has source and query\n */\nconst DerivedScopeResource = resource(\n <K extends keyof AssistantScopes>({\n scopeName,\n element,\n parentClient,\n }: {\n scopeName: K;\n element: ResourceElement<\n AssistantScopes[K],\n DerivedScopeProps<AssistantScopes[K]>\n >;\n parentClient: AssistantClient;\n }) => {\n const get = tapEffectEvent(element.props.get);\n const source = element.props.source;\n const query = element.props.query;\n return tapMemo(() => {\n const scopeFunction = (() => get(parentClient)) as ScopeField<\n AssistantScopes[K]\n >;\n scopeFunction.source = source;\n scopeFunction.query = query;\n\n return [scopeName, scopeFunction] as const;\n }, [scopeName, get, source, JSON.stringify(query), parentClient]);\n },\n);\n\n/**\n * Resource for all derived scopes\n * Builds stable scope functions with source and query metadata\n */\nconst DerivedScopesResource = resource(\n ({\n scopes,\n parentClient,\n }: {\n scopes: ScopesInput;\n parentClient: AssistantClient;\n }) => {\n const resultEntries = tapResources(\n Object.entries(scopes).map(([scopeName, element]) =>\n DerivedScopeResource(\n {\n scopeName: scopeName as keyof AssistantScopes,\n element: element as ScopeInput<\n AssistantScopes[keyof AssistantScopes]\n >,\n parentClient,\n },\n { key: scopeName },\n ),\n ),\n );\n\n return tapMemo(() => {\n return Object.fromEntries(resultEntries) as {\n [K in keyof typeof scopes]: ScopeField<AssistantScopes[K]>;\n };\n }, [...resultEntries]);\n },\n);\n\n/**\n * Hook to mount and access derived scopes\n */\nexport const useDerivedScopes = (\n derivedScopes: ScopesInput,\n parentClient: AssistantClient,\n) => {\n return useResource(\n DerivedScopesResource({ scopes: derivedScopes, parentClient }),\n );\n};\n\nconst useExtendedAssistantClientImpl = (\n scopes: ScopesInput,\n): AssistantClient => {\n const baseClient = useAssistantContextValue();\n const { rootScopes, derivedScopes } = splitScopes(scopes);\n\n // Mount the scopes to keep them alive\n const rootFields = useRootScopes(rootScopes, baseClient);\n const derivedFields = useDerivedScopes(derivedScopes, baseClient);\n\n return useMemo(() => {\n // Merge base client with extended client\n // If baseClient is the default proxy, spreading it will be a no-op\n return {\n ...baseClient,\n ...rootFields.scopes,\n ...derivedFields,\n subscribe: rootFields.subscribe ?? baseClient.subscribe,\n flushSync: rootFields.flushSync ?? baseClient.flushSync,\n on: rootFields.on ?? baseClient.on,\n } as AssistantClient;\n }, [baseClient, rootFields, derivedFields]);\n};\n\n/**\n * Hook to access or extend the AssistantClient\n *\n * @example Without config - returns the client from context:\n * ```typescript\n * const client = useAssistantClient();\n * const fooState = client.foo.getState();\n * ```\n *\n * @example With config - creates a new client with additional scopes:\n * ```typescript\n * const client = useAssistantClient({\n * message: DerivedScope({\n * source: \"thread\",\n * query: { type: \"index\", index: 0 },\n * get: () => messageApi,\n * }),\n * });\n * ```\n */\nexport function useAssistantClient(): AssistantClient;\nexport function useAssistantClient(scopes: ScopesInput): AssistantClient;\nexport function useAssistantClient(scopes?: ScopesInput): AssistantClient {\n if (scopes) {\n return useExtendedAssistantClientImpl(scopes);\n } else {\n return useAssistantContextValue();\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { AssistantEvent, AssistantEventCallback, AssistantEventSelector } from "./EventContext";
|
|
2
|
+
export declare const useAssistantEvent: <TEvent extends AssistantEvent>(selector: AssistantEventSelector<TEvent>, callback: AssistantEventCallback<TEvent>) => void;
|
|
3
|
+
//# sourceMappingURL=useAssistantEvent.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useAssistantEvent.d.ts","sourceRoot":"","sources":["../src/useAssistantEvent.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,cAAc,EACd,sBAAsB,EACtB,sBAAsB,EACvB,MAAM,gBAAgB,CAAC;AAGxB,eAAO,MAAM,iBAAiB,GAAI,MAAM,SAAS,cAAc,EAC7D,UAAU,sBAAsB,CAAC,MAAM,CAAC,EACxC,UAAU,sBAAsB,CAAC,MAAM,CAAC,SAUzC,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// src/useAssistantEvent.ts
|
|
2
|
+
import { useEffect, useEffectEvent } from "react";
|
|
3
|
+
import { useAssistantClient } from "./useAssistantClient.js";
|
|
4
|
+
import { normalizeEventSelector } from "./EventContext.js";
|
|
5
|
+
var useAssistantEvent = (selector, callback) => {
|
|
6
|
+
const client = useAssistantClient();
|
|
7
|
+
const callbackRef = useEffectEvent(callback);
|
|
8
|
+
const { scope, event } = normalizeEventSelector(selector);
|
|
9
|
+
useEffect(
|
|
10
|
+
() => client.on({ scope, event }, callbackRef),
|
|
11
|
+
[client, scope, event]
|
|
12
|
+
);
|
|
13
|
+
};
|
|
14
|
+
export {
|
|
15
|
+
useAssistantEvent
|
|
16
|
+
};
|
|
17
|
+
//# sourceMappingURL=useAssistantEvent.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/useAssistantEvent.ts"],"sourcesContent":["import { useEffect, useEffectEvent } from \"react\";\nimport { useAssistantClient } from \"./useAssistantClient\";\nimport type {\n AssistantEvent,\n AssistantEventCallback,\n AssistantEventSelector,\n} from \"./EventContext\";\nimport { normalizeEventSelector } from \"./EventContext\";\n\nexport const useAssistantEvent = <TEvent extends AssistantEvent>(\n selector: AssistantEventSelector<TEvent>,\n callback: AssistantEventCallback<TEvent>,\n) => {\n const client = useAssistantClient();\n const callbackRef = useEffectEvent(callback);\n\n const { scope, event } = normalizeEventSelector(selector);\n useEffect(\n () => client.on({ scope, event }, callbackRef),\n [client, scope, event],\n );\n};\n"],"mappings":";AAAA,SAAS,WAAW,sBAAsB;AAC1C,SAAS,0BAA0B;AAMnC,SAAS,8BAA8B;AAEhC,IAAM,oBAAoB,CAC/B,UACA,aACG;AACH,QAAM,SAAS,mBAAmB;AAClC,QAAM,cAAc,eAAe,QAAQ;AAE3C,QAAM,EAAE,OAAO,MAAM,IAAI,uBAAuB,QAAQ;AACxD;AAAA,IACE,MAAM,OAAO,GAAG,EAAE,OAAO,MAAM,GAAG,WAAW;AAAA,IAC7C,CAAC,QAAQ,OAAO,KAAK;AAAA,EACvB;AACF;","names":[]}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@assistant-ui/store",
|
|
3
3
|
"description": "Tap-based state management for @assistant-ui",
|
|
4
|
-
"version": "0.0.
|
|
4
|
+
"version": "0.0.2",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"exports": {
|
|
@@ -20,15 +20,15 @@
|
|
|
20
20
|
],
|
|
21
21
|
"sideEffects": false,
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"@assistant-ui/tap": "0.3.
|
|
23
|
+
"@assistant-ui/tap": "0.3.2"
|
|
24
24
|
},
|
|
25
25
|
"peerDependencies": {
|
|
26
26
|
"react": "^18.0.0 || ^19.0.0"
|
|
27
27
|
},
|
|
28
28
|
"devDependencies": {
|
|
29
29
|
"@types/node": "^24.10.1",
|
|
30
|
-
"@types/react": "19.2.
|
|
31
|
-
"tsx": "^4.
|
|
30
|
+
"@types/react": "19.2.7",
|
|
31
|
+
"tsx": "^4.21.0",
|
|
32
32
|
"@assistant-ui/x-buildutils": "0.0.1"
|
|
33
33
|
},
|
|
34
34
|
"publishConfig": {
|
|
@@ -44,7 +44,6 @@
|
|
|
44
44
|
"url": "https://github.com/assistant-ui/assistant-ui/issues"
|
|
45
45
|
},
|
|
46
46
|
"scripts": {
|
|
47
|
-
"build": "tsx scripts/build.mts"
|
|
48
|
-
"lint": "eslint ."
|
|
47
|
+
"build": "tsx scripts/build.mts"
|
|
49
48
|
}
|
|
50
49
|
}
|
package/src/AssistantContext.tsx
CHANGED
|
@@ -23,7 +23,7 @@ export const AssistantContext = createContext<AssistantClient>(
|
|
|
23
23
|
get(_, prop: string) {
|
|
24
24
|
// Allow access to subscribe and flushSync without error
|
|
25
25
|
if (prop === "subscribe") return NO_OP_SUBSCRIBE;
|
|
26
|
-
|
|
26
|
+
if (prop === "on") return NO_OP_SUBSCRIBE;
|
|
27
27
|
if (prop === "flushSync") return NO_OP_FLUSH_SYNC;
|
|
28
28
|
|
|
29
29
|
// If this is a registered scope, return a function that errors when called or accessed
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import type { FC, PropsWithChildren } from "react";
|
|
4
|
+
import { useAssistantState } from "./useAssistantState";
|
|
5
|
+
import type { AssistantState } from "./types";
|
|
6
|
+
|
|
7
|
+
type UseAssistantIfProps = {
|
|
8
|
+
condition: AssistantIf.Condition;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
const useAssistantIf = (props: UseAssistantIfProps) => {
|
|
12
|
+
return useAssistantState(props.condition);
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export namespace AssistantIf {
|
|
16
|
+
export type Props = PropsWithChildren<UseAssistantIfProps>;
|
|
17
|
+
export type Condition = (state: AssistantState) => boolean;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export const AssistantIf: FC<AssistantIf.Props> = ({ children, condition }) => {
|
|
21
|
+
const result = useAssistantIf({ condition });
|
|
22
|
+
return result ? children : null;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
AssistantIf.displayName = "AssistantIf";
|
package/src/DerivedScope.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { resource } from "@assistant-ui/tap";
|
|
2
|
-
import type { ScopeDefinition,
|
|
2
|
+
import type { ScopeDefinition, DerivedScopeProps } from "./types";
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Creates a derived scope field that memoizes based on source and query.
|
|
@@ -7,15 +7,17 @@ import type { ScopeDefinition, ScopeValue, DerivedScopeProps } from "./types";
|
|
|
7
7
|
*
|
|
8
8
|
* @example
|
|
9
9
|
* ```typescript
|
|
10
|
-
* const
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
10
|
+
* const client = useAssistantClient({
|
|
11
|
+
* message: DerivedScope({
|
|
12
|
+
* source: "thread",
|
|
13
|
+
* query: { index: 0 },
|
|
14
|
+
* get: (client) => client.thread().message({ index: 0 }),
|
|
15
|
+
* }),
|
|
14
16
|
* });
|
|
15
17
|
* ```
|
|
16
18
|
*/
|
|
17
19
|
export const DerivedScope = resource(
|
|
18
|
-
<T extends ScopeDefinition>(
|
|
19
|
-
return
|
|
20
|
+
<T extends ScopeDefinition>(_config: DerivedScopeProps<T>): null => {
|
|
21
|
+
return null;
|
|
20
22
|
},
|
|
21
23
|
);
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import { resource, tapMemo } from "@assistant-ui/tap";
|
|
2
|
+
import type { AssistantScopes, Unsubscribe } from "./types";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Module augmentation interface for event scope configuration.
|
|
6
|
+
* Maps event sources to their parent scopes.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```typescript
|
|
10
|
+
* declare module "@assistant-ui/store" {
|
|
11
|
+
* interface AssistantEventScopeConfig {
|
|
12
|
+
* composer: "thread" | "message";
|
|
13
|
+
* thread: never;
|
|
14
|
+
* }
|
|
15
|
+
* }
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
export interface AssistantEventScopeConfig {}
|
|
19
|
+
|
|
20
|
+
type UnionToIntersection<U> = (
|
|
21
|
+
U extends unknown
|
|
22
|
+
? (x: U) => void
|
|
23
|
+
: never
|
|
24
|
+
) extends (x: infer I) => void
|
|
25
|
+
? I
|
|
26
|
+
: never;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Event map derived from scope event definitions
|
|
30
|
+
*/
|
|
31
|
+
export type ScopeEventMap = UnionToIntersection<
|
|
32
|
+
{
|
|
33
|
+
[K in keyof AssistantScopes]: AssistantScopes[K] extends {
|
|
34
|
+
events: infer E;
|
|
35
|
+
}
|
|
36
|
+
? E extends Record<string, unknown>
|
|
37
|
+
? E
|
|
38
|
+
: never
|
|
39
|
+
: never;
|
|
40
|
+
}[keyof AssistantScopes]
|
|
41
|
+
>;
|
|
42
|
+
|
|
43
|
+
type WildcardPayload = {
|
|
44
|
+
[K in keyof ScopeEventMap]: {
|
|
45
|
+
event: K;
|
|
46
|
+
payload: ScopeEventMap[K];
|
|
47
|
+
};
|
|
48
|
+
}[keyof ScopeEventMap];
|
|
49
|
+
|
|
50
|
+
export type AssistantEventMap = ScopeEventMap & {
|
|
51
|
+
// Catch-all
|
|
52
|
+
"*": WildcardPayload;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export type AssistantEvent = keyof AssistantEventMap;
|
|
56
|
+
|
|
57
|
+
export type EventSource<T extends AssistantEvent = AssistantEvent> =
|
|
58
|
+
T extends `${infer Source}.${string}` ? Source : never;
|
|
59
|
+
|
|
60
|
+
export type SourceByScope<TScope extends AssistantEventScope<AssistantEvent>> =
|
|
61
|
+
| (TScope extends "*" ? EventSource : never)
|
|
62
|
+
| (TScope extends keyof AssistantEventScopeConfig ? TScope : never)
|
|
63
|
+
| {
|
|
64
|
+
[K in keyof AssistantEventScopeConfig]: TScope extends AssistantEventScopeConfig[K]
|
|
65
|
+
? K
|
|
66
|
+
: never;
|
|
67
|
+
}[keyof AssistantEventScopeConfig];
|
|
68
|
+
|
|
69
|
+
export type AssistantEventScope<TEvent extends AssistantEvent> =
|
|
70
|
+
| "*"
|
|
71
|
+
| EventSource<TEvent>
|
|
72
|
+
| (EventSource<TEvent> extends keyof AssistantEventScopeConfig
|
|
73
|
+
? AssistantEventScopeConfig[EventSource<TEvent>]
|
|
74
|
+
: never);
|
|
75
|
+
|
|
76
|
+
export type AssistantEventSelector<TEvent extends AssistantEvent> =
|
|
77
|
+
| TEvent
|
|
78
|
+
| {
|
|
79
|
+
scope: AssistantEventScope<TEvent>;
|
|
80
|
+
event: TEvent;
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
export const normalizeEventSelector = <TEvent extends AssistantEvent>(
|
|
84
|
+
selector: AssistantEventSelector<TEvent>,
|
|
85
|
+
) => {
|
|
86
|
+
if (typeof selector === "string") {
|
|
87
|
+
const source = selector.split(".")[0] as AssistantEventScope<TEvent>;
|
|
88
|
+
return {
|
|
89
|
+
scope: source,
|
|
90
|
+
event: selector,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return {
|
|
95
|
+
scope: selector.scope,
|
|
96
|
+
event: selector.event,
|
|
97
|
+
};
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
export const checkEventScope = <
|
|
101
|
+
TEvent extends AssistantEvent,
|
|
102
|
+
TExpectedScope extends AssistantEventScope<AssistantEvent>,
|
|
103
|
+
>(
|
|
104
|
+
expectedScope: TExpectedScope,
|
|
105
|
+
scope: AssistantEventScope<TEvent>,
|
|
106
|
+
_event: TEvent,
|
|
107
|
+
): _event is Extract<TEvent, `${SourceByScope<TExpectedScope>}.${string}`> => {
|
|
108
|
+
return scope === expectedScope;
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
export type AssistantEventCallback<TEvent extends AssistantEvent> = (
|
|
112
|
+
payload: AssistantEventMap[TEvent],
|
|
113
|
+
) => void;
|
|
114
|
+
|
|
115
|
+
export type EventManager = {
|
|
116
|
+
on<TEvent extends AssistantEvent>(
|
|
117
|
+
event: TEvent,
|
|
118
|
+
callback: AssistantEventCallback<TEvent>,
|
|
119
|
+
): Unsubscribe;
|
|
120
|
+
emit<TEvent extends Exclude<AssistantEvent, "*">>(
|
|
121
|
+
event: TEvent,
|
|
122
|
+
payload: AssistantEventMap[TEvent],
|
|
123
|
+
): void;
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
type ListenerMap = Omit<
|
|
127
|
+
Map<AssistantEvent, Set<AssistantEventCallback<AssistantEvent>>>,
|
|
128
|
+
"get" | "set"
|
|
129
|
+
> & {
|
|
130
|
+
get<TEvent extends AssistantEvent>(
|
|
131
|
+
event: TEvent,
|
|
132
|
+
): Set<AssistantEventCallback<TEvent>> | undefined;
|
|
133
|
+
set<TEvent extends AssistantEvent>(
|
|
134
|
+
event: TEvent,
|
|
135
|
+
value: Set<AssistantEventCallback<TEvent>>,
|
|
136
|
+
): void;
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
export const EventManager = resource(() => {
|
|
140
|
+
const events = tapMemo(() => {
|
|
141
|
+
const listeners: ListenerMap = new Map();
|
|
142
|
+
|
|
143
|
+
return {
|
|
144
|
+
on: (event, callback) => {
|
|
145
|
+
if (!listeners.has(event)) {
|
|
146
|
+
listeners.set(event, new Set());
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const eventListeners = listeners.get(event)!;
|
|
150
|
+
eventListeners.add(callback);
|
|
151
|
+
|
|
152
|
+
return () => {
|
|
153
|
+
eventListeners.delete(callback);
|
|
154
|
+
if (eventListeners.size === 0) {
|
|
155
|
+
listeners.delete(event);
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
},
|
|
159
|
+
|
|
160
|
+
emit: (event, payload) => {
|
|
161
|
+
const eventListeners = listeners.get(event);
|
|
162
|
+
const wildcardListeners = listeners.get("*");
|
|
163
|
+
|
|
164
|
+
if (!eventListeners && !wildcardListeners) return;
|
|
165
|
+
|
|
166
|
+
// make sure state updates flush
|
|
167
|
+
queueMicrotask(() => {
|
|
168
|
+
// Emit to specific event listeners
|
|
169
|
+
if (eventListeners) {
|
|
170
|
+
for (const callback of eventListeners) {
|
|
171
|
+
callback(payload);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Emit to wildcard listeners
|
|
176
|
+
if (wildcardListeners) {
|
|
177
|
+
for (const callback of wildcardListeners) {
|
|
178
|
+
callback({ event, payload } as any);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
},
|
|
183
|
+
} satisfies EventManager;
|
|
184
|
+
}, []);
|
|
185
|
+
|
|
186
|
+
return events;
|
|
187
|
+
});
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createContext,
|
|
3
|
+
tapContext,
|
|
4
|
+
withContextProvider,
|
|
5
|
+
} from "@assistant-ui/tap";
|
|
6
|
+
import type { EventManager } from "./EventContext";
|
|
7
|
+
import type { AssistantClient } from "./types";
|
|
8
|
+
|
|
9
|
+
export type StoreContextValue = {
|
|
10
|
+
events: EventManager;
|
|
11
|
+
parent: AssistantClient;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const StoreContext = createContext<StoreContextValue | null>(null);
|
|
15
|
+
|
|
16
|
+
export const withStoreContextProvider = <TResult>(
|
|
17
|
+
value: StoreContextValue,
|
|
18
|
+
fn: () => TResult,
|
|
19
|
+
) => {
|
|
20
|
+
return withContextProvider(StoreContext, value, fn);
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export const tapStoreContext = () => {
|
|
24
|
+
const ctx = tapContext(StoreContext);
|
|
25
|
+
if (!ctx) throw new Error("Store context is not available");
|
|
26
|
+
|
|
27
|
+
return ctx;
|
|
28
|
+
};
|
package/src/index.ts
CHANGED
|
@@ -1,13 +1,40 @@
|
|
|
1
|
+
// hooks
|
|
1
2
|
export { useAssistantClient } from "./useAssistantClient";
|
|
2
3
|
export { useAssistantState } from "./useAssistantState";
|
|
4
|
+
export { useAssistantEvent } from "./useAssistantEvent";
|
|
5
|
+
|
|
6
|
+
// components
|
|
7
|
+
export { AssistantIf } from "./AssistantIf";
|
|
3
8
|
export { AssistantProvider } from "./AssistantContext";
|
|
4
|
-
|
|
9
|
+
|
|
10
|
+
// resources
|
|
5
11
|
export { DerivedScope } from "./DerivedScope";
|
|
6
|
-
|
|
7
|
-
|
|
12
|
+
|
|
13
|
+
// tap hooks
|
|
14
|
+
export { tapApi, type ApiObject } from "./tapApi";
|
|
15
|
+
export { tapStoreContext, type StoreContextValue } from "./StoreContext";
|
|
8
16
|
export { tapLookupResources } from "./tapLookupResources";
|
|
9
|
-
export { tapStoreList } from "./tapStoreList";
|
|
10
|
-
|
|
17
|
+
export { tapStoreList, type TapStoreListConfig } from "./tapStoreList";
|
|
18
|
+
|
|
19
|
+
// registration
|
|
11
20
|
export { registerAssistantScope } from "./ScopeRegistry";
|
|
12
21
|
|
|
13
|
-
|
|
22
|
+
// types
|
|
23
|
+
export type {
|
|
24
|
+
AssistantScopes,
|
|
25
|
+
AssistantScopeRegistry,
|
|
26
|
+
AssistantClient,
|
|
27
|
+
AssistantState,
|
|
28
|
+
} from "./types";
|
|
29
|
+
|
|
30
|
+
export type {
|
|
31
|
+
AssistantEvent,
|
|
32
|
+
AssistantEventScopeConfig,
|
|
33
|
+
AssistantEventMap,
|
|
34
|
+
AssistantEventScope,
|
|
35
|
+
AssistantEventSelector,
|
|
36
|
+
AssistantEventCallback,
|
|
37
|
+
EventSource,
|
|
38
|
+
SourceByScope,
|
|
39
|
+
EventManager,
|
|
40
|
+
} from "./EventContext";
|