@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.
Files changed (42) hide show
  1. package/dist/AssistantContext.js +1 -0
  2. package/dist/AssistantContext.js.map +1 -1
  3. package/dist/AssistantIf.d.ts +12 -0
  4. package/dist/AssistantIf.d.ts.map +1 -0
  5. package/dist/AssistantIf.js +16 -0
  6. package/dist/AssistantIf.js.map +1 -0
  7. package/dist/DerivedScope.d.ts +8 -6
  8. package/dist/DerivedScope.d.ts.map +1 -1
  9. package/dist/DerivedScope.js +2 -2
  10. package/dist/DerivedScope.js.map +1 -1
  11. package/dist/EventContext.d.ts +61 -0
  12. package/dist/EventContext.d.ts.map +1 -0
  13. package/dist/EventContext.js +62 -0
  14. package/dist/EventContext.js.map +1 -0
  15. package/dist/StoreContext.d.ts +9 -0
  16. package/dist/StoreContext.d.ts.map +1 -0
  17. package/dist/StoreContext.js +20 -0
  18. package/dist/StoreContext.js.map +1 -0
  19. package/dist/index.d.ts +7 -6
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/index.js +6 -0
  22. package/dist/index.js.map +1 -1
  23. package/dist/types.d.ts +21 -23
  24. package/dist/types.d.ts.map +1 -1
  25. package/dist/useAssistantClient.d.ts +10 -2
  26. package/dist/useAssistantClient.d.ts.map +1 -1
  27. package/dist/useAssistantClient.js +82 -48
  28. package/dist/useAssistantClient.js.map +1 -1
  29. package/dist/useAssistantEvent.d.ts +3 -0
  30. package/dist/useAssistantEvent.d.ts.map +1 -0
  31. package/dist/useAssistantEvent.js +17 -0
  32. package/dist/useAssistantEvent.js.map +1 -0
  33. package/package.json +5 -6
  34. package/src/AssistantContext.tsx +1 -1
  35. package/src/AssistantIf.tsx +25 -0
  36. package/src/DerivedScope.ts +9 -7
  37. package/src/EventContext.ts +187 -0
  38. package/src/StoreContext.ts +28 -0
  39. package/src/index.ts +33 -6
  40. package/src/types.ts +41 -42
  41. package/src/useAssistantClient.tsx +106 -53
  42. 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(asStore(element));
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((scopes) => {
36
- const resultEntries = tapResources(
37
- Object.entries(scopes).map(
38
- ([scopeName, element]) => RootScopeResource(
39
- {
40
- scopeName,
41
- element
42
- },
43
- { key: scopeName }
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
- return tapMemo(() => {
48
- if (resultEntries.length === 0) {
49
- return {
50
- scopes: {}
51
- };
52
- }
53
- return {
54
- scopes: Object.fromEntries(
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
- }, [...resultEntries]);
78
- });
79
- var useRootScopes = (rootScopes) => {
80
- return useResource(RootScopesResource(rootScopes));
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.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.0"
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.5",
31
- "tsx": "^4.20.6",
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
  }
@@ -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";
@@ -1,5 +1,5 @@
1
1
  import { resource } from "@assistant-ui/tap";
2
- import type { ScopeDefinition, ScopeValue, DerivedScopeProps } from "./types";
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 MessageScope = DerivedScope<MessageScopeDefinition>({
11
- * source: "thread",
12
- * query: { type: "index", index: 0 },
13
- * get: () => messageApi,
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>(config: DerivedScopeProps<T>): ScopeValue<T> => {
19
- return config;
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
- export type { AssistantScopes, AssistantClient, AssistantState } from "./types";
9
+
10
+ // resources
5
11
  export { DerivedScope } from "./DerivedScope";
6
- export type { ApiObject } from "./tapApi";
7
- export { tapApi } from "./tapApi";
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
- export type { TapStoreListConfig } from "./tapStoreList";
17
+ export { tapStoreList, type TapStoreListConfig } from "./tapStoreList";
18
+
19
+ // registration
11
20
  export { registerAssistantScope } from "./ScopeRegistry";
12
21
 
13
- export type { AssistantScopeRegistry } from "./types";
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";