@assistant-ui/store 0.0.2 → 0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (155) hide show
  1. package/README.md +59 -262
  2. package/dist/AssistantIf.d.ts +4 -6
  3. package/dist/AssistantIf.d.ts.map +1 -1
  4. package/dist/AssistantIf.js +1 -4
  5. package/dist/AssistantIf.js.map +1 -1
  6. package/dist/Derived.d.ts +34 -0
  7. package/dist/Derived.d.ts.map +1 -0
  8. package/dist/Derived.js +11 -0
  9. package/dist/Derived.js.map +1 -0
  10. package/dist/attachDefaultPeers.d.ts +56 -0
  11. package/dist/attachDefaultPeers.d.ts.map +1 -0
  12. package/dist/attachDefaultPeers.js +22 -0
  13. package/dist/attachDefaultPeers.js.map +1 -0
  14. package/dist/index.d.ts +10 -9
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +17 -13
  17. package/dist/index.js.map +1 -1
  18. package/dist/tapClientList.d.ts +24 -0
  19. package/dist/tapClientList.d.ts.map +1 -0
  20. package/dist/tapClientList.js +72 -0
  21. package/dist/tapClientList.js.map +1 -0
  22. package/dist/tapClientLookup.d.ts +11 -0
  23. package/dist/tapClientLookup.d.ts.map +1 -0
  24. package/dist/tapClientLookup.js +42 -0
  25. package/dist/tapClientLookup.js.map +1 -0
  26. package/dist/tapClientResource.d.ts +24 -0
  27. package/dist/tapClientResource.d.ts.map +1 -0
  28. package/dist/tapClientResource.js +100 -0
  29. package/dist/tapClientResource.js.map +1 -0
  30. package/dist/types/client.d.ts +117 -0
  31. package/dist/types/client.d.ts.map +1 -0
  32. package/dist/types/client.js +1 -0
  33. package/dist/types/events.d.ts +33 -0
  34. package/dist/types/events.d.ts.map +1 -0
  35. package/dist/types/events.js +12 -0
  36. package/dist/types/events.js.map +1 -0
  37. package/dist/useAssistantClient.d.ts +13 -46
  38. package/dist/useAssistantClient.d.ts.map +1 -1
  39. package/dist/useAssistantClient.js +177 -137
  40. package/dist/useAssistantClient.js.map +1 -1
  41. package/dist/useAssistantEvent.d.ts +2 -2
  42. package/dist/useAssistantEvent.d.ts.map +1 -1
  43. package/dist/useAssistantEvent.js +3 -6
  44. package/dist/useAssistantEvent.js.map +1 -1
  45. package/dist/useAssistantState.d.ts +2 -2
  46. package/dist/useAssistantState.d.ts.map +1 -1
  47. package/dist/useAssistantState.js +7 -36
  48. package/dist/useAssistantState.js.map +1 -1
  49. package/dist/utils/BaseProxyHandler.d.ts +23 -0
  50. package/dist/utils/BaseProxyHandler.d.ts.map +1 -0
  51. package/dist/utils/BaseProxyHandler.js +41 -0
  52. package/dist/utils/BaseProxyHandler.js.map +1 -0
  53. package/dist/utils/NotificationManager.d.ts +11 -0
  54. package/dist/utils/NotificationManager.d.ts.map +1 -0
  55. package/dist/utils/NotificationManager.js +81 -0
  56. package/dist/utils/NotificationManager.js.map +1 -0
  57. package/dist/utils/StoreResource.d.ts +14 -0
  58. package/dist/utils/StoreResource.d.ts.map +1 -0
  59. package/dist/utils/StoreResource.js +23 -0
  60. package/dist/utils/StoreResource.js.map +1 -0
  61. package/dist/utils/proxied-assistant-state.d.ts +8 -0
  62. package/dist/utils/proxied-assistant-state.d.ts.map +1 -0
  63. package/dist/utils/proxied-assistant-state.js +41 -0
  64. package/dist/utils/proxied-assistant-state.js.map +1 -0
  65. package/dist/{AssistantContext.d.ts → utils/react-assistant-context.d.ts} +6 -6
  66. package/dist/utils/react-assistant-context.d.ts.map +1 -0
  67. package/dist/utils/react-assistant-context.js +73 -0
  68. package/dist/utils/react-assistant-context.js.map +1 -0
  69. package/dist/utils/splitClients.d.ts +28 -0
  70. package/dist/utils/splitClients.d.ts.map +1 -0
  71. package/dist/utils/splitClients.js +37 -0
  72. package/dist/utils/splitClients.js.map +1 -0
  73. package/dist/utils/tap-assistant-context.d.ts +19 -0
  74. package/dist/utils/tap-assistant-context.d.ts.map +1 -0
  75. package/dist/utils/tap-assistant-context.js +37 -0
  76. package/dist/utils/tap-assistant-context.js.map +1 -0
  77. package/dist/utils/tap-client-stack-context.d.ts +23 -0
  78. package/dist/utils/tap-client-stack-context.d.ts.map +1 -0
  79. package/dist/utils/tap-client-stack-context.js +30 -0
  80. package/dist/utils/tap-client-stack-context.js.map +1 -0
  81. package/package.json +3 -3
  82. package/src/AssistantIf.tsx +3 -11
  83. package/src/Derived.ts +46 -0
  84. package/src/attachDefaultPeers.ts +78 -0
  85. package/src/index.ts +19 -22
  86. package/src/tapClientList.ts +105 -0
  87. package/src/tapClientLookup.ts +56 -0
  88. package/src/tapClientResource.ts +152 -0
  89. package/src/types/client.ts +186 -0
  90. package/src/types/events.ts +77 -0
  91. package/src/useAssistantClient.tsx +252 -234
  92. package/src/useAssistantEvent.ts +6 -9
  93. package/src/useAssistantState.tsx +10 -48
  94. package/src/utils/BaseProxyHandler.ts +50 -0
  95. package/src/utils/NotificationManager.ts +110 -0
  96. package/src/utils/StoreResource.ts +36 -0
  97. package/src/utils/proxied-assistant-state.tsx +53 -0
  98. package/src/utils/react-assistant-context.tsx +107 -0
  99. package/src/utils/splitClients.ts +85 -0
  100. package/src/utils/tap-assistant-context.ts +59 -0
  101. package/src/utils/tap-client-stack-context.ts +51 -0
  102. package/dist/AssistantContext.d.ts.map +0 -1
  103. package/dist/AssistantContext.js +0 -45
  104. package/dist/AssistantContext.js.map +0 -1
  105. package/dist/DerivedScope.d.ts +0 -20
  106. package/dist/DerivedScope.d.ts.map +0 -1
  107. package/dist/DerivedScope.js +0 -11
  108. package/dist/DerivedScope.js.map +0 -1
  109. package/dist/EventContext.d.ts +0 -61
  110. package/dist/EventContext.d.ts.map +0 -1
  111. package/dist/EventContext.js +0 -62
  112. package/dist/EventContext.js.map +0 -1
  113. package/dist/ScopeRegistry.d.ts +0 -41
  114. package/dist/ScopeRegistry.d.ts.map +0 -1
  115. package/dist/ScopeRegistry.js +0 -17
  116. package/dist/ScopeRegistry.js.map +0 -1
  117. package/dist/StoreContext.d.ts +0 -9
  118. package/dist/StoreContext.d.ts.map +0 -1
  119. package/dist/StoreContext.js +0 -20
  120. package/dist/StoreContext.js.map +0 -1
  121. package/dist/asStore.d.ts +0 -20
  122. package/dist/asStore.d.ts.map +0 -1
  123. package/dist/asStore.js +0 -23
  124. package/dist/asStore.js.map +0 -1
  125. package/dist/tapApi.d.ts +0 -36
  126. package/dist/tapApi.d.ts.map +0 -1
  127. package/dist/tapApi.js +0 -52
  128. package/dist/tapApi.js.map +0 -1
  129. package/dist/tapLookupResources.d.ts +0 -44
  130. package/dist/tapLookupResources.d.ts.map +0 -1
  131. package/dist/tapLookupResources.js +0 -21
  132. package/dist/tapLookupResources.js.map +0 -1
  133. package/dist/tapStoreList.d.ts +0 -76
  134. package/dist/tapStoreList.d.ts.map +0 -1
  135. package/dist/tapStoreList.js +0 -46
  136. package/dist/tapStoreList.js.map +0 -1
  137. package/dist/types.d.ts +0 -84
  138. package/dist/types.d.ts.map +0 -1
  139. package/dist/types.js +0 -1
  140. package/dist/utils/splitScopes.d.ts +0 -24
  141. package/dist/utils/splitScopes.d.ts.map +0 -1
  142. package/dist/utils/splitScopes.js +0 -18
  143. package/dist/utils/splitScopes.js.map +0 -1
  144. package/src/AssistantContext.tsx +0 -64
  145. package/src/DerivedScope.ts +0 -23
  146. package/src/EventContext.ts +0 -187
  147. package/src/ScopeRegistry.ts +0 -58
  148. package/src/StoreContext.ts +0 -28
  149. package/src/asStore.ts +0 -40
  150. package/src/tapApi.ts +0 -91
  151. package/src/tapLookupResources.ts +0 -62
  152. package/src/tapStoreList.ts +0 -133
  153. package/src/types.ts +0 -119
  154. package/src/utils/splitScopes.ts +0 -38
  155. /package/dist/{types.js.map → types/client.js.map} +0 -0
