@assistant-ui/store 0.1.6 → 0.2.1
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 +9 -10
- package/dist/AuiIf.d.ts.map +1 -1
- package/dist/AuiIf.js.map +1 -1
- package/dist/attachTransformScopes.d.ts +11 -0
- package/dist/attachTransformScopes.d.ts.map +1 -0
- package/dist/attachTransformScopes.js +12 -0
- package/dist/attachTransformScopes.js.map +1 -0
- package/dist/index.d.ts +4 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/tapClientList.d.ts +9 -5
- package/dist/tapClientList.d.ts.map +1 -1
- package/dist/tapClientList.js.map +1 -1
- package/dist/tapClientLookup.d.ts +7 -3
- package/dist/tapClientLookup.d.ts.map +1 -1
- package/dist/tapClientLookup.js +2 -2
- package/dist/tapClientLookup.js.map +1 -1
- package/dist/tapClientResource.d.ts +9 -24
- package/dist/tapClientResource.d.ts.map +1 -1
- package/dist/tapClientResource.js +11 -19
- package/dist/tapClientResource.js.map +1 -1
- package/dist/types/client.d.ts +23 -27
- package/dist/types/client.d.ts.map +1 -1
- package/dist/useAui.d.ts.map +1 -1
- package/dist/useAui.js +4 -4
- package/dist/useAui.js.map +1 -1
- package/dist/useAuiState.d.ts +1 -1
- package/dist/useAuiState.d.ts.map +1 -1
- package/dist/useAuiState.js +1 -1
- package/dist/useAuiState.js.map +1 -1
- package/dist/utils/proxied-assistant-state.d.ts.map +1 -1
- package/dist/utils/proxied-assistant-state.js.map +1 -1
- package/dist/utils/splitClients.d.ts.map +1 -1
- package/dist/utils/splitClients.js +25 -55
- package/dist/utils/splitClients.js.map +1 -1
- package/package.json +11 -4
- package/src/__tests__/hooks.test.tsx +126 -0
- package/src/attachTransformScopes.ts +38 -0
- package/src/index.ts +18 -9
- package/src/tapClientList.ts +13 -10
- package/src/tapClientLookup.ts +17 -14
- package/src/tapClientResource.ts +36 -31
- package/src/types/client.ts +41 -47
- package/src/{useAui.tsx → useAui.ts} +5 -6
- package/src/{useAuiState.tsx → useAuiState.ts} +2 -2
- package/src/utils/splitClients.ts +32 -82
- package/dist/attachDefaultPeers.d.ts +0 -56
- package/dist/attachDefaultPeers.d.ts.map +0 -1
- package/dist/attachDefaultPeers.js +0 -51
- package/dist/attachDefaultPeers.js.map +0 -1
- package/src/attachDefaultPeers.ts +0 -78
- /package/src/{AuiIf.tsx → AuiIf.ts} +0 -0
- /package/src/utils/{proxied-assistant-state.tsx → proxied-assistant-state.ts} +0 -0
package/src/tapClientResource.ts
CHANGED
|
@@ -4,9 +4,8 @@ import {
|
|
|
4
4
|
tapRef,
|
|
5
5
|
type ResourceElement,
|
|
6
6
|
tapResource,
|
|
7
|
-
tapInlineResource,
|
|
8
7
|
} from "@assistant-ui/tap";
|
|
9
|
-
import type { ClientMethods
|
|
8
|
+
import type { ClientMethods } from "./types/client";
|
|
10
9
|
import {
|
|
11
10
|
tapClientStack,
|
|
12
11
|
tapWithClientStack,
|
|
@@ -25,7 +24,7 @@ import { wrapperResource } from "./wrapperResource";
|
|
|
25
24
|
const SYMBOL_GET_OUTPUT = Symbol("assistant-ui.store.getValue");
|
|
26
25
|
|
|
27
26
|
type ClientInternal = {
|
|
28
|
-
[SYMBOL_GET_OUTPUT]:
|
|
27
|
+
[SYMBOL_GET_OUTPUT]: ClientMethods;
|
|
29
28
|
};
|
|
30
29
|
|
|
31
30
|
export const getClientState = (client: ClientMethods) => {
|
|
@@ -36,7 +35,7 @@ export const getClientState = (client: ClientMethods) => {
|
|
|
36
35
|
"Ensure your Derived get() returns a client created with tapClientResource(), not a plain resource.",
|
|
37
36
|
);
|
|
38
37
|
}
|
|
39
|
-
return output.
|
|
38
|
+
return (output as any).getState?.();
|
|
40
39
|
};
|
|
41
40
|
|
|
42
41
|
// Global cache for function templates by field name
|
|
@@ -64,7 +63,7 @@ function getOrCreateProxyFn(prop: string | symbol) {
|
|
|
64
63
|
);
|
|
65
64
|
}
|
|
66
65
|
|
|
67
|
-
const method = output
|
|
66
|
+
const method = output[prop];
|
|
68
67
|
if (!method)
|
|
69
68
|
throw new Error(`Method "${String(prop)}" is not implemented.`);
|
|
70
69
|
if (typeof method !== "function")
|
|
@@ -85,7 +84,7 @@ class ClientProxyHandler
|
|
|
85
84
|
|
|
86
85
|
constructor(
|
|
87
86
|
private readonly outputRef: {
|
|
88
|
-
current:
|
|
87
|
+
current: ClientMethods;
|
|
89
88
|
},
|
|
90
89
|
private readonly index: number,
|
|
91
90
|
) {
|
|
@@ -97,7 +96,7 @@ class ClientProxyHandler
|
|
|
97
96
|
if (prop === SYMBOL_CLIENT_INDEX) return this.index;
|
|
98
97
|
const introspection = handleIntrospectionProp(prop, "ClientProxy");
|
|
99
98
|
if (introspection !== false) return introspection;
|
|
100
|
-
const value = this.outputRef.current
|
|
99
|
+
const value = this.outputRef.current[prop];
|
|
101
100
|
if (typeof value === "function") {
|
|
102
101
|
if (this.cachedReceiver !== receiver) {
|
|
103
102
|
this.boundFns = new Map();
|
|
@@ -114,43 +113,34 @@ class ClientProxyHandler
|
|
|
114
113
|
}
|
|
115
114
|
|
|
116
115
|
ownKeys(): ArrayLike<string | symbol> {
|
|
117
|
-
return Object.keys(this.outputRef.current
|
|
116
|
+
return Object.keys(this.outputRef.current);
|
|
118
117
|
}
|
|
119
118
|
|
|
120
119
|
has(_: unknown, prop: string | symbol) {
|
|
121
120
|
if (prop === SYMBOL_GET_OUTPUT) return true;
|
|
122
121
|
if (prop === SYMBOL_CLIENT_INDEX) return true;
|
|
123
|
-
return prop in this.outputRef.current
|
|
122
|
+
return prop in this.outputRef.current;
|
|
124
123
|
}
|
|
125
124
|
}
|
|
126
125
|
|
|
127
126
|
/**
|
|
128
127
|
* Resource that wraps a plain resource element to create a stable client proxy.
|
|
129
128
|
*
|
|
130
|
-
* Takes a ResourceElement that returns
|
|
129
|
+
* Takes a ResourceElement that returns methods (with optional getState()) and
|
|
131
130
|
* wraps it to produce a stable client proxy. This adds the client to the
|
|
132
131
|
* client stack, enabling event scoping.
|
|
133
132
|
*
|
|
134
|
-
*
|
|
135
|
-
*
|
|
136
|
-
* @example
|
|
137
|
-
* ```typescript
|
|
138
|
-
* const MessageResource = resource(({ messageId }: { messageId: string }) => {
|
|
139
|
-
* return tapInlineResource(
|
|
140
|
-
* tapClientResource(InnerMessageResource({ messageId }))
|
|
141
|
-
* );
|
|
142
|
-
* });
|
|
143
|
-
* ```
|
|
133
|
+
* @internal
|
|
144
134
|
*/
|
|
145
135
|
export const ClientResource = wrapperResource(
|
|
146
|
-
<
|
|
147
|
-
element: ResourceElement<
|
|
148
|
-
):
|
|
136
|
+
<TMethods extends ClientMethods>(
|
|
137
|
+
element: ResourceElement<TMethods>,
|
|
138
|
+
): {
|
|
139
|
+
methods: TMethods;
|
|
140
|
+
state: unknown;
|
|
149
141
|
key: string | number | undefined;
|
|
150
142
|
} => {
|
|
151
|
-
const valueRef = tapRef(
|
|
152
|
-
null as unknown as ClientOutputOf<TState, TMethods>,
|
|
153
|
-
);
|
|
143
|
+
const valueRef = tapRef(null as unknown as TMethods);
|
|
154
144
|
|
|
155
145
|
const index = tapClientStack().length;
|
|
156
146
|
const methods = tapMemo(
|
|
@@ -171,12 +161,27 @@ export const ClientResource = wrapperResource(
|
|
|
171
161
|
valueRef.current = value;
|
|
172
162
|
});
|
|
173
163
|
|
|
174
|
-
|
|
164
|
+
const state = (value as any).getState?.();
|
|
165
|
+
return { methods, state, key: element.key };
|
|
175
166
|
},
|
|
176
167
|
);
|
|
177
168
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
169
|
+
type InferClientState<TMethods> = TMethods extends {
|
|
170
|
+
getState: () => infer S;
|
|
171
|
+
}
|
|
172
|
+
? S
|
|
173
|
+
: undefined;
|
|
174
|
+
|
|
175
|
+
export const tapClientResource = <TMethods extends ClientMethods>(
|
|
176
|
+
element: ResourceElement<TMethods>,
|
|
177
|
+
): {
|
|
178
|
+
state: InferClientState<TMethods>;
|
|
179
|
+
methods: TMethods;
|
|
180
|
+
key: string | number | undefined;
|
|
181
|
+
} => {
|
|
182
|
+
return tapResource(ClientResource(element)) as {
|
|
183
|
+
state: InferClientState<TMethods>;
|
|
184
|
+
methods: TMethods;
|
|
185
|
+
key: string | number | undefined;
|
|
186
|
+
};
|
|
182
187
|
};
|
package/src/types/client.ts
CHANGED
|
@@ -9,7 +9,7 @@ import type {
|
|
|
9
9
|
* Base type for methods that can be called on a client.
|
|
10
10
|
*/
|
|
11
11
|
export interface ClientMethods {
|
|
12
|
-
[key: string | symbol]: (
|
|
12
|
+
[key: string | symbol]: (...args: any[]) => any;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
type ClientMetaType = { source: ClientNames; query: Record<string, unknown> };
|
|
@@ -23,12 +23,10 @@ type ClientMetaType = { source: ClientNames; query: Record<string, unknown> };
|
|
|
23
23
|
* @internal
|
|
24
24
|
*/
|
|
25
25
|
export type ClientSchema<
|
|
26
|
-
TState = unknown,
|
|
27
26
|
TMethods extends ClientMethods = ClientMethods,
|
|
28
27
|
TMeta extends ClientMetaType = never,
|
|
29
28
|
TEvents extends Record<string, unknown> = never,
|
|
30
29
|
> = {
|
|
31
|
-
state: TState;
|
|
32
30
|
methods: TMethods;
|
|
33
31
|
meta?: TMeta;
|
|
34
32
|
events?: TEvents;
|
|
@@ -40,16 +38,20 @@ export type ClientSchema<
|
|
|
40
38
|
* @example
|
|
41
39
|
* ```typescript
|
|
42
40
|
* declare module "@assistant-ui/store" {
|
|
43
|
-
* interface
|
|
41
|
+
* interface ScopeRegistry {
|
|
44
42
|
* // Simple client (meta and events are optional)
|
|
45
43
|
* foo: {
|
|
46
|
-
*
|
|
47
|
-
*
|
|
44
|
+
* methods: {
|
|
45
|
+
* getState: () => { bar: string };
|
|
46
|
+
* updateBar: (bar: string) => void;
|
|
47
|
+
* };
|
|
48
48
|
* };
|
|
49
49
|
* // Full client with meta and events
|
|
50
50
|
* bar: {
|
|
51
|
-
*
|
|
52
|
-
*
|
|
51
|
+
* methods: {
|
|
52
|
+
* getState: () => { id: string };
|
|
53
|
+
* update: () => void;
|
|
54
|
+
* };
|
|
53
55
|
* meta: { source: "fooList"; query: { index: number } };
|
|
54
56
|
* events: {
|
|
55
57
|
* "bar.updated": { id: string };
|
|
@@ -59,7 +61,7 @@ export type ClientSchema<
|
|
|
59
61
|
* }
|
|
60
62
|
* ```
|
|
61
63
|
*/
|
|
62
|
-
export interface
|
|
64
|
+
export interface ScopeRegistry {}
|
|
63
65
|
|
|
64
66
|
type ClientEventsType<K extends ClientNames> = Record<
|
|
65
67
|
`${K}.${string}`,
|
|
@@ -67,63 +69,51 @@ type ClientEventsType<K extends ClientNames> = Record<
|
|
|
67
69
|
>;
|
|
68
70
|
|
|
69
71
|
type ClientError<E extends string> = {
|
|
70
|
-
state: E;
|
|
71
72
|
methods: Record<E, () => E>;
|
|
72
73
|
meta: { source: ClientNames; query: Record<E, E> };
|
|
73
74
|
events: Record<`${E}.`, E>;
|
|
74
75
|
};
|
|
75
76
|
|
|
76
|
-
type ValidateClient<K extends keyof
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
: ClientRegistry[K]
|
|
85
|
-
: ClientError<`ERROR: ${K & string} has invalid meta type`>
|
|
86
|
-
: "events" extends keyof ClientRegistry[K]
|
|
87
|
-
? ClientRegistry[K]["events"] extends ClientEventsType<K>
|
|
88
|
-
? ClientRegistry[K]
|
|
77
|
+
type ValidateClient<K extends keyof ScopeRegistry> = ScopeRegistry[K] extends {
|
|
78
|
+
methods: ClientMethods;
|
|
79
|
+
}
|
|
80
|
+
? "meta" extends keyof ScopeRegistry[K]
|
|
81
|
+
? ScopeRegistry[K]["meta"] extends ClientMetaType
|
|
82
|
+
? "events" extends keyof ScopeRegistry[K]
|
|
83
|
+
? ScopeRegistry[K]["events"] extends ClientEventsType<K>
|
|
84
|
+
? ScopeRegistry[K]
|
|
89
85
|
: ClientError<`ERROR: ${K & string} has invalid events type`>
|
|
90
|
-
:
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
86
|
+
: ScopeRegistry[K]
|
|
87
|
+
: ClientError<`ERROR: ${K & string} has invalid meta type`>
|
|
88
|
+
: "events" extends keyof ScopeRegistry[K]
|
|
89
|
+
? ScopeRegistry[K]["events"] extends ClientEventsType<K>
|
|
90
|
+
? ScopeRegistry[K]
|
|
91
|
+
: ClientError<`ERROR: ${K & string} has invalid events type`>
|
|
92
|
+
: ScopeRegistry[K]
|
|
93
|
+
: ClientError<`ERROR: ${K & string} has invalid methods type`>;
|
|
94
|
+
|
|
95
|
+
type ClientSchemas = keyof ScopeRegistry extends never
|
|
94
96
|
? {
|
|
95
97
|
"ERROR: No clients were defined": ClientError<"ERROR: No clients were defined">;
|
|
96
98
|
}
|
|
97
|
-
: { [K in keyof
|
|
99
|
+
: { [K in keyof ScopeRegistry]: ValidateClient<K> };
|
|
98
100
|
|
|
99
101
|
/**
|
|
100
|
-
* Output type that client resources return
|
|
102
|
+
* Output type that client resources return (just methods).
|
|
101
103
|
*
|
|
102
104
|
* @example
|
|
103
105
|
* ```typescript
|
|
104
106
|
* const FooResource = resource((): ClientResourceOutput<"foo"> => {
|
|
105
107
|
* const [state, setState] = tapState({ bar: "hello" });
|
|
106
108
|
* return {
|
|
107
|
-
* state,
|
|
108
|
-
*
|
|
109
|
-
* updateBar: (b) => setState({ bar: b })
|
|
110
|
-
* }
|
|
109
|
+
* getState: () => state,
|
|
110
|
+
* updateBar: (b) => setState({ bar: b }),
|
|
111
111
|
* };
|
|
112
112
|
* });
|
|
113
113
|
* ```
|
|
114
114
|
*/
|
|
115
|
-
export type ClientOutput<K extends ClientNames> =
|
|
116
|
-
|
|
117
|
-
ClientSchemas[K]["methods"] & ClientMethods
|
|
118
|
-
>;
|
|
119
|
-
|
|
120
|
-
/**
|
|
121
|
-
* Generic version of ClientResourceOutput for library code.
|
|
122
|
-
*/
|
|
123
|
-
export type ClientOutputOf<TState, TMethods extends ClientMethods> = {
|
|
124
|
-
state: TState;
|
|
125
|
-
methods: TMethods;
|
|
126
|
-
};
|
|
115
|
+
export type ClientOutput<K extends ClientNames> = ClientSchemas[K]["methods"] &
|
|
116
|
+
ClientMethods;
|
|
127
117
|
|
|
128
118
|
export type ClientNames = keyof ClientSchemas extends infer U ? U : never;
|
|
129
119
|
|
|
@@ -154,10 +144,14 @@ export type ClientElement<K extends ClientNames> = ResourceElement<
|
|
|
154
144
|
export type Unsubscribe = () => void;
|
|
155
145
|
|
|
156
146
|
/**
|
|
157
|
-
* State type extracted from all clients.
|
|
147
|
+
* State type extracted from all clients via their getState() methods.
|
|
158
148
|
*/
|
|
159
149
|
export type AssistantState = {
|
|
160
|
-
[K in ClientNames]: ClientSchemas[K]["
|
|
150
|
+
[K in ClientNames]: ClientSchemas[K]["methods"] extends {
|
|
151
|
+
getState: () => infer S;
|
|
152
|
+
}
|
|
153
|
+
? S
|
|
154
|
+
: never;
|
|
161
155
|
};
|
|
162
156
|
|
|
163
157
|
/**
|
|
@@ -6,12 +6,11 @@ import {
|
|
|
6
6
|
tapMemo,
|
|
7
7
|
tapResources,
|
|
8
8
|
tapEffectEvent,
|
|
9
|
-
tapInlineResource,
|
|
10
9
|
tapEffect,
|
|
11
10
|
tapRef,
|
|
12
11
|
tapResource,
|
|
13
12
|
withKey,
|
|
14
|
-
|
|
13
|
+
tapResourceRoot,
|
|
15
14
|
} from "@assistant-ui/tap";
|
|
16
15
|
import type {
|
|
17
16
|
AssistantClient,
|
|
@@ -46,7 +45,7 @@ import {
|
|
|
46
45
|
createProxiedAssistantState,
|
|
47
46
|
} from "./utils/proxied-assistant-state";
|
|
48
47
|
|
|
49
|
-
const tapShallowMemoArray = <T
|
|
48
|
+
const tapShallowMemoArray = <T>(array: readonly T[]) => {
|
|
50
49
|
// biome-ignore lint/correctness/useExhaustiveDependencies: shallow memo
|
|
51
50
|
return tapMemo(() => array, array);
|
|
52
51
|
};
|
|
@@ -81,7 +80,7 @@ const RootClientAccessorResource = resource(
|
|
|
81
80
|
clientRef: { parent: AssistantClient; current: AssistantClient | null };
|
|
82
81
|
name: K;
|
|
83
82
|
}): AssistantClientAccessor<K> => {
|
|
84
|
-
const store =
|
|
83
|
+
const store = tapResourceRoot(
|
|
85
84
|
RootClientResource({ element, emit: notifications.emit, clientRef }),
|
|
86
85
|
);
|
|
87
86
|
|
|
@@ -129,7 +128,7 @@ const RootClientsAccessorsResource = resource(
|
|
|
129
128
|
clients: RootClients;
|
|
130
129
|
clientRef: { parent: AssistantClient; current: AssistantClient | null };
|
|
131
130
|
}) => {
|
|
132
|
-
const notifications =
|
|
131
|
+
const notifications = tapResource(NotificationManager());
|
|
133
132
|
|
|
134
133
|
tapEffect(
|
|
135
134
|
() => clientRef.parent.subscribe(notifications.notifySubscribers),
|
|
@@ -318,7 +317,7 @@ export const AssistantClientResource = resource(
|
|
|
318
317
|
: NoOpRootClientsAccessorsResource(),
|
|
319
318
|
);
|
|
320
319
|
|
|
321
|
-
const derivedFields =
|
|
320
|
+
const derivedFields = tapResource(
|
|
322
321
|
DerivedClientsAccessorsResource({ clients: derivedClients, clientRef }),
|
|
323
322
|
);
|
|
324
323
|
|
|
@@ -15,10 +15,10 @@ import { getProxiedAssistantState } from "./utils/proxied-assistant-state";
|
|
|
15
15
|
* foo: RootScope({ ... }),
|
|
16
16
|
* });
|
|
17
17
|
*
|
|
18
|
-
* const bar = useAuiState((
|
|
18
|
+
* const bar = useAuiState((s) => s.foo.bar);
|
|
19
19
|
* ```
|
|
20
20
|
*/
|
|
21
|
-
export const useAuiState = <T
|
|
21
|
+
export const useAuiState = <T>(selector: (state: AssistantState) => T): T => {
|
|
22
22
|
const aui = useAui();
|
|
23
23
|
const proxiedState = getProxiedAssistantState(aui);
|
|
24
24
|
|
|
@@ -4,7 +4,7 @@ import type {
|
|
|
4
4
|
ClientElement,
|
|
5
5
|
ClientNames,
|
|
6
6
|
} from "../types/client";
|
|
7
|
-
import {
|
|
7
|
+
import { getTransformScopes } from "../attachTransformScopes";
|
|
8
8
|
import type { useAui } from "../useAui";
|
|
9
9
|
import { tapMemo } from "@assistant-ui/tap";
|
|
10
10
|
|
|
@@ -16,99 +16,49 @@ export type DerivedClients = Partial<
|
|
|
16
16
|
>;
|
|
17
17
|
|
|
18
18
|
/**
|
|
19
|
-
* Splits a clients object into root clients and derived clients
|
|
20
|
-
*
|
|
21
|
-
* @param clients - The clients input object to split
|
|
22
|
-
* @returns An object with { rootClients, derivedClients }
|
|
23
|
-
*
|
|
24
|
-
* @example
|
|
25
|
-
* ```typescript
|
|
26
|
-
* const clients = {
|
|
27
|
-
* foo: RootClient({ ... }),
|
|
28
|
-
* bar: Derived({ ... }),
|
|
29
|
-
* };
|
|
30
|
-
*
|
|
31
|
-
* const { rootClients, derivedClients } = splitClients(clients);
|
|
32
|
-
* // rootClients = { foo: ... }
|
|
33
|
-
* // derivedClients = { bar: ... }
|
|
34
|
-
* ```
|
|
19
|
+
* Splits a clients object into root clients and derived clients,
|
|
20
|
+
* applying transformScopes from root client elements.
|
|
35
21
|
*/
|
|
36
22
|
function splitClients(clients: useAui.Props, baseClient: AssistantClient) {
|
|
23
|
+
// 1. Collect transforms from root elements and run them iteratively
|
|
24
|
+
let scopes = { ...clients } as Record<
|
|
25
|
+
string,
|
|
26
|
+
ClientElement<ClientNames> | DerivedElement<ClientNames>
|
|
27
|
+
>;
|
|
28
|
+
const visited = new Set<(...args: any[]) => any>();
|
|
29
|
+
|
|
30
|
+
let changed = true;
|
|
31
|
+
while (changed) {
|
|
32
|
+
changed = false;
|
|
33
|
+
for (const clientElement of Object.values(scopes)) {
|
|
34
|
+
if (clientElement.type === (Derived as unknown)) continue;
|
|
35
|
+
if (visited.has(clientElement.type)) continue;
|
|
36
|
+
visited.add(clientElement.type);
|
|
37
|
+
|
|
38
|
+
const transform = getTransformScopes(clientElement.type);
|
|
39
|
+
if (transform) {
|
|
40
|
+
scopes = transform(scopes, baseClient) as typeof scopes;
|
|
41
|
+
changed = true;
|
|
42
|
+
break; // restart iteration since scopes may have new root elements
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// 2. Split result into root/derived
|
|
37
48
|
const rootClients: RootClients = {};
|
|
38
49
|
const derivedClients: DerivedClients = {};
|
|
39
50
|
|
|
40
|
-
for (const [key, clientElement] of Object.entries(
|
|
41
|
-
|
|
42
|
-
|
|
51
|
+
for (const [key, clientElement] of Object.entries(scopes) as [
|
|
52
|
+
ClientNames,
|
|
53
|
+
ClientElement<ClientNames> | DerivedElement<ClientNames>,
|
|
43
54
|
][]) {
|
|
44
|
-
if (clientElement.type === Derived) {
|
|
55
|
+
if (clientElement.type === (Derived as unknown)) {
|
|
45
56
|
derivedClients[key] = clientElement as DerivedElement<ClientNames>;
|
|
46
57
|
} else {
|
|
47
58
|
rootClients[key] = clientElement as ClientElement<ClientNames>;
|
|
48
59
|
}
|
|
49
60
|
}
|
|
50
61
|
|
|
51
|
-
// Recursively gather all default peers, flattening nested ones
|
|
52
|
-
const gatherDefaultPeers = (
|
|
53
|
-
clientElement: ClientElement<ClientNames>,
|
|
54
|
-
visited = new Set<ClientElement<ClientNames>>(),
|
|
55
|
-
): Array<
|
|
56
|
-
[ClientNames, ClientElement<ClientNames> | DerivedElement<ClientNames>]
|
|
57
|
-
> => {
|
|
58
|
-
// Prevent infinite loops
|
|
59
|
-
if (visited.has(clientElement)) return [];
|
|
60
|
-
visited.add(clientElement);
|
|
61
|
-
|
|
62
|
-
const defaultPeers = getDefaultPeers(clientElement.type);
|
|
63
|
-
if (!defaultPeers) return [];
|
|
64
|
-
|
|
65
|
-
const result: Array<
|
|
66
|
-
[ClientNames, ClientElement<ClientNames> | DerivedElement<ClientNames>]
|
|
67
|
-
> = [];
|
|
68
|
-
|
|
69
|
-
for (const [key, peerElement] of Object.entries(defaultPeers) as [
|
|
70
|
-
ClientNames,
|
|
71
|
-
ClientElement<ClientNames> | DerivedElement<ClientNames>,
|
|
72
|
-
][]) {
|
|
73
|
-
result.push([key, peerElement]);
|
|
74
|
-
|
|
75
|
-
// If this peer is a root client with its own default peers, recursively gather them
|
|
76
|
-
if (peerElement.type !== Derived<ClientNames>) {
|
|
77
|
-
const nestedPeers = gatherDefaultPeers(
|
|
78
|
-
peerElement as ClientElement<ClientNames>,
|
|
79
|
-
visited,
|
|
80
|
-
);
|
|
81
|
-
result.push(...nestedPeers);
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
return result;
|
|
86
|
-
};
|
|
87
|
-
|
|
88
|
-
// Apply flattened default peers for each root client
|
|
89
|
-
for (const [_clientKey, clientElement] of Object.entries(rootClients) as [
|
|
90
|
-
ClientNames,
|
|
91
|
-
ClientElement<ClientNames>,
|
|
92
|
-
][]) {
|
|
93
|
-
const allPeers = gatherDefaultPeers(clientElement);
|
|
94
|
-
|
|
95
|
-
for (const [key, peerElement] of allPeers) {
|
|
96
|
-
// Skip if already exists (first wins)
|
|
97
|
-
if (
|
|
98
|
-
key in rootClients ||
|
|
99
|
-
key in derivedClients ||
|
|
100
|
-
baseClient[key].source !== null
|
|
101
|
-
)
|
|
102
|
-
continue;
|
|
103
|
-
|
|
104
|
-
if (peerElement.type === Derived<ClientNames>) {
|
|
105
|
-
derivedClients[key] = peerElement as DerivedElement<ClientNames>;
|
|
106
|
-
} else {
|
|
107
|
-
rootClients[key] = peerElement as ClientElement<ClientNames>;
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
|
|
112
62
|
return { rootClients, derivedClients };
|
|
113
63
|
}
|
|
114
64
|
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
import type { ResourceElement } from "@assistant-ui/tap";
|
|
2
|
-
import type { ClientElement, ClientNames } from "./types/client.js";
|
|
3
|
-
import type { DerivedElement } from "./Derived.js";
|
|
4
|
-
/**
|
|
5
|
-
* Symbol used to store default peer clients on a resource.
|
|
6
|
-
*/
|
|
7
|
-
declare const DEFAULT_PEERS: unique symbol;
|
|
8
|
-
/**
|
|
9
|
-
* Type for resources that have default peers attached.
|
|
10
|
-
*/
|
|
11
|
-
export type ResourceWithDefaultPeers = {
|
|
12
|
-
[DEFAULT_PEERS]?: DefaultPeers;
|
|
13
|
-
};
|
|
14
|
-
/**
|
|
15
|
-
* Default peers configuration - can be either root clients or derived clients.
|
|
16
|
-
*/
|
|
17
|
-
export type DefaultPeers = {
|
|
18
|
-
[K in ClientNames]?: ClientElement<K> | DerivedElement<K>;
|
|
19
|
-
};
|
|
20
|
-
/**
|
|
21
|
-
* Attaches default peer clients to a resource.
|
|
22
|
-
*
|
|
23
|
-
* Default peers are only applied if the scope doesn't exist:
|
|
24
|
-
* - Not defined in parent context
|
|
25
|
-
* - Not provided by user
|
|
26
|
-
* - Not already defined by a previous resource's default peers
|
|
27
|
-
*
|
|
28
|
-
* First definition wins - no overriding is permitted.
|
|
29
|
-
*
|
|
30
|
-
* @param resource - The resource to attach default peers to
|
|
31
|
-
* @param peers - The default peer clients to attach
|
|
32
|
-
* @throws Error if a peer key already exists in the resource's default peers
|
|
33
|
-
*
|
|
34
|
-
* @example
|
|
35
|
-
* ```typescript
|
|
36
|
-
* const ThreadListClient = resource(({ ... }) => { ... });
|
|
37
|
-
*
|
|
38
|
-
* attachDefaultPeers(ThreadListClient, {
|
|
39
|
-
* // Derived default peers
|
|
40
|
-
* thread: Derived({ source: "threads", query: { type: "main" }, get: ... }),
|
|
41
|
-
* threadListItem: Derived({ ... }),
|
|
42
|
-
* composer: Derived({ getMeta: ..., get: ... }),
|
|
43
|
-
*
|
|
44
|
-
* // Root default peers
|
|
45
|
-
* tools: Tools({}),
|
|
46
|
-
* modelContext: ModelContext({}),
|
|
47
|
-
* });
|
|
48
|
-
* ```
|
|
49
|
-
*/
|
|
50
|
-
export declare function attachDefaultPeers<T extends (...args: any[]) => ResourceElement<any>>(resource: T, peers: DefaultPeers): void;
|
|
51
|
-
/**
|
|
52
|
-
* Gets the default peers attached to a resource, if any.
|
|
53
|
-
*/
|
|
54
|
-
export declare function getDefaultPeers<T extends (...args: any[]) => ResourceElement<any>>(resource: T): DefaultPeers | undefined;
|
|
55
|
-
export {};
|
|
56
|
-
//# sourceMappingURL=attachDefaultPeers.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"attachDefaultPeers.d.ts","sourceRoot":"","sources":["../src/attachDefaultPeers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACzD,OAAO,KAAK,EAAE,aAAa,EAAE,WAAW,EAAE,0BAAuB;AACjE,OAAO,KAAK,EAAE,cAAc,EAAE,qBAAkB;AAEhD;;GAEG;AACH,QAAA,MAAM,aAAa,eAAuC,CAAC;AAE3D;;GAEG;AACH,MAAM,MAAM,wBAAwB,GAAG;IACrC,CAAC,aAAa,CAAC,CAAC,EAAE,YAAY,CAAC;CAChC,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,YAAY,GAAG;KACxB,CAAC,IAAI,WAAW,CAAC,CAAC,EAAE,aAAa,CAAC,CAAC,CAAC,GAAG,cAAc,CAAC,CAAC,CAAC;CAC1D,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAgB,kBAAkB,CAChC,CAAC,SAAS,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,eAAe,CAAC,GAAG,CAAC,EAClD,QAAQ,EAAE,CAAC,EAAE,KAAK,EAAE,YAAY,GAAG,IAAI,CAaxC;AAED;;GAEG;AACH,wBAAgB,eAAe,CAC7B,CAAC,SAAS,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,eAAe,CAAC,GAAG,CAAC,EAClD,QAAQ,EAAE,CAAC,GAAG,YAAY,GAAG,SAAS,CAEvC"}
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Symbol used to store default peer clients on a resource.
|
|
3
|
-
*/
|
|
4
|
-
const DEFAULT_PEERS = Symbol("assistant-ui.default-peers");
|
|
5
|
-
/**
|
|
6
|
-
* Attaches default peer clients to a resource.
|
|
7
|
-
*
|
|
8
|
-
* Default peers are only applied if the scope doesn't exist:
|
|
9
|
-
* - Not defined in parent context
|
|
10
|
-
* - Not provided by user
|
|
11
|
-
* - Not already defined by a previous resource's default peers
|
|
12
|
-
*
|
|
13
|
-
* First definition wins - no overriding is permitted.
|
|
14
|
-
*
|
|
15
|
-
* @param resource - The resource to attach default peers to
|
|
16
|
-
* @param peers - The default peer clients to attach
|
|
17
|
-
* @throws Error if a peer key already exists in the resource's default peers
|
|
18
|
-
*
|
|
19
|
-
* @example
|
|
20
|
-
* ```typescript
|
|
21
|
-
* const ThreadListClient = resource(({ ... }) => { ... });
|
|
22
|
-
*
|
|
23
|
-
* attachDefaultPeers(ThreadListClient, {
|
|
24
|
-
* // Derived default peers
|
|
25
|
-
* thread: Derived({ source: "threads", query: { type: "main" }, get: ... }),
|
|
26
|
-
* threadListItem: Derived({ ... }),
|
|
27
|
-
* composer: Derived({ getMeta: ..., get: ... }),
|
|
28
|
-
*
|
|
29
|
-
* // Root default peers
|
|
30
|
-
* tools: Tools({}),
|
|
31
|
-
* modelContext: ModelContext({}),
|
|
32
|
-
* });
|
|
33
|
-
* ```
|
|
34
|
-
*/
|
|
35
|
-
export function attachDefaultPeers(resource, peers) {
|
|
36
|
-
const resourceWithPeers = resource;
|
|
37
|
-
const existing = resourceWithPeers[DEFAULT_PEERS] ?? {};
|
|
38
|
-
for (const key of Object.keys(peers)) {
|
|
39
|
-
if (key in existing) {
|
|
40
|
-
throw new Error(`Default peer "${key}" is already attached to this resource`);
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
resourceWithPeers[DEFAULT_PEERS] = { ...existing, ...peers };
|
|
44
|
-
}
|
|
45
|
-
/**
|
|
46
|
-
* Gets the default peers attached to a resource, if any.
|
|
47
|
-
*/
|
|
48
|
-
export function getDefaultPeers(resource) {
|
|
49
|
-
return resource[DEFAULT_PEERS];
|
|
50
|
-
}
|
|
51
|
-
//# sourceMappingURL=attachDefaultPeers.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"attachDefaultPeers.js","sourceRoot":"","sources":["../src/attachDefaultPeers.ts"],"names":[],"mappings":"AAIA;;GAEG;AACH,MAAM,aAAa,GAAG,MAAM,CAAC,4BAA4B,CAAC,CAAC;AAgB3D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,MAAM,UAAU,kBAAkB,CAEhC,QAAW,EAAE,KAAmB;IAChC,MAAM,iBAAiB,GAAG,QAAwC,CAAC;IACnE,MAAM,QAAQ,GAAG,iBAAiB,CAAC,aAAa,CAAC,IAAI,EAAE,CAAC;IAExD,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QACrC,IAAI,GAAG,IAAI,QAAQ,EAAE,CAAC;YACpB,MAAM,IAAI,KAAK,CACb,iBAAiB,GAAG,wCAAwC,CAC7D,CAAC;QACJ,CAAC;IACH,CAAC;IAED,iBAAiB,CAAC,aAAa,CAAC,GAAG,EAAE,GAAG,QAAQ,EAAE,GAAG,KAAK,EAAE,CAAC;AAC/D,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,eAAe,CAE7B,QAAW;IACX,OAAQ,QAAyC,CAAC,aAAa,CAAC,CAAC;AACnE,CAAC"}
|