@assistant-ui/store 0.0.1 → 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +59 -262
- package/dist/AssistantIf.d.ts +10 -0
- package/dist/AssistantIf.d.ts.map +1 -0
- package/dist/AssistantIf.js +13 -0
- package/dist/AssistantIf.js.map +1 -0
- package/dist/Derived.d.ts +34 -0
- package/dist/Derived.d.ts.map +1 -0
- package/dist/Derived.js +11 -0
- package/dist/Derived.js.map +1 -0
- package/dist/attachDefaultPeers.d.ts +56 -0
- package/dist/attachDefaultPeers.d.ts.map +1 -0
- package/dist/attachDefaultPeers.js +22 -0
- package/dist/attachDefaultPeers.js.map +1 -0
- package/dist/index.d.ts +11 -14
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +19 -16
- package/dist/index.js.map +1 -1
- package/dist/tapClientList.d.ts +24 -0
- package/dist/tapClientList.d.ts.map +1 -0
- package/dist/tapClientList.js +72 -0
- package/dist/tapClientList.js.map +1 -0
- package/dist/tapClientLookup.d.ts +11 -0
- package/dist/tapClientLookup.d.ts.map +1 -0
- package/dist/tapClientLookup.js +42 -0
- package/dist/tapClientLookup.js.map +1 -0
- package/dist/tapClientResource.d.ts +24 -0
- package/dist/tapClientResource.d.ts.map +1 -0
- package/dist/tapClientResource.js +100 -0
- package/dist/tapClientResource.js.map +1 -0
- package/dist/types/client.d.ts +117 -0
- package/dist/types/client.d.ts.map +1 -0
- package/dist/types/client.js +1 -0
- package/dist/types/events.d.ts +33 -0
- package/dist/types/events.d.ts.map +1 -0
- package/dist/types/events.js +12 -0
- package/dist/types/events.js.map +1 -0
- package/dist/useAssistantClient.d.ts +13 -41
- package/dist/useAssistantClient.d.ts.map +1 -1
- package/dist/useAssistantClient.js +185 -130
- package/dist/useAssistantClient.js.map +1 -1
- package/dist/useAssistantEvent.d.ts +2 -2
- package/dist/useAssistantEvent.d.ts.map +1 -1
- package/dist/useAssistantEvent.js +3 -6
- package/dist/useAssistantEvent.js.map +1 -1
- package/dist/useAssistantState.d.ts +2 -2
- package/dist/useAssistantState.d.ts.map +1 -1
- package/dist/useAssistantState.js +7 -36
- package/dist/useAssistantState.js.map +1 -1
- package/dist/utils/BaseProxyHandler.d.ts +23 -0
- package/dist/utils/BaseProxyHandler.d.ts.map +1 -0
- package/dist/utils/BaseProxyHandler.js +41 -0
- package/dist/utils/BaseProxyHandler.js.map +1 -0
- package/dist/utils/NotificationManager.d.ts +11 -0
- package/dist/utils/NotificationManager.d.ts.map +1 -0
- package/dist/utils/NotificationManager.js +81 -0
- package/dist/utils/NotificationManager.js.map +1 -0
- package/dist/utils/StoreResource.d.ts +14 -0
- package/dist/utils/StoreResource.d.ts.map +1 -0
- package/dist/utils/StoreResource.js +23 -0
- package/dist/utils/StoreResource.js.map +1 -0
- package/dist/utils/proxied-assistant-state.d.ts +8 -0
- package/dist/utils/proxied-assistant-state.d.ts.map +1 -0
- package/dist/utils/proxied-assistant-state.js +41 -0
- package/dist/utils/proxied-assistant-state.js.map +1 -0
- package/dist/{AssistantContext.d.ts → utils/react-assistant-context.d.ts} +6 -6
- package/dist/utils/react-assistant-context.d.ts.map +1 -0
- package/dist/utils/react-assistant-context.js +73 -0
- package/dist/utils/react-assistant-context.js.map +1 -0
- package/dist/utils/splitClients.d.ts +28 -0
- package/dist/utils/splitClients.d.ts.map +1 -0
- package/dist/utils/splitClients.js +37 -0
- package/dist/utils/splitClients.js.map +1 -0
- package/dist/utils/tap-assistant-context.d.ts +19 -0
- package/dist/utils/tap-assistant-context.d.ts.map +1 -0
- package/dist/utils/tap-assistant-context.js +37 -0
- package/dist/utils/tap-assistant-context.js.map +1 -0
- package/dist/utils/tap-client-stack-context.d.ts +23 -0
- package/dist/utils/tap-client-stack-context.d.ts.map +1 -0
- package/dist/utils/tap-client-stack-context.js +30 -0
- package/dist/utils/tap-client-stack-context.js.map +1 -0
- package/package.json +5 -6
- package/src/AssistantIf.tsx +17 -0
- package/src/Derived.ts +46 -0
- package/src/attachDefaultPeers.ts +78 -0
- package/src/index.ts +31 -25
- package/src/tapClientList.ts +105 -0
- package/src/tapClientLookup.ts +56 -0
- package/src/tapClientResource.ts +152 -0
- package/src/types/client.ts +186 -0
- package/src/types/events.ts +77 -0
- package/src/useAssistantClient.tsx +259 -217
- package/src/useAssistantEvent.ts +6 -9
- package/src/useAssistantState.tsx +10 -48
- package/src/utils/BaseProxyHandler.ts +50 -0
- package/src/utils/NotificationManager.ts +110 -0
- package/src/utils/StoreResource.ts +36 -0
- package/src/utils/proxied-assistant-state.tsx +53 -0
- package/src/utils/react-assistant-context.tsx +107 -0
- package/src/utils/splitClients.ts +85 -0
- package/src/utils/tap-assistant-context.ts +59 -0
- package/src/utils/tap-client-stack-context.ts +51 -0
- package/dist/AssistantContext.d.ts.map +0 -1
- package/dist/AssistantContext.js +0 -45
- package/dist/AssistantContext.js.map +0 -1
- package/dist/DerivedScope.d.ts +0 -18
- package/dist/DerivedScope.d.ts.map +0 -1
- package/dist/DerivedScope.js +0 -11
- package/dist/DerivedScope.js.map +0 -1
- package/dist/EventContext.d.ts +0 -65
- package/dist/EventContext.d.ts.map +0 -1
- package/dist/EventContext.js +0 -62
- package/dist/EventContext.js.map +0 -1
- package/dist/ScopeRegistry.d.ts +0 -41
- package/dist/ScopeRegistry.d.ts.map +0 -1
- package/dist/ScopeRegistry.js +0 -17
- package/dist/ScopeRegistry.js.map +0 -1
- package/dist/StoreContext.d.ts +0 -9
- package/dist/StoreContext.d.ts.map +0 -1
- package/dist/StoreContext.js +0 -20
- package/dist/StoreContext.js.map +0 -1
- package/dist/asStore.d.ts +0 -20
- package/dist/asStore.d.ts.map +0 -1
- package/dist/asStore.js +0 -23
- package/dist/asStore.js.map +0 -1
- package/dist/tapApi.d.ts +0 -36
- package/dist/tapApi.d.ts.map +0 -1
- package/dist/tapApi.js +0 -52
- package/dist/tapApi.js.map +0 -1
- package/dist/tapLookupResources.d.ts +0 -44
- package/dist/tapLookupResources.d.ts.map +0 -1
- package/dist/tapLookupResources.js +0 -21
- package/dist/tapLookupResources.js.map +0 -1
- package/dist/tapStoreList.d.ts +0 -76
- package/dist/tapStoreList.d.ts.map +0 -1
- package/dist/tapStoreList.js +0 -46
- package/dist/tapStoreList.js.map +0 -1
- package/dist/types.d.ts +0 -88
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -1
- package/dist/utils/splitScopes.d.ts +0 -24
- package/dist/utils/splitScopes.d.ts.map +0 -1
- package/dist/utils/splitScopes.js +0 -18
- package/dist/utils/splitScopes.js.map +0 -1
- package/src/AssistantContext.tsx +0 -64
- package/src/DerivedScope.ts +0 -21
- package/src/EventContext.ts +0 -184
- package/src/ScopeRegistry.ts +0 -58
- package/src/StoreContext.ts +0 -28
- package/src/asStore.ts +0 -40
- package/src/tapApi.ts +0 -91
- package/src/tapLookupResources.ts +0 -62
- package/src/tapStoreList.ts +0 -133
- package/src/types.ts +0 -129
- package/src/utils/splitScopes.ts +0 -38
- /package/dist/{types.js.map → types/client.js.map} +0 -0
|
@@ -1,41 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import type {
|
|
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
|
|
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
|
|
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
|
-
|
|
28
|
+
aui.subscribe,
|
|
67
29
|
() => selector(proxiedState),
|
|
68
30
|
() => selector(proxiedState),
|
|
69
31
|
);
|
|
70
32
|
|
|
71
|
-
|
|
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"}
|
package/dist/AssistantContext.js
DELETED
|
@@ -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":[]}
|