@@ -1,41 +1,7 @@
1
- import { useMemo, useSyncExternalStore, useDebugValue } from "react";
2
- import type { AssistantClient, AssistantState } from "./types";
1
+ import { useSyncExternalStore, useDebugValue } from "react";
2
+ import type { AssistantState } from "./types/client";
3
3
  import { useAssistantClient } from "./useAssistantClient";
4
-
5
- /**
6
- * Proxied state that lazily accesses scope states
7
- */
8
- class ProxiedAssistantState {
9
- #client: AssistantClient;
10
-
11
- constructor(client: AssistantClient) {
12
- this.#client = client;
13
- }
14
-
15
- #getScope<K extends keyof AssistantState>(key: K): AssistantState[K] {
16
- const scopeField = this.#client[key];
17
- if (!scopeField) {
18
- throw new Error(`Scope "${String(key)}" not found in client`);
19
- }
20
-
21
- const api = scopeField();
22
- const state = api.getState();
23
- return state as AssistantState[K];
24
- }
25
-
26
- // Create a Proxy to dynamically handle property access
27
- static create(client: AssistantClient): AssistantState {
28
- const instance = new ProxiedAssistantState(client);
29
- return new Proxy(instance, {
30
- get(target, prop) {
31
- if (typeof prop === "string" && prop in client) {
32
- return target.#getScope(prop as keyof AssistantState);
33
- }
34
- return undefined;
35
- },
36
- }) as unknown as AssistantState;
37
- }
38
- }
4
+ import { getProxiedAssistantState } from "./utils/proxied-assistant-state";
39
5
 
40
6
  /**
41
7
  * Hook to access a slice of the assistant state with automatic subscription
@@ -45,7 +11,7 @@ class ProxiedAssistantState {
45
11
  *
46
12
  * @example
47
13
  * ```typescript
48
- * const client = useAssistantClient({
14
+ * const aui = useAssistantClient({
49
15
  * foo: RootScope({ ... }),
50
16
  * });
51
17
  *
@@ -55,26 +21,22 @@ class ProxiedAssistantState {
55
21
  export const useAssistantState = <T,>(
56
22
  selector: (state: AssistantState) => T,
57
23
  ): T => {
58
- const client = useAssistantClient();
59
-
60
- const proxiedState = useMemo(
61
- () => ProxiedAssistantState.create(client),
62
- [client],
63
- );
24
+ const aui = useAssistantClient();
25
+ const proxiedState = getProxiedAssistantState(aui);
64
26
 
65
27
  const slice = useSyncExternalStore(
66
- client.subscribe,
28
+ aui.subscribe,
67
29
  () => selector(proxiedState),
68
30
  () => selector(proxiedState),
69
31
  );
70
32
 
71
- useDebugValue(slice);
72
-
73
- if (slice instanceof ProxiedAssistantState) {
33
+ if (slice === proxiedState) {
74
34
  throw new Error(
75
35
  "You tried to return the entire AssistantState. This is not supported due to technical limitations.",
76
36
  );
77
37
  }
78
38
 
39
+ useDebugValue(slice);
40
+
79
41
  return slice;
80
42
  };
@@ -0,0 +1,50 @@
1
+ const INTROSPECTION_PROPS = new Set(["$$typeof", "nodeType", "then"]);
2
+
3
+ /**
4
+ * Handles common proxy introspection properties.
5
+ * Returns the appropriate value for toStringTag, toJSON, and props that should return undefined.
6
+ * Returns `false` if the prop should be handled by the subclass.
7
+ */
8
+ export const handleIntrospectionProp = (
9
+ prop: string | symbol,
10
+ name: string,
11
+ ): unknown | false => {
12
+ if (prop === Symbol.toStringTag) return name;
13
+ if (typeof prop === "symbol") return undefined;
14
+ if (prop === "toJSON") return () => name;
15
+ if (INTROSPECTION_PROPS.has(prop)) return undefined;
16
+ return false;
17
+ };
18
+
19
+ export abstract class BaseProxyHandler implements ProxyHandler<object> {
20
+ abstract get(_: unknown, prop: string | symbol): unknown;
21
+ abstract ownKeys(): ArrayLike<string | symbol>;
22
+ abstract has(_: unknown, prop: string | symbol): boolean;
23
+
24
+ getOwnPropertyDescriptor(_: unknown, prop: string | symbol) {
25
+ const value = this.get(_, prop);
26
+ if (value === undefined) return undefined;
27
+ return {
28
+ value,
29
+ writable: false,
30
+ enumerable: true,
31
+ configurable: false,
32
+ };
33
+ }
34
+
35
+ set() {
36
+ return false;
37
+ }
38
+ setPrototypeOf() {
39
+ return false;
40
+ }
41
+ defineProperty() {
42
+ return false;
43
+ }
44
+ deleteProperty() {
45
+ return false;
46
+ }
47
+ preventExtensions(): boolean {
48
+ return false;
49
+ }
50
+ }
@@ -0,0 +1,110 @@
1
+ import { resource, tapMemo } from "@assistant-ui/tap";
2
+ import type { ClientStack } from "./tap-client-stack-context";
3
+ import type {
4
+ AssistantEventName,
5
+ AssistantEventPayload,
6
+ } from "../types/events";
7
+ import { Unsubscribe } from "../types/client";
8
+
9
+ type InternalCallback = (payload: unknown, clientStack: ClientStack) => void;
10
+
11
+ export type NotificationManager = {
12
+ on<TEvent extends AssistantEventName>(
13
+ event: TEvent,
14
+ callback: (
15
+ payload: AssistantEventPayload[TEvent],
16
+ clientStack: ClientStack,
17
+ ) => void,
18
+ ): Unsubscribe;
19
+ emit<TEvent extends Exclude<AssistantEventName, "*">>(
20
+ event: TEvent,
21
+ payload: AssistantEventPayload[TEvent],
22
+ clientStack: ClientStack,
23
+ ): void;
24
+ subscribe(callback: () => void): Unsubscribe;
25
+ notifySubscribers(): void;
26
+ };
27
+
28
+ export const NotificationManager = resource((): NotificationManager => {
29
+ return tapMemo(() => {
30
+ const listeners = new Map<string, Set<InternalCallback>>();
31
+ const wildcardListeners = new Set<InternalCallback>();
32
+ const subscribers = new Set<() => void>();
33
+
34
+ return {
35
+ on(event, callback) {
36
+ const cb = callback as InternalCallback;
37
+ if (event === "*") {
38
+ wildcardListeners.add(cb);
39
+ return () => wildcardListeners.delete(cb);
40
+ }
41
+
42
+ let set = listeners.get(event);
43
+ if (!set) {
44
+ set = new Set();
45
+ listeners.set(event, set);
46
+ }
47
+ set.add(cb);
48
+
49
+ return () => {
50
+ set!.delete(cb);
51
+ if (set!.size === 0) listeners.delete(event);
52
+ };
53
+ },
54
+
55
+ emit(event, payload, clientStack) {
56
+ const eventListeners = listeners.get(event);
57
+ if (!eventListeners && wildcardListeners.size === 0) return;
58
+
59
+ queueMicrotask(() => {
60
+ const errors = [];
61
+ if (eventListeners) {
62
+ for (const cb of eventListeners) {
63
+ try {
64
+ cb(payload, clientStack);
65
+ } catch (e) {
66
+ errors.push(e);
67
+ }
68
+ }
69
+ }
70
+ if (wildcardListeners.size > 0) {
71
+ const wrapped = { event, payload };
72
+ for (const cb of wildcardListeners) {
73
+ try {
74
+ cb(wrapped, clientStack);
75
+ } catch (e) {
76
+ errors.push(e);
77
+ }
78
+ }
79
+ }
80
+
81
+ if (errors.length > 0) {
82
+ if (errors.length === 1) {
83
+ throw errors[0];
84
+ } else {
85
+ throw new AggregateError(
86
+ errors,
87
+ "Errors occurred during event emission",
88
+ );
89
+ }
90
+ }
91
+ });
92
+ },
93
+
94
+ subscribe(callback) {
95
+ subscribers.add(callback);
96
+ return () => subscribers.delete(callback);
97
+ },
98
+
99
+ notifySubscribers() {
100
+ for (const cb of subscribers) {
101
+ try {
102
+ cb();
103
+ } catch (e) {
104
+ console.error("NotificationManager: subscriber callback error", e);
105
+ }
106
+ }
107
+ },
108
+ };
109
+ }, []);
110
+ });
@@ -0,0 +1,36 @@
1
+ import {
2
+ tapEffect,
3
+ ResourceElement,
4
+ resource,
5
+ createResource,
6
+ tapState,
7
+ } from "@assistant-ui/tap";
8
+ import { Unsubscribe } from "../types/client";
9
+
10
+ export interface Store<TState> {
11
+ /**
12
+ * Get the current state of the store.
13
+ */
14
+ getState(): TState;
15
+
16
+ /**
17
+ * Subscribe to the store.
18
+ */
19
+ subscribe(listener: () => void): Unsubscribe;
20
+ }
21
+
22
+ export const StoreResource = resource(
23
+ <TState>(element: ResourceElement<TState>): Store<TState> => {
24
+ const [handle] = tapState(() => createResource(element, { mount: false }));
25
+
26
+ tapEffect(() => {
27
+ return handle.unmount;
28
+ }, [handle]);
29
+
30
+ tapEffect(() => {
31
+ handle.render(element);
32
+ }, [handle, element]);
33
+
34
+ return handle;
35
+ },
36
+ );
@@ -0,0 +1,53 @@
1
+ "use client";
2
+ import { getClientState } from "../tapClientResource";
3
+ import type { AssistantClient, AssistantState } from "../types/client";
4
+ import { BaseProxyHandler, handleIntrospectionProp } from "./BaseProxyHandler";
5
+
6
+ export const PROXIED_ASSISTANT_STATE_SYMBOL = Symbol(
7
+ "assistant-ui.store.proxiedAssistantState",
8
+ );
9
+
10
+ const isIgnoredKey = (key: string | symbol): key is "on" | "subscribe" => {
11
+ return key === "on" || key === "subscribe" || typeof key === "symbol";
12
+ };
13
+
14
+ /**
15
+ * Proxied state that lazily accesses scope states
16
+ */
17
+ export const createProxiedAssistantState = (
18
+ client: AssistantClient,
19
+ ): AssistantState => {
20
+ class ProxiedAssistantStateProxyHandler
21
+ extends BaseProxyHandler
22
+ implements ProxyHandler<AssistantState>
23
+ {
24
+ get(_: unknown, prop: string | symbol) {
25
+ const introspection = handleIntrospectionProp(prop, "AssistantState");
26
+ if (introspection !== false) return introspection;
27
+ const scope = prop as keyof AssistantClient;
28
+ if (isIgnoredKey(scope)) return undefined;
29
+ return getClientState(client[scope]());
30
+ }
31
+
32
+ ownKeys(): ArrayLike<string | symbol> {
33
+ return Object.keys(client).filter((key) => !isIgnoredKey(key));
34
+ }
35
+
36
+ has(_: unknown, prop: string | symbol): boolean {
37
+ return !isIgnoredKey(prop) && prop in client;
38
+ }
39
+ }
40
+
41
+ return new Proxy<AssistantState>(
42
+ {} as AssistantState,
43
+ new ProxiedAssistantStateProxyHandler(),
44
+ );
45
+ };
46
+
47
+ export const getProxiedAssistantState = (
48
+ client: AssistantClient,
49
+ ): AssistantState => {
50
+ return (
51
+ client as unknown as { [PROXIED_ASSISTANT_STATE_SYMBOL]: AssistantState }
52
+ )[PROXIED_ASSISTANT_STATE_SYMBOL];
53
+ };
@@ -0,0 +1,107 @@
1
+ import React, { createContext, useContext } from "react";
2
+ import type { AssistantClient, AssistantClientAccessor } from "../types/client";
3
+ import {
4
+ createProxiedAssistantState,
5
+ PROXIED_ASSISTANT_STATE_SYMBOL,
6
+ } from "./proxied-assistant-state";
7
+ import { BaseProxyHandler, handleIntrospectionProp } from "./BaseProxyHandler";
8
+
9
+ const NO_OP_SUBSCRIBE = () => () => {};
10
+
11
+ const createErrorClientField = (
12
+ message: string,
13
+ ): AssistantClientAccessor<never> => {
14
+ const fn = (() => {
15
+ throw new Error(message);
16
+ }) as AssistantClientAccessor<never>;
17
+ fn.source = null;
18
+ fn.query = null;
19
+ return fn;
20
+ };
21
+
22
+ class DefaultAssistantClientProxyHandler
23
+ extends BaseProxyHandler
24
+ implements ProxyHandler<AssistantClient>
25
+ {
26
+ get(_: unknown, prop: string | symbol) {
27
+ if (prop === "subscribe") return NO_OP_SUBSCRIBE;
28
+ if (prop === "on") return NO_OP_SUBSCRIBE;
29
+ if (prop === PROXIED_ASSISTANT_STATE_SYMBOL)
30
+ return DefaultAssistantClientProxiedAssistantState;
31
+ const introspection = handleIntrospectionProp(
32
+ prop,
33
+ "DefaultAssistantClient",
34
+ );
35
+ if (introspection !== false) return introspection;
36
+ return createErrorClientField(
37
+ `The current scope does not have a "${String(prop)}" property.`,
38
+ );
39
+ }
40
+
41
+ ownKeys(): ArrayLike<string | symbol> {
42
+ return ["subscribe", "on", PROXIED_ASSISTANT_STATE_SYMBOL];
43
+ }
44
+
45
+ has(_: unknown, prop: string | symbol): boolean {
46
+ return (
47
+ prop === "subscribe" ||
48
+ prop === "on" ||
49
+ prop === PROXIED_ASSISTANT_STATE_SYMBOL
50
+ );
51
+ }
52
+ }
53
+ /** Default context value - throws "wrap in AssistantProvider" error */
54
+ export const DefaultAssistantClient: AssistantClient =
55
+ new Proxy<AssistantClient>(
56
+ {} as AssistantClient,
57
+ new DefaultAssistantClientProxyHandler(),
58
+ );
59
+
60
+ const DefaultAssistantClientProxiedAssistantState = createProxiedAssistantState(
61
+ DefaultAssistantClient,
62
+ );
63
+
64
+ /** Root prototype for created clients - throws "scope not defined" error */
65
+ export const createRootAssistantClient = (): AssistantClient =>
66
+ new Proxy<AssistantClient>({} as AssistantClient, {
67
+ get(_: AssistantClient, prop: string | symbol) {
68
+ const introspection = handleIntrospectionProp(prop, "AssistantClient");
69
+ if (introspection !== false) return introspection;
70
+ return createErrorClientField(
71
+ `The current scope does not have a "${String(prop)}" property.`,
72
+ );
73
+ },
74
+ });
75
+
76
+ /**
77
+ * React Context for the AssistantClient
78
+ */
79
+ const AssistantContext = createContext<AssistantClient>(DefaultAssistantClient);
80
+
81
+ export const useAssistantContextValue = (): AssistantClient => {
82
+ return useContext(AssistantContext);
83
+ };
84
+
85
+ /**
86
+ * Provider component for AssistantClient
87
+ *
88
+ * @example
89
+ * ```typescript
90
+ * <AssistantProvider client={client}>
91
+ * <YourApp />
92
+ * </AssistantProvider>
93
+ * ```
94
+ */
95
+ export const AssistantProvider = ({
96
+ client,
97
+ children,
98
+ }: {
99
+ client: AssistantClient;
100
+ children: React.ReactNode;
101
+ }): React.ReactElement => {
102
+ return (
103
+ <AssistantContext.Provider value={client}>
104
+ {children}
105
+ </AssistantContext.Provider>
106
+ );
107
+ };
@@ -0,0 +1,85 @@
1
+ import { Derived, DerivedElement } from "../Derived";
2
+ import type {
3
+ AssistantClient,
4
+ ClientElement,
5
+ ClientNames,
6
+ } from "../types/client";
7
+ import { getDefaultPeers } from "../attachDefaultPeers";
8
+ import type { useAssistantClient } from "../useAssistantClient";
9
+
10
+ export type RootClients = Partial<
11
+ Record<ClientNames, ClientElement<ClientNames>>
12
+ >;
13
+ export type DerivedClients = Partial<
14
+ Record<ClientNames, DerivedElement<ClientNames>>
15
+ >;
16
+
17
+ /**
18
+ * Splits a clients object into root clients and derived clients.
19
+ *
20
+ * @param clients - The clients input object to split
21
+ * @returns An object with { rootClients, derivedClients }
22
+ *
23
+ * @example
24
+ * ```typescript
25
+ * const clients = {
26
+ * foo: RootClient({ ... }),
27
+ * bar: Derived({ ... }),
28
+ * };
29
+ *
30
+ * const { rootClients, derivedClients } = splitClients(clients);
31
+ * // rootClients = { foo: ... }
32
+ * // derivedClients = { bar: ... }
33
+ * ```
34
+ */
35
+ export function splitClients(
36
+ clients: useAssistantClient.Props,
37
+ baseClient: AssistantClient,
38
+ ) {
39
+ const rootClients: RootClients = {};
40
+ const derivedClients: DerivedClients = {};
41
+
42
+ for (const [key, clientElement] of Object.entries(clients) as [
43
+ keyof useAssistantClient.Props,
44
+ NonNullable<useAssistantClient.Props[keyof useAssistantClient.Props]>,
45
+ ][]) {
46
+ if (clientElement.type === Derived) {
47
+ derivedClients[key] = clientElement as DerivedElement<ClientNames>;
48
+ } else {
49
+ rootClients[key] = clientElement as ClientElement<ClientNames>;
50
+ }
51
+ }
52
+
53
+ for (const [clientKey, clientElement] of Object.entries(rootClients) as [
54
+ ClientNames,
55
+ ClientElement<ClientNames>,
56
+ ][]) {
57
+ const defaultPeers = getDefaultPeers(clientElement.type);
58
+ if (!defaultPeers) continue;
59
+
60
+ for (const [key, peerElement] of Object.entries(defaultPeers) as [
61
+ ClientNames,
62
+ ClientElement<ClientNames> | DerivedElement<ClientNames>,
63
+ ][]) {
64
+ if (
65
+ key in rootClients ||
66
+ key in derivedClients ||
67
+ baseClient[key].source !== null
68
+ )
69
+ continue;
70
+
71
+ if (peerElement.type === Derived<ClientNames>) {
72
+ derivedClients[key] = peerElement as DerivedElement<ClientNames>;
73
+ } else {
74
+ rootClients[key] = peerElement as ClientElement<ClientNames>;
75
+ const subDefaultPeers = getDefaultPeers(peerElement.type);
76
+ if (subDefaultPeers)
77
+ throw new Error(
78
+ `Nested default peers are not supported. Client "${clientKey}" has default peers, but its peer "${key}" also has default peers.`,
79
+ );
80
+ }
81
+ }
82
+ }
83
+
84
+ return { rootClients, derivedClients };
85
+ }
@@ -0,0 +1,59 @@
1
+ import {
2
+ createContext,
3
+ tapContext,
4
+ withContextProvider,
5
+ tapEffectEvent,
6
+ } from "@assistant-ui/tap";
7
+ import type {
8
+ AssistantEventName,
9
+ AssistantEventPayload,
10
+ } from "../types/events";
11
+ import type { AssistantClient } from "../types/client";
12
+ import { tapClientStack, type ClientStack } from "./tap-client-stack-context";
13
+
14
+ type EmitFn = <TEvent extends Exclude<AssistantEventName, "*">>(
15
+ event: TEvent,
16
+ payload: AssistantEventPayload[TEvent],
17
+ clientStack: ClientStack,
18
+ ) => void;
19
+
20
+ export type AssistantTapContextValue = {
21
+ clientRef: { parent: AssistantClient; current: AssistantClient | null };
22
+ emit: EmitFn;
23
+ };
24
+
25
+ const AssistantTapContext = createContext<AssistantTapContextValue | null>(
26
+ null,
27
+ );
28
+
29
+ export const withAssistantTapContextProvider = <TResult>(
30
+ value: AssistantTapContextValue,
31
+ fn: () => TResult,
32
+ ) => {
33
+ return withContextProvider(AssistantTapContext, value, fn);
34
+ };
35
+
36
+ const tapAssistantTapContext = () => {
37
+ const ctx = tapContext(AssistantTapContext);
38
+ if (!ctx) throw new Error("AssistantTapContext is not available");
39
+
40
+ return ctx;
41
+ };
42
+
43
+ export const tapAssistantClientRef = () => {
44
+ return tapAssistantTapContext().clientRef;
45
+ };
46
+
47
+ export const tapAssistantEmit = () => {
48
+ const { emit } = tapAssistantTapContext();
49
+ const clientStack = tapClientStack();
50
+
51
+ return tapEffectEvent(
52
+ <TEvent extends Exclude<AssistantEventName, "*">>(
53
+ event: TEvent,
54
+ payload: AssistantEventPayload[TEvent],
55
+ ) => {
56
+ emit(event, payload, clientStack);
57
+ },
58
+ );
59
+ };
@@ -0,0 +1,51 @@
1
+ import {
2
+ createContext,
3
+ tapContext,
4
+ withContextProvider,
5
+ tapMemo,
6
+ } from "@assistant-ui/tap";
7
+ import type { ClientMethods } from "../types/client";
8
+
9
+ /**
10
+ * Symbol used to get the client index from a ClientProxy.
11
+ */
12
+ export const SYMBOL_CLIENT_INDEX = Symbol("assistant-ui.store.clientIndex");
13
+
14
+ /**
15
+ * Get the index of a client (its position in the client stack when created).
16
+ */
17
+ export const getClientIndex = (client: ClientMethods): number => {
18
+ return (client as unknown as { [SYMBOL_CLIENT_INDEX]: number })[
19
+ SYMBOL_CLIENT_INDEX
20
+ ];
21
+ };
22
+
23
+ /**
24
+ * The client stack - an array of clients representing the current hierarchy.
25
+ */
26
+ export type ClientStack = readonly ClientMethods[];
27
+
28
+ const ClientStackContext = createContext<ClientStack>([]);
29
+
30
+ /**
31
+ * Get the current client stack inside a tap resource.
32
+ */
33
+ export const tapClientStack = (): ClientStack => {
34
+ return tapContext(ClientStackContext);
35
+ };
36
+
37
+ /**
38
+ * Execute a callback with a client pushed onto the stack.
39
+ * The stack is duplicated, not mutated.
40
+ */
41
+ export const tapWithClientStack = <T>(
42
+ client: ClientMethods,
43
+ callback: () => T,
44
+ ): T => {
45
+ const currentStack = tapClientStack();
46
+ const newStack = tapMemo(
47
+ () => [...currentStack, client],
48
+ [currentStack, client],
49
+ );
50
+ return withContextProvider(ClientStackContext, newStack, callback);
51
+ };
@@ -1 +0,0 @@
1
- {"version":3,"file":"AssistantContext.d.ts","sourceRoot":"","sources":["../src/AssistantContext.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAoC,MAAM,OAAO,CAAC;AACzD,OAAO,KAAK,EAAE,eAAe,EAA+B,MAAM,SAAS,CAAC;AAgB5E;;GAEG;AACH,eAAO,MAAM,gBAAgB,gCAe5B,CAAC;AAEF,eAAO,MAAM,wBAAwB,QAAO,eAE3C,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,iBAAiB,GAAI,uBAG/B;IACD,MAAM,EAAE,eAAe,CAAC;IACxB,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;CAC3B,KAAG,KAAK,CAAC,YAMT,CAAC"}
@@ -1,45 +0,0 @@
1
- // src/AssistantContext.tsx
2
- import { createContext, useContext } from "react";
3
- import { hasRegisteredScope } from "./ScopeRegistry.js";
4
- import { jsx } from "react/jsx-runtime";
5
- var NO_OP_SUBSCRIBE = () => () => {
6
- };
7
- var NO_OP_FLUSH_SYNC = () => {
8
- };
9
- var NO_OP_SCOPE_FIELD = (() => {
10
- const fn = (() => {
11
- throw new Error(
12
- "You need to wrap this component/hook in <AssistantProvider>"
13
- );
14
- });
15
- fn.source = null;
16
- fn.query = null;
17
- return fn;
18
- })();
19
- var AssistantContext = createContext(
20
- new Proxy({}, {
21
- get(_, prop) {
22
- if (prop === "subscribe") return NO_OP_SUBSCRIBE;
23
- if (prop === "on") return NO_OP_SUBSCRIBE;
24
- if (prop === "flushSync") return NO_OP_FLUSH_SYNC;
25
- if (hasRegisteredScope(prop))
26
- return NO_OP_SCOPE_FIELD;
27
- return null;
28
- }
29
- })
30
- );
31
- var useAssistantContextValue = () => {
32
- return useContext(AssistantContext);
33
- };
34
- var AssistantProvider = ({
35
- client,
36
- children
37
- }) => {
38
- return /* @__PURE__ */ jsx(AssistantContext.Provider, { value: client, children });
39
- };
40
- export {
41
- AssistantContext,
42
- AssistantProvider,
43
- useAssistantContextValue
44
- };
45
- //# sourceMappingURL=AssistantContext.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/AssistantContext.tsx"],"sourcesContent":["import React, { createContext, useContext } from \"react\";\nimport type { AssistantClient, AssistantScopes, ScopeField } from \"./types\";\nimport { hasRegisteredScope } from \"./ScopeRegistry\";\n\nconst NO_OP_SUBSCRIBE = () => () => {};\nconst NO_OP_FLUSH_SYNC = () => {};\nconst NO_OP_SCOPE_FIELD = (() => {\n const fn = (() => {\n throw new Error(\n \"You need to wrap this component/hook in <AssistantProvider>\",\n );\n }) as ScopeField<never>;\n fn.source = null;\n fn.query = null;\n return fn;\n})();\n\n/**\n * React Context for the AssistantClient\n */\nexport const AssistantContext = createContext<AssistantClient>(\n new Proxy({} as AssistantClient, {\n get(_, prop: string) {\n // Allow access to subscribe and flushSync without error\n if (prop === \"subscribe\") return NO_OP_SUBSCRIBE;\n if (prop === \"on\") return NO_OP_SUBSCRIBE;\n if (prop === \"flushSync\") return NO_OP_FLUSH_SYNC;\n\n // If this is a registered scope, return a function that errors when called or accessed\n if (hasRegisteredScope(prop as keyof AssistantScopes))\n return NO_OP_SCOPE_FIELD;\n\n return null;\n },\n }),\n);\n\nexport const useAssistantContextValue = (): AssistantClient => {\n return useContext(AssistantContext);\n};\n\n/**\n * Provider component for AssistantClient\n *\n * @example\n * ```typescript\n * <AssistantProvider client={client}>\n * <YourApp />\n * </AssistantProvider>\n * ```\n */\nexport const AssistantProvider = ({\n client,\n children,\n}: {\n client: AssistantClient;\n children: React.ReactNode;\n}): React.ReactElement => {\n return (\n <AssistantContext.Provider value={client}>\n {children}\n </AssistantContext.Provider>\n );\n};\n"],"mappings":";AAAA,SAAgB,eAAe,kBAAkB;AAEjD,SAAS,0BAA0B;AAyD/B;AAvDJ,IAAM,kBAAkB,MAAM,MAAM;AAAC;AACrC,IAAM,mBAAmB,MAAM;AAAC;AAChC,IAAM,qBAAqB,MAAM;AAC/B,QAAM,MAAM,MAAM;AAChB,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACA,KAAG,SAAS;AACZ,KAAG,QAAQ;AACX,SAAO;AACT,GAAG;AAKI,IAAM,mBAAmB;AAAA,EAC9B,IAAI,MAAM,CAAC,GAAsB;AAAA,IAC/B,IAAI,GAAG,MAAc;AAEnB,UAAI,SAAS,YAAa,QAAO;AACjC,UAAI,SAAS,KAAM,QAAO;AAC1B,UAAI,SAAS,YAAa,QAAO;AAGjC,UAAI,mBAAmB,IAA6B;AAClD,eAAO;AAET,aAAO;AAAA,IACT;AAAA,EACF,CAAC;AACH;AAEO,IAAM,2BAA2B,MAAuB;AAC7D,SAAO,WAAW,gBAAgB;AACpC;AAYO,IAAM,oBAAoB,CAAC;AAAA,EAChC;AAAA,EACA;AACF,MAG0B;AACxB,SACE,oBAAC,iBAAiB,UAAjB,EAA0B,OAAO,QAC/B,UACH;AAEJ;","names":[]}