@assistant-ui/store 0.0.0
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/LICENSE +21 -0
- package/README.md +295 -0
- package/dist/AssistantContext.d.ts +22 -0
- package/dist/AssistantContext.d.ts.map +1 -0
- package/dist/AssistantContext.js +44 -0
- package/dist/AssistantContext.js.map +1 -0
- package/dist/DerivedScope.d.ts +18 -0
- package/dist/DerivedScope.d.ts.map +1 -0
- package/dist/DerivedScope.js +11 -0
- package/dist/DerivedScope.js.map +1 -0
- package/dist/ScopeRegistry.d.ts +41 -0
- package/dist/ScopeRegistry.d.ts.map +1 -0
- package/dist/ScopeRegistry.js +17 -0
- package/dist/ScopeRegistry.js.map +1 -0
- package/dist/asStore.d.ts +20 -0
- package/dist/asStore.d.ts.map +1 -0
- package/dist/asStore.js +23 -0
- package/dist/asStore.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +20 -0
- package/dist/index.js.map +1 -0
- package/dist/tapApi.d.ts +36 -0
- package/dist/tapApi.d.ts.map +1 -0
- package/dist/tapApi.js +52 -0
- package/dist/tapApi.js.map +1 -0
- package/dist/tapLookupResources.d.ts +44 -0
- package/dist/tapLookupResources.d.ts.map +1 -0
- package/dist/tapLookupResources.js +21 -0
- package/dist/tapLookupResources.js.map +1 -0
- package/dist/tapStoreList.d.ts +76 -0
- package/dist/tapStoreList.d.ts.map +1 -0
- package/dist/tapStoreList.js +46 -0
- package/dist/tapStoreList.js.map +1 -0
- package/dist/types.d.ts +86 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +1 -0
- package/dist/types.js.map +1 -0
- package/dist/useAssistantClient.d.ts +42 -0
- package/dist/useAssistantClient.d.ts.map +1 -0
- package/dist/useAssistantClient.js +153 -0
- package/dist/useAssistantClient.js.map +1 -0
- package/dist/useAssistantState.d.ts +18 -0
- package/dist/useAssistantState.d.ts.map +1 -0
- package/dist/useAssistantState.js +53 -0
- package/dist/useAssistantState.js.map +1 -0
- package/dist/utils/splitScopes.d.ts +24 -0
- package/dist/utils/splitScopes.d.ts.map +1 -0
- package/dist/utils/splitScopes.js +18 -0
- package/dist/utils/splitScopes.js.map +1 -0
- package/package.json +50 -0
- package/src/AssistantContext.tsx +64 -0
- package/src/DerivedScope.ts +21 -0
- package/src/ScopeRegistry.ts +58 -0
- package/src/asStore.ts +40 -0
- package/src/index.ts +13 -0
- package/src/tapApi.ts +91 -0
- package/src/tapLookupResources.ts +62 -0
- package/src/tapStoreList.ts +133 -0
- package/src/types.ts +120 -0
- package/src/useAssistantClient.tsx +250 -0
- package/src/useAssistantState.tsx +80 -0
- package/src/utils/splitScopes.ts +38 -0
package/dist/tapApi.js
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// src/tapApi.ts
|
|
2
|
+
import { tapEffect, tapMemo, tapRef } from "@assistant-ui/tap";
|
|
3
|
+
var ReadonlyApiHandler = class {
|
|
4
|
+
constructor(getApi) {
|
|
5
|
+
this.getApi = getApi;
|
|
6
|
+
}
|
|
7
|
+
get(_, prop) {
|
|
8
|
+
return this.getApi()[prop];
|
|
9
|
+
}
|
|
10
|
+
ownKeys() {
|
|
11
|
+
return Object.keys(this.getApi());
|
|
12
|
+
}
|
|
13
|
+
has(_, prop) {
|
|
14
|
+
return prop in this.getApi();
|
|
15
|
+
}
|
|
16
|
+
getOwnPropertyDescriptor(_, prop) {
|
|
17
|
+
return Object.getOwnPropertyDescriptor(this.getApi(), prop);
|
|
18
|
+
}
|
|
19
|
+
set() {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
defineProperty() {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
deleteProperty() {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
var tapApi = (api, options) => {
|
|
30
|
+
const ref = tapRef(api);
|
|
31
|
+
tapEffect(() => {
|
|
32
|
+
ref.current = api;
|
|
33
|
+
});
|
|
34
|
+
const apiProxy = tapMemo(
|
|
35
|
+
() => new Proxy({}, new ReadonlyApiHandler(() => ref.current)),
|
|
36
|
+
[]
|
|
37
|
+
);
|
|
38
|
+
const key = options?.key;
|
|
39
|
+
const state = api.getState();
|
|
40
|
+
return tapMemo(
|
|
41
|
+
() => ({
|
|
42
|
+
key,
|
|
43
|
+
state,
|
|
44
|
+
api: apiProxy
|
|
45
|
+
}),
|
|
46
|
+
[state, key]
|
|
47
|
+
);
|
|
48
|
+
};
|
|
49
|
+
export {
|
|
50
|
+
tapApi
|
|
51
|
+
};
|
|
52
|
+
//# sourceMappingURL=tapApi.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/tapApi.ts"],"sourcesContent":["import { tapEffect, tapMemo, tapRef } from \"@assistant-ui/tap\";\n\n/**\n * API object type\n */\nexport interface ApiObject {\n [key: string]: ((...args: any[]) => any) | ApiObject;\n}\n\n/**\n * Readonly API handler for creating stable proxies\n */\nclass ReadonlyApiHandler<TApi extends ApiObject> implements ProxyHandler<TApi> {\n constructor(private readonly getApi: () => TApi) {}\n\n get(_: unknown, prop: string | symbol) {\n return this.getApi()[prop as keyof TApi];\n }\n\n ownKeys(): ArrayLike<string | symbol> {\n return Object.keys(this.getApi() as object);\n }\n\n has(_: unknown, prop: string | symbol) {\n return prop in (this.getApi() as object);\n }\n\n getOwnPropertyDescriptor(_: unknown, prop: string | symbol) {\n return Object.getOwnPropertyDescriptor(this.getApi(), prop);\n }\n\n set() {\n return false;\n }\n defineProperty() {\n return false;\n }\n deleteProperty() {\n return false;\n }\n}\n\n/**\n * Wraps an API object to make it stable across renders while keeping getState reactive.\n * This is the recommended pattern for scope resources.\n *\n * @example\n * ```typescript\n * export const FooResource = resource(() => {\n * const [state, setState] = tapState({ bar: \"Hello\" });\n *\n * const updateBar = (newBar: string) => {\n * setState({ bar: newBar });\n * };\n *\n * return tapApi({\n * getState: () => state,\n * updateBar,\n * });\n * });\n * ```\n */\nexport const tapApi = <TApi extends ApiObject & { getState: () => any }>(\n api: TApi,\n options?: {\n key?: string | undefined;\n },\n) => {\n const ref = tapRef(api);\n tapEffect(() => {\n ref.current = api;\n });\n\n const apiProxy = tapMemo(\n () =>\n new Proxy<TApi>({} as TApi, new ReadonlyApiHandler(() => ref.current)),\n [],\n );\n\n const key = options?.key;\n const state = api.getState();\n\n return tapMemo(\n () => ({\n key,\n state,\n api: apiProxy,\n }),\n [state, key],\n );\n};\n"],"mappings":";AAAA,SAAS,WAAW,SAAS,cAAc;AAY3C,IAAM,qBAAN,MAA+E;AAAA,EAC7E,YAA6B,QAAoB;AAApB;AAAA,EAAqB;AAAA,EAElD,IAAI,GAAY,MAAuB;AACrC,WAAO,KAAK,OAAO,EAAE,IAAkB;AAAA,EACzC;AAAA,EAEA,UAAsC;AACpC,WAAO,OAAO,KAAK,KAAK,OAAO,CAAW;AAAA,EAC5C;AAAA,EAEA,IAAI,GAAY,MAAuB;AACrC,WAAO,QAAS,KAAK,OAAO;AAAA,EAC9B;AAAA,EAEA,yBAAyB,GAAY,MAAuB;AAC1D,WAAO,OAAO,yBAAyB,KAAK,OAAO,GAAG,IAAI;AAAA,EAC5D;AAAA,EAEA,MAAM;AACJ,WAAO;AAAA,EACT;AAAA,EACA,iBAAiB;AACf,WAAO;AAAA,EACT;AAAA,EACA,iBAAiB;AACf,WAAO;AAAA,EACT;AACF;AAsBO,IAAM,SAAS,CACpB,KACA,YAGG;AACH,QAAM,MAAM,OAAO,GAAG;AACtB,YAAU,MAAM;AACd,QAAI,UAAU;AAAA,EAChB,CAAC;AAED,QAAM,WAAW;AAAA,IACf,MACE,IAAI,MAAY,CAAC,GAAW,IAAI,mBAAmB,MAAM,IAAI,OAAO,CAAC;AAAA,IACvE,CAAC;AAAA,EACH;AAEA,QAAM,MAAM,SAAS;AACrB,QAAM,QAAQ,IAAI,SAAS;AAE3B,SAAO;AAAA,IACL,OAAO;AAAA,MACL;AAAA,MACA;AAAA,MACA,KAAK;AAAA,IACP;AAAA,IACA,CAAC,OAAO,GAAG;AAAA,EACb;AACF;","names":[]}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { ResourceElement } from "@assistant-ui/tap";
|
|
2
|
+
import { ApiObject } from "./tapApi";
|
|
3
|
+
/**
|
|
4
|
+
* Creates a lookup-based resource collection for managing lists of items.
|
|
5
|
+
* Returns both the combined state array and an API function to lookup specific items.
|
|
6
|
+
*
|
|
7
|
+
* @param elements - Array of resource elements, each returning { key, state, api }
|
|
8
|
+
* @returns Object with { state: TState[], api: (lookup) => TApi }
|
|
9
|
+
*
|
|
10
|
+
* The api function accepts { index: number } or { key: string } for lookups.
|
|
11
|
+
* Consumers can wrap it to rename the key field (e.g., to "id" or "toolCallId").
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```typescript
|
|
15
|
+
* const foos = tapLookupResources(
|
|
16
|
+
* items.map((item) => FooItem({ id: item.id }, { key: item.id }))
|
|
17
|
+
* );
|
|
18
|
+
*
|
|
19
|
+
* // Access state array
|
|
20
|
+
* const allStates = foos.state;
|
|
21
|
+
*
|
|
22
|
+
* // Wrap to rename key field to "id"
|
|
23
|
+
* const wrappedApi = (lookup: { index: number } | { id: string }) => {
|
|
24
|
+
* if ("id" in lookup) {
|
|
25
|
+
* return foos.api({ key: lookup.id });
|
|
26
|
+
* } else {
|
|
27
|
+
* return foos.api(lookup);
|
|
28
|
+
* }
|
|
29
|
+
* };
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
export declare const tapLookupResources: <TState, TApi extends ApiObject>(elements: ResourceElement<{
|
|
33
|
+
key: string | undefined;
|
|
34
|
+
state: TState;
|
|
35
|
+
api: TApi;
|
|
36
|
+
}>[]) => {
|
|
37
|
+
state: TState[];
|
|
38
|
+
api: (lookup: {
|
|
39
|
+
index: number;
|
|
40
|
+
} | {
|
|
41
|
+
key: string;
|
|
42
|
+
}) => TApi;
|
|
43
|
+
};
|
|
44
|
+
//# sourceMappingURL=tapLookupResources.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tapLookupResources.d.ts","sourceRoot":"","sources":["../src/tapLookupResources.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAgB,MAAM,mBAAmB,CAAC;AAClE,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAErC;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,eAAO,MAAM,kBAAkB,GAAI,MAAM,EAAE,IAAI,SAAS,SAAS,EAC/D,UAAU,eAAe,CAAC;IACxB,GAAG,EAAE,MAAM,GAAG,SAAS,CAAC;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,IAAI,CAAC;CACX,CAAC,EAAE,KACH;IACD,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,GAAG,EAAE,CAAC,MAAM,EAAE;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,GAAG;QAAE,GAAG,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;CAqB5D,CAAC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// src/tapLookupResources.ts
|
|
2
|
+
import { tapResources } from "@assistant-ui/tap";
|
|
3
|
+
var tapLookupResources = (elements) => {
|
|
4
|
+
const resources = tapResources(elements);
|
|
5
|
+
return {
|
|
6
|
+
state: resources.map((r) => r.state),
|
|
7
|
+
api: (lookup) => {
|
|
8
|
+
const value = "index" in lookup ? resources[lookup.index]?.api : resources.find((r) => r.key === lookup.key)?.api;
|
|
9
|
+
if (!value) {
|
|
10
|
+
throw new Error(
|
|
11
|
+
`tapLookupResources: Resource not found for lookup: ${JSON.stringify(lookup)}`
|
|
12
|
+
);
|
|
13
|
+
}
|
|
14
|
+
return value;
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
};
|
|
18
|
+
export {
|
|
19
|
+
tapLookupResources
|
|
20
|
+
};
|
|
21
|
+
//# sourceMappingURL=tapLookupResources.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/tapLookupResources.ts"],"sourcesContent":["import { ResourceElement, tapResources } from \"@assistant-ui/tap\";\nimport { ApiObject } from \"./tapApi\";\n\n/**\n * Creates a lookup-based resource collection for managing lists of items.\n * Returns both the combined state array and an API function to lookup specific items.\n *\n * @param elements - Array of resource elements, each returning { key, state, api }\n * @returns Object with { state: TState[], api: (lookup) => TApi }\n *\n * The api function accepts { index: number } or { key: string } for lookups.\n * Consumers can wrap it to rename the key field (e.g., to \"id\" or \"toolCallId\").\n *\n * @example\n * ```typescript\n * const foos = tapLookupResources(\n * items.map((item) => FooItem({ id: item.id }, { key: item.id }))\n * );\n *\n * // Access state array\n * const allStates = foos.state;\n *\n * // Wrap to rename key field to \"id\"\n * const wrappedApi = (lookup: { index: number } | { id: string }) => {\n * if (\"id\" in lookup) {\n * return foos.api({ key: lookup.id });\n * } else {\n * return foos.api(lookup);\n * }\n * };\n * ```\n */\nexport const tapLookupResources = <TState, TApi extends ApiObject>(\n elements: ResourceElement<{\n key: string | undefined;\n state: TState;\n api: TApi;\n }>[],\n): {\n state: TState[];\n api: (lookup: { index: number } | { key: string }) => TApi;\n} => {\n const resources = tapResources(elements);\n\n return {\n state: resources.map((r) => r.state),\n api: (lookup: { index: number } | { key: string }) => {\n const value =\n \"index\" in lookup\n ? resources[lookup.index]?.api\n : resources.find((r) => r.key === lookup.key)?.api;\n\n if (!value) {\n throw new Error(\n `tapLookupResources: Resource not found for lookup: ${JSON.stringify(lookup)}`,\n );\n }\n\n return value;\n },\n };\n};\n"],"mappings":";AAAA,SAA0B,oBAAoB;AAgCvC,IAAM,qBAAqB,CAChC,aAQG;AACH,QAAM,YAAY,aAAa,QAAQ;AAEvC,SAAO;AAAA,IACL,OAAO,UAAU,IAAI,CAAC,MAAM,EAAE,KAAK;AAAA,IACnC,KAAK,CAAC,WAAgD;AACpD,YAAM,QACJ,WAAW,SACP,UAAU,OAAO,KAAK,GAAG,MACzB,UAAU,KAAK,CAAC,MAAM,EAAE,QAAQ,OAAO,GAAG,GAAG;AAEnD,UAAI,CAAC,OAAO;AACV,cAAM,IAAI;AAAA,UACR,sDAAsD,KAAK,UAAU,MAAM,CAAC;AAAA,QAC9E;AAAA,MACF;AAEA,aAAO;AAAA,IACT;AAAA,EACF;AACF;","names":[]}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import type { ContravariantResource } from "@assistant-ui/tap";
|
|
2
|
+
import { ApiObject } from "./tapApi";
|
|
3
|
+
/**
|
|
4
|
+
* Resource props that will be passed to each item resource
|
|
5
|
+
*/
|
|
6
|
+
export type TapStoreListResourceProps<TProps> = {
|
|
7
|
+
initialValue: TProps;
|
|
8
|
+
remove: () => void;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Configuration for tapStoreList hook
|
|
12
|
+
*/
|
|
13
|
+
export type TapStoreListConfig<TProps, TState, TApi extends ApiObject> = {
|
|
14
|
+
/**
|
|
15
|
+
* Initial values for the list items
|
|
16
|
+
*/
|
|
17
|
+
initialValues: TProps[];
|
|
18
|
+
/**
|
|
19
|
+
* Resource function that creates an element for each item
|
|
20
|
+
* Should return a ResourceElement with { key, state, api }
|
|
21
|
+
*
|
|
22
|
+
* The resource will receive { initialValue, remove } as props.
|
|
23
|
+
*/
|
|
24
|
+
resource: ContravariantResource<{
|
|
25
|
+
key: string | undefined;
|
|
26
|
+
state: TState;
|
|
27
|
+
api: TApi;
|
|
28
|
+
}, TapStoreListResourceProps<TProps>>;
|
|
29
|
+
/**
|
|
30
|
+
* Optional ID generator function for new items
|
|
31
|
+
* If not provided, items must include an ID when added
|
|
32
|
+
*/
|
|
33
|
+
idGenerator?: () => string;
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* Creates a stateful list with add functionality, rendering each item via the provided resource.
|
|
37
|
+
* Returns state array, api lookup function, and add method.
|
|
38
|
+
*
|
|
39
|
+
* @param config - Configuration object with initialValues, resource, and optional idGenerator
|
|
40
|
+
* @returns Object with { state: TState[], api: (lookup) => TApi, add: (id?) => void }
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* ```typescript
|
|
44
|
+
* const todoList = tapStoreList({
|
|
45
|
+
* initialValues: [
|
|
46
|
+
* { id: "1", text: "First todo" },
|
|
47
|
+
* { id: "2", text: "Second todo" }
|
|
48
|
+
* ],
|
|
49
|
+
* resource: (props) => TodoItemResource(props, { key: props.id }),
|
|
50
|
+
* idGenerator: () => `todo-${Date.now()}`
|
|
51
|
+
* });
|
|
52
|
+
*
|
|
53
|
+
* // Access state array
|
|
54
|
+
* const allTodos = todoList.state;
|
|
55
|
+
*
|
|
56
|
+
* // Lookup specific item
|
|
57
|
+
* const firstTodo = todoList.api({ index: 0 });
|
|
58
|
+
* const specificTodo = todoList.api({ key: "1" });
|
|
59
|
+
*
|
|
60
|
+
* // Add new item
|
|
61
|
+
* todoList.add(); // Uses idGenerator
|
|
62
|
+
* todoList.add("custom-id"); // Uses provided id
|
|
63
|
+
* ```
|
|
64
|
+
*/
|
|
65
|
+
export declare const tapStoreList: <TProps extends {
|
|
66
|
+
id: string;
|
|
67
|
+
}, TState, TApi extends ApiObject>(config: TapStoreListConfig<TProps, TState, TApi>) => {
|
|
68
|
+
state: TState[];
|
|
69
|
+
api: (lookup: {
|
|
70
|
+
index: number;
|
|
71
|
+
} | {
|
|
72
|
+
id: string;
|
|
73
|
+
}) => TApi;
|
|
74
|
+
add: (id?: string) => void;
|
|
75
|
+
};
|
|
76
|
+
//# sourceMappingURL=tapStoreList.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tapStoreList.d.ts","sourceRoot":"","sources":["../src/tapStoreList.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,mBAAmB,CAAC;AAE/D,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAErC;;GAEG;AACH,MAAM,MAAM,yBAAyB,CAAC,MAAM,IAAI;IAC9C,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,IAAI,CAAC;CACpB,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,SAAS,SAAS,IAAI;IACvE;;OAEG;IACH,aAAa,EAAE,MAAM,EAAE,CAAC;IAMxB;;;;;OAKG;IACH,QAAQ,EAAE,qBAAqB,CAC7B;QACE,GAAG,EAAE,MAAM,GAAG,SAAS,CAAC;QACxB,KAAK,EAAE,MAAM,CAAC;QACd,GAAG,EAAE,IAAI,CAAC;KACX,EACD,yBAAyB,CAAC,MAAM,CAAC,CAClC,CAAC;IACF;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,MAAM,CAAC;CAC5B,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,eAAO,MAAM,YAAY,GACvB,MAAM,SAAS;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,EAC7B,MAAM,EACN,IAAI,SAAS,SAAS,EAEtB,QAAQ,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,CAAC,KAC/C;IACD,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,GAAG,EAAE,CAAC,MAAM,EAAE;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,GAAG;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;IAC1D,GAAG,EAAE,CAAC,EAAE,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;CA8C5B,CAAC"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// src/tapStoreList.ts
|
|
2
|
+
import { tapState } from "@assistant-ui/tap";
|
|
3
|
+
import { tapLookupResources } from "./tapLookupResources.js";
|
|
4
|
+
var tapStoreList = (config) => {
|
|
5
|
+
const { initialValues, resource: Resource, idGenerator } = config;
|
|
6
|
+
const [items, setItems] = tapState(initialValues);
|
|
7
|
+
const lookup = tapLookupResources(
|
|
8
|
+
items.map(
|
|
9
|
+
(item) => Resource(
|
|
10
|
+
{
|
|
11
|
+
initialValue: item,
|
|
12
|
+
remove: () => {
|
|
13
|
+
setItems(items.filter((i) => i !== item));
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
key: item.id
|
|
18
|
+
}
|
|
19
|
+
)
|
|
20
|
+
)
|
|
21
|
+
);
|
|
22
|
+
const add = (id) => {
|
|
23
|
+
const newId = id ?? idGenerator?.();
|
|
24
|
+
if (!newId) {
|
|
25
|
+
throw new Error(
|
|
26
|
+
"tapStoreList: Either provide an id to add() or configure an idGenerator"
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
const newItem = { id: newId };
|
|
30
|
+
setItems([...items, newItem]);
|
|
31
|
+
};
|
|
32
|
+
return {
|
|
33
|
+
state: lookup.state,
|
|
34
|
+
api: (query) => {
|
|
35
|
+
if ("index" in query) {
|
|
36
|
+
return lookup.api({ index: query.index });
|
|
37
|
+
}
|
|
38
|
+
return lookup.api({ key: query.id });
|
|
39
|
+
},
|
|
40
|
+
add
|
|
41
|
+
};
|
|
42
|
+
};
|
|
43
|
+
export {
|
|
44
|
+
tapStoreList
|
|
45
|
+
};
|
|
46
|
+
//# sourceMappingURL=tapStoreList.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/tapStoreList.ts"],"sourcesContent":["import { tapState } from \"@assistant-ui/tap\";\nimport type { ContravariantResource } from \"@assistant-ui/tap\";\nimport { tapLookupResources } from \"./tapLookupResources\";\nimport { ApiObject } from \"./tapApi\";\n\n/**\n * Resource props that will be passed to each item resource\n */\nexport type TapStoreListResourceProps<TProps> = {\n initialValue: TProps;\n remove: () => void;\n};\n\n/**\n * Configuration for tapStoreList hook\n */\nexport type TapStoreListConfig<TProps, TState, TApi extends ApiObject> = {\n /**\n * Initial values for the list items\n */\n initialValues: TProps[];\n\n // TODO we can't use Resource type here because of contravariance\n // I think we need a special type in tap that correctly handles the contravariance\n // or change the behavior of the Resource type\n\n /**\n * Resource function that creates an element for each item\n * Should return a ResourceElement with { key, state, api }\n *\n * The resource will receive { initialValue, remove } as props.\n */\n resource: ContravariantResource<\n {\n key: string | undefined;\n state: TState;\n api: TApi;\n },\n TapStoreListResourceProps<TProps>\n >;\n /**\n * Optional ID generator function for new items\n * If not provided, items must include an ID when added\n */\n idGenerator?: () => string;\n};\n\n/**\n * Creates a stateful list with add functionality, rendering each item via the provided resource.\n * Returns state array, api lookup function, and add method.\n *\n * @param config - Configuration object with initialValues, resource, and optional idGenerator\n * @returns Object with { state: TState[], api: (lookup) => TApi, add: (id?) => void }\n *\n * @example\n * ```typescript\n * const todoList = tapStoreList({\n * initialValues: [\n * { id: \"1\", text: \"First todo\" },\n * { id: \"2\", text: \"Second todo\" }\n * ],\n * resource: (props) => TodoItemResource(props, { key: props.id }),\n * idGenerator: () => `todo-${Date.now()}`\n * });\n *\n * // Access state array\n * const allTodos = todoList.state;\n *\n * // Lookup specific item\n * const firstTodo = todoList.api({ index: 0 });\n * const specificTodo = todoList.api({ key: \"1\" });\n *\n * // Add new item\n * todoList.add(); // Uses idGenerator\n * todoList.add(\"custom-id\"); // Uses provided id\n * ```\n */\nexport const tapStoreList = <\n TProps extends { id: string },\n TState,\n TApi extends ApiObject,\n>(\n config: TapStoreListConfig<TProps, TState, TApi>,\n): {\n state: TState[];\n api: (lookup: { index: number } | { id: string }) => TApi;\n add: (id?: string) => void;\n} => {\n const { initialValues, resource: Resource, idGenerator } = config;\n\n const [items, setItems] = tapState<TProps[]>(initialValues);\n\n const lookup = tapLookupResources(\n items.map((item) =>\n Resource(\n {\n initialValue: item,\n remove: () => {\n setItems(items.filter((i) => i !== item));\n },\n },\n {\n key: item.id,\n },\n ),\n ),\n );\n\n const add = (id?: string) => {\n const newId = id ?? idGenerator?.();\n if (!newId) {\n throw new Error(\n \"tapStoreList: Either provide an id to add() or configure an idGenerator\",\n );\n }\n\n // Create a new item with the generated/provided id\n // This assumes TProps has an 'id' field - users will need to ensure their props type supports this\n const newItem = { id: newId } as TProps;\n setItems([...items, newItem]);\n };\n\n return {\n state: lookup.state,\n api: (query: { index: number } | { id: string }) => {\n if (\"index\" in query) {\n return lookup.api({ index: query.index });\n }\n return lookup.api({ key: query.id });\n },\n add,\n };\n};\n"],"mappings":";AAAA,SAAS,gBAAgB;AAEzB,SAAS,0BAA0B;AA2E5B,IAAM,eAAe,CAK1B,WAKG;AACH,QAAM,EAAE,eAAe,UAAU,UAAU,YAAY,IAAI;AAE3D,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAmB,aAAa;AAE1D,QAAM,SAAS;AAAA,IACb,MAAM;AAAA,MAAI,CAAC,SACT;AAAA,QACE;AAAA,UACE,cAAc;AAAA,UACd,QAAQ,MAAM;AACZ,qBAAS,MAAM,OAAO,CAAC,MAAM,MAAM,IAAI,CAAC;AAAA,UAC1C;AAAA,QACF;AAAA,QACA;AAAA,UACE,KAAK,KAAK;AAAA,QACZ;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,QAAM,MAAM,CAAC,OAAgB;AAC3B,UAAM,QAAQ,MAAM,cAAc;AAClC,QAAI,CAAC,OAAO;AACV,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AAIA,UAAM,UAAU,EAAE,IAAI,MAAM;AAC5B,aAAS,CAAC,GAAG,OAAO,OAAO,CAAC;AAAA,EAC9B;AAEA,SAAO;AAAA,IACL,OAAO,OAAO;AAAA,IACd,KAAK,CAAC,UAA8C;AAClD,UAAI,WAAW,OAAO;AACpB,eAAO,OAAO,IAAI,EAAE,OAAO,MAAM,MAAM,CAAC;AAAA,MAC1C;AACA,aAAO,OAAO,IAAI,EAAE,KAAK,MAAM,GAAG,CAAC;AAAA,IACrC;AAAA,IACA;AAAA,EACF;AACF;","names":[]}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import type { ResourceElement } from "@assistant-ui/tap";
|
|
2
|
+
/**
|
|
3
|
+
* Module augmentation interface for assistant-ui store type extensions.
|
|
4
|
+
*
|
|
5
|
+
* @example
|
|
6
|
+
* ```typescript
|
|
7
|
+
* declare module "@assistant-ui/store" {
|
|
8
|
+
* interface AssistantScopeRegistry {
|
|
9
|
+
* foo: {
|
|
10
|
+
* value: { getState: () => { bar: string }; updateBar: (bar: string) => void };
|
|
11
|
+
* source: "root";
|
|
12
|
+
* query: Record<string, never>;
|
|
13
|
+
* };
|
|
14
|
+
* }
|
|
15
|
+
* }
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
export interface AssistantScopeRegistry {
|
|
19
|
+
}
|
|
20
|
+
export type AssistantScopes = keyof AssistantScopeRegistry extends never ? Record<"ERROR: No scopes were defined", ScopeDefinition> : {
|
|
21
|
+
[K in keyof AssistantScopeRegistry]: AssistantScopeRegistry[K];
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Helper type to extract the value type from a scope definition
|
|
25
|
+
*/
|
|
26
|
+
export type ScopeValue<T extends ScopeDefinition> = T["value"];
|
|
27
|
+
/**
|
|
28
|
+
* Helper type to extract the source type from a scope definition
|
|
29
|
+
*/
|
|
30
|
+
export type ScopeSource<T extends ScopeDefinition> = T["source"];
|
|
31
|
+
/**
|
|
32
|
+
* Helper type to extract the query type from a scope definition
|
|
33
|
+
*/
|
|
34
|
+
export type ScopeQuery<T extends ScopeDefinition> = T["query"];
|
|
35
|
+
/**
|
|
36
|
+
* Type for a scope field - a function that returns the current API value,
|
|
37
|
+
* with source and query metadata attached
|
|
38
|
+
*/
|
|
39
|
+
export type ScopeField<T extends ScopeDefinition> = (() => ScopeValue<T>) & ({
|
|
40
|
+
source: ScopeSource<T>;
|
|
41
|
+
query: ScopeQuery<T>;
|
|
42
|
+
} | {
|
|
43
|
+
source: null;
|
|
44
|
+
query: null;
|
|
45
|
+
});
|
|
46
|
+
/**
|
|
47
|
+
* Props passed to a derived scope resource element
|
|
48
|
+
*/
|
|
49
|
+
export type DerivedScopeProps<T extends ScopeDefinition> = {
|
|
50
|
+
get: (parent: AssistantClient) => ScopeValue<T>;
|
|
51
|
+
source: ScopeSource<T>;
|
|
52
|
+
query: ScopeQuery<T>;
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* Input type for scope definitions - ResourceElement that returns the API value
|
|
56
|
+
* Can optionally include source/query metadata via DerivedScope
|
|
57
|
+
*/
|
|
58
|
+
export type ScopeInput<T extends ScopeDefinition> = ResourceElement<{
|
|
59
|
+
api: ScopeValue<T>;
|
|
60
|
+
}>;
|
|
61
|
+
/**
|
|
62
|
+
* Map of scope names to their input definitions
|
|
63
|
+
*/
|
|
64
|
+
export type ScopesInput = {
|
|
65
|
+
[K in keyof AssistantScopes]?: ScopeInput<AssistantScopes[K]>;
|
|
66
|
+
};
|
|
67
|
+
/**
|
|
68
|
+
* Unsubscribe function type
|
|
69
|
+
*/
|
|
70
|
+
export type Unsubscribe = () => void;
|
|
71
|
+
/**
|
|
72
|
+
* State type extracted from all scopes
|
|
73
|
+
*/
|
|
74
|
+
export type AssistantState = {
|
|
75
|
+
[K in keyof AssistantScopes]: ReturnType<AssistantScopes[K]["value"]["getState"]>;
|
|
76
|
+
};
|
|
77
|
+
/**
|
|
78
|
+
* The assistant client type with all registered scopes
|
|
79
|
+
*/
|
|
80
|
+
export type AssistantClient = {
|
|
81
|
+
[K in keyof AssistantScopes]: ScopeField<AssistantScopes[K]>;
|
|
82
|
+
} & {
|
|
83
|
+
subscribe(listener: () => void): Unsubscribe;
|
|
84
|
+
flushSync(): void;
|
|
85
|
+
};
|
|
86
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAmBzD;;;;;;;;;;;;;;;GAeG;AAEH,MAAM,WAAW,sBAAsB;CAAG;AAE1C,MAAM,MAAM,eAAe,GAAG,MAAM,sBAAsB,SAAS,KAAK,GACpE,MAAM,CAAC,+BAA+B,EAAE,eAAe,CAAC,GACxD;KAAG,CAAC,IAAI,MAAM,sBAAsB,GAAG,sBAAsB,CAAC,CAAC,CAAC;CAAE,CAAC;AAEvE;;GAEG;AACH,MAAM,MAAM,UAAU,CAAC,CAAC,SAAS,eAAe,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC;AAE/D;;GAEG;AACH,MAAM,MAAM,WAAW,CAAC,CAAC,SAAS,eAAe,IAAI,CAAC,CAAC,QAAQ,CAAC,CAAC;AAEjE;;GAEG;AACH,MAAM,MAAM,UAAU,CAAC,CAAC,SAAS,eAAe,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC;AAE/D;;;GAGG;AACH,MAAM,MAAM,UAAU,CAAC,CAAC,SAAS,eAAe,IAAI,CAAC,MAAM,UAAU,CAAC,CAAC,CAAC,CAAC,GACvE,CACI;IACE,MAAM,EAAE,WAAW,CAAC,CAAC,CAAC,CAAC;IACvB,KAAK,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC;CACtB,GACD;IACE,MAAM,EAAE,IAAI,CAAC;IACb,KAAK,EAAE,IAAI,CAAC;CACb,CACJ,CAAC;AAEJ;;GAEG;AACH,MAAM,MAAM,iBAAiB,CAAC,CAAC,SAAS,eAAe,IAAI;IACzD,GAAG,EAAE,CAAC,MAAM,EAAE,eAAe,KAAK,UAAU,CAAC,CAAC,CAAC,CAAC;IAChD,MAAM,EAAE,WAAW,CAAC,CAAC,CAAC,CAAC;IACvB,KAAK,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC;CACtB,CAAC;AAEF;;;GAGG;AACH,MAAM,MAAM,UAAU,CAAC,CAAC,SAAS,eAAe,IAAI,eAAe,CAAC;IAClE,GAAG,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC;CACpB,CAAC,CAAC;AAEH;;GAEG;AACH,MAAM,MAAM,WAAW,GAAG;KACvB,CAAC,IAAI,MAAM,eAAe,CAAC,CAAC,EAAE,UAAU,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC;CAC9D,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,WAAW,GAAG,MAAM,IAAI,CAAC;AAErC;;GAEG;AACH,MAAM,MAAM,cAAc,GAAG;KAC1B,CAAC,IAAI,MAAM,eAAe,GAAG,UAAU,CACtC,eAAe,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,UAAU,CAAC,CACxC;CACF,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,eAAe,GAAG;KAC3B,CAAC,IAAI,MAAM,eAAe,GAAG,UAAU,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC;CAC7D,GAAG;IACF,SAAS,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,WAAW,CAAC;IAC7C,SAAS,IAAI,IAAI,CAAC;CACnB,CAAC"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { AssistantClient, AssistantScopes, ScopesInput, ScopeField } from "./types";
|
|
2
|
+
/**
|
|
3
|
+
* Hook to mount and access root scopes
|
|
4
|
+
*/
|
|
5
|
+
export declare const useRootScopes: (rootScopes: ScopesInput) => {
|
|
6
|
+
scopes: {};
|
|
7
|
+
subscribe?: never;
|
|
8
|
+
flushSync?: never;
|
|
9
|
+
} | {
|
|
10
|
+
scopes: { [K in "ERROR: No scopes were defined"]: ScopeField<AssistantScopes[K]>; };
|
|
11
|
+
subscribe: (callback: () => void) => () => void;
|
|
12
|
+
flushSync: () => void;
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Hook to mount and access derived scopes
|
|
16
|
+
*/
|
|
17
|
+
export declare const useDerivedScopes: (derivedScopes: ScopesInput, parentClient: AssistantClient) => {
|
|
18
|
+
"ERROR: No scopes were defined"?: ScopeField<import("./types").ScopeDefinition<any, any, any>>;
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Hook to access or extend the AssistantClient
|
|
22
|
+
*
|
|
23
|
+
* @example Without config - returns the client from context:
|
|
24
|
+
* ```typescript
|
|
25
|
+
* const client = useAssistantClient();
|
|
26
|
+
* const fooState = client.foo.getState();
|
|
27
|
+
* ```
|
|
28
|
+
*
|
|
29
|
+
* @example With config - creates a new client with additional scopes:
|
|
30
|
+
* ```typescript
|
|
31
|
+
* const client = useAssistantClient({
|
|
32
|
+
* message: DerivedScope({
|
|
33
|
+
* source: "thread",
|
|
34
|
+
* query: { type: "index", index: 0 },
|
|
35
|
+
* get: () => messageApi,
|
|
36
|
+
* }),
|
|
37
|
+
* });
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
export declare function useAssistantClient(): AssistantClient;
|
|
41
|
+
export declare function useAssistantClient(scopes: ScopesInput): AssistantClient;
|
|
42
|
+
//# sourceMappingURL=useAssistantClient.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useAssistantClient.d.ts","sourceRoot":"","sources":["../src/useAssistantClient.tsx"],"names":[],"mappings":"AAUA,OAAO,KAAK,EACV,eAAe,EACf,eAAe,EACf,WAAW,EACX,UAAU,EAGX,MAAM,SAAS,CAAC;AA6FjB;;GAEG;AACH,eAAO,MAAM,aAAa,GAAI,YAAY,WAAW;;;;;YA1B1C,GACF,CAAC,mCAAuB,GAAG,UAAU,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC,GAC3D;0BACqB,MAAM,IAAI;;CAyBrC,CAAC;AAqEF;;GAEG;AACH,eAAO,MAAM,gBAAgB,GAC3B,eAAe,WAAW,EAC1B,cAAc,eAAe;;CAK9B,CAAC;AAyBF;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,kBAAkB,IAAI,eAAe,CAAC;AACtD,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,WAAW,GAAG,eAAe,CAAC"}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
// src/useAssistantClient.tsx
|
|
2
|
+
import { useMemo } from "react";
|
|
3
|
+
import { useResource } from "@assistant-ui/tap/react";
|
|
4
|
+
import {
|
|
5
|
+
resource,
|
|
6
|
+
tapMemo,
|
|
7
|
+
tapResource,
|
|
8
|
+
tapResources,
|
|
9
|
+
tapEffectEvent
|
|
10
|
+
} from "@assistant-ui/tap";
|
|
11
|
+
import { asStore } from "./asStore.js";
|
|
12
|
+
import { useAssistantContextValue } from "./AssistantContext.js";
|
|
13
|
+
import { splitScopes } from "./utils/splitScopes.js";
|
|
14
|
+
var RootScopeResource = resource(
|
|
15
|
+
({
|
|
16
|
+
scopeName,
|
|
17
|
+
element
|
|
18
|
+
}) => {
|
|
19
|
+
const store = tapResource(asStore(element));
|
|
20
|
+
return tapMemo(() => {
|
|
21
|
+
const scopeFunction = (() => store.getState().api);
|
|
22
|
+
scopeFunction.source = "root";
|
|
23
|
+
scopeFunction.query = {};
|
|
24
|
+
return [
|
|
25
|
+
scopeName,
|
|
26
|
+
{
|
|
27
|
+
scopeFunction,
|
|
28
|
+
subscribe: store.subscribe,
|
|
29
|
+
flushSync: store.flushSync
|
|
30
|
+
}
|
|
31
|
+
];
|
|
32
|
+
}, [scopeName, store]);
|
|
33
|
+
}
|
|
34
|
+
);
|
|
35
|
+
var RootScopesResource = resource((scopes) => {
|
|
36
|
+
const resultEntries = tapResources(
|
|
37
|
+
Object.entries(scopes).map(
|
|
38
|
+
([scopeName, element]) => RootScopeResource(
|
|
39
|
+
{
|
|
40
|
+
scopeName,
|
|
41
|
+
element
|
|
42
|
+
},
|
|
43
|
+
{ key: scopeName }
|
|
44
|
+
)
|
|
45
|
+
)
|
|
46
|
+
);
|
|
47
|
+
return tapMemo(() => {
|
|
48
|
+
if (resultEntries.length === 0) {
|
|
49
|
+
return {
|
|
50
|
+
scopes: {}
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
return {
|
|
54
|
+
scopes: Object.fromEntries(
|
|
55
|
+
resultEntries.map(([scopeName, { scopeFunction }]) => [
|
|
56
|
+
scopeName,
|
|
57
|
+
scopeFunction
|
|
58
|
+
])
|
|
59
|
+
),
|
|
60
|
+
subscribe: (callback) => {
|
|
61
|
+
const unsubscribes = resultEntries.map(([, { subscribe }]) => {
|
|
62
|
+
return subscribe(() => {
|
|
63
|
+
console.log("Callback called for");
|
|
64
|
+
callback();
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
return () => {
|
|
68
|
+
unsubscribes.forEach((unsubscribe) => unsubscribe());
|
|
69
|
+
};
|
|
70
|
+
},
|
|
71
|
+
flushSync: () => {
|
|
72
|
+
resultEntries.forEach(([, { flushSync }]) => {
|
|
73
|
+
flushSync();
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
}, [...resultEntries]);
|
|
78
|
+
});
|
|
79
|
+
var useRootScopes = (rootScopes) => {
|
|
80
|
+
return useResource(RootScopesResource(rootScopes));
|
|
81
|
+
};
|
|
82
|
+
var DerivedScopeResource = resource(
|
|
83
|
+
({
|
|
84
|
+
scopeName,
|
|
85
|
+
element,
|
|
86
|
+
parentClient
|
|
87
|
+
}) => {
|
|
88
|
+
const get = tapEffectEvent(element.props.get);
|
|
89
|
+
const source = element.props.source;
|
|
90
|
+
const query = element.props.query;
|
|
91
|
+
return tapMemo(() => {
|
|
92
|
+
const scopeFunction = (() => get(parentClient));
|
|
93
|
+
scopeFunction.source = source;
|
|
94
|
+
scopeFunction.query = query;
|
|
95
|
+
return [scopeName, scopeFunction];
|
|
96
|
+
}, [scopeName, get, source, JSON.stringify(query), parentClient]);
|
|
97
|
+
}
|
|
98
|
+
);
|
|
99
|
+
var DerivedScopesResource = resource(
|
|
100
|
+
({
|
|
101
|
+
scopes,
|
|
102
|
+
parentClient
|
|
103
|
+
}) => {
|
|
104
|
+
const resultEntries = tapResources(
|
|
105
|
+
Object.entries(scopes).map(
|
|
106
|
+
([scopeName, element]) => DerivedScopeResource(
|
|
107
|
+
{
|
|
108
|
+
scopeName,
|
|
109
|
+
element,
|
|
110
|
+
parentClient
|
|
111
|
+
},
|
|
112
|
+
{ key: scopeName }
|
|
113
|
+
)
|
|
114
|
+
)
|
|
115
|
+
);
|
|
116
|
+
return tapMemo(() => {
|
|
117
|
+
return Object.fromEntries(resultEntries);
|
|
118
|
+
}, [...resultEntries]);
|
|
119
|
+
}
|
|
120
|
+
);
|
|
121
|
+
var useDerivedScopes = (derivedScopes, parentClient) => {
|
|
122
|
+
return useResource(
|
|
123
|
+
DerivedScopesResource({ scopes: derivedScopes, parentClient })
|
|
124
|
+
);
|
|
125
|
+
};
|
|
126
|
+
var useExtendedAssistantClientImpl = (scopes) => {
|
|
127
|
+
const baseClient = useAssistantContextValue();
|
|
128
|
+
const { rootScopes, derivedScopes } = splitScopes(scopes);
|
|
129
|
+
const rootFields = useRootScopes(rootScopes);
|
|
130
|
+
const derivedFields = useDerivedScopes(derivedScopes, baseClient);
|
|
131
|
+
return useMemo(() => {
|
|
132
|
+
return {
|
|
133
|
+
...baseClient,
|
|
134
|
+
...rootFields.scopes,
|
|
135
|
+
...derivedFields,
|
|
136
|
+
subscribe: rootFields.subscribe ?? baseClient.subscribe,
|
|
137
|
+
flushSync: rootFields.flushSync ?? baseClient.flushSync
|
|
138
|
+
};
|
|
139
|
+
}, [baseClient, rootFields, derivedFields]);
|
|
140
|
+
};
|
|
141
|
+
function useAssistantClient(scopes) {
|
|
142
|
+
if (scopes) {
|
|
143
|
+
return useExtendedAssistantClientImpl(scopes);
|
|
144
|
+
} else {
|
|
145
|
+
return useAssistantContextValue();
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
export {
|
|
149
|
+
useAssistantClient,
|
|
150
|
+
useDerivedScopes,
|
|
151
|
+
useRootScopes
|
|
152
|
+
};
|
|
153
|
+
//# sourceMappingURL=useAssistantClient.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/useAssistantClient.tsx"],"sourcesContent":["import { useMemo } from \"react\";\nimport { useResource } from \"@assistant-ui/tap/react\";\nimport {\n resource,\n tapMemo,\n tapResource,\n tapResources,\n tapEffectEvent,\n ResourceElement,\n} from \"@assistant-ui/tap\";\nimport type {\n AssistantClient,\n AssistantScopes,\n ScopesInput,\n ScopeField,\n ScopeInput,\n DerivedScopeProps,\n} from \"./types\";\nimport { asStore } from \"./asStore\";\nimport { useAssistantContextValue } from \"./AssistantContext\";\nimport { splitScopes } from \"./utils/splitScopes\";\n\n/**\n * Resource for a single root scope\n * Returns a tuple of [scopeName, {scopeFunction, subscribe, flushSync}]\n */\nconst RootScopeResource = resource(\n <K extends keyof AssistantScopes>({\n scopeName,\n element,\n }: {\n scopeName: K;\n element: ScopeInput<AssistantScopes[K]>;\n }) => {\n const store = tapResource(asStore(element));\n\n return tapMemo(() => {\n const scopeFunction = (() => store.getState().api) as ScopeField<\n AssistantScopes[K]\n >;\n scopeFunction.source = \"root\";\n scopeFunction.query = {} as AssistantScopes[K][\"query\"];\n\n return [\n scopeName,\n {\n scopeFunction,\n subscribe: store.subscribe,\n flushSync: store.flushSync,\n },\n ] as const;\n }, [scopeName, store]);\n },\n);\n\n/**\n * Resource for all root scopes\n * Mounts each root scope and returns an object mapping scope names to their stores\n */\nconst RootScopesResource = resource((scopes: ScopesInput) => {\n const resultEntries = tapResources(\n Object.entries(scopes).map(([scopeName, element]) =>\n RootScopeResource(\n {\n scopeName: scopeName as keyof AssistantScopes,\n element: element as ScopeInput<\n AssistantScopes[keyof AssistantScopes]\n >,\n },\n { key: scopeName },\n ),\n ),\n );\n\n return tapMemo(() => {\n if (resultEntries.length === 0) {\n return {\n scopes: {},\n };\n }\n\n return {\n scopes: Object.fromEntries(\n resultEntries.map(([scopeName, { scopeFunction }]) => [\n scopeName,\n scopeFunction,\n ]),\n ) as {\n [K in keyof typeof scopes]: ScopeField<AssistantScopes[K]>;\n },\n subscribe: (callback: () => void) => {\n const unsubscribes = resultEntries.map(([, { subscribe }]) => {\n return subscribe(() => {\n console.log(\"Callback called for\");\n callback();\n });\n });\n return () => {\n unsubscribes.forEach((unsubscribe) => unsubscribe());\n };\n },\n flushSync: () => {\n resultEntries.forEach(([, { flushSync }]) => {\n flushSync();\n });\n },\n };\n }, [...resultEntries]);\n});\n\n/**\n * Hook to mount and access root scopes\n */\nexport const useRootScopes = (rootScopes: ScopesInput) => {\n return useResource(RootScopesResource(rootScopes));\n};\n\n/**\n * Resource for a single derived scope\n * Returns a tuple of [scopeName, scopeFunction] where scopeFunction has source and query\n */\nconst DerivedScopeResource = resource(\n <K extends keyof AssistantScopes>({\n scopeName,\n element,\n parentClient,\n }: {\n scopeName: K;\n element: ResourceElement<\n AssistantScopes[K],\n DerivedScopeProps<AssistantScopes[K]>\n >;\n parentClient: AssistantClient;\n }) => {\n const get = tapEffectEvent(element.props.get);\n const source = element.props.source;\n const query = element.props.query;\n return tapMemo(() => {\n const scopeFunction = (() => get(parentClient)) as ScopeField<\n AssistantScopes[K]\n >;\n scopeFunction.source = source;\n scopeFunction.query = query;\n\n return [scopeName, scopeFunction] as const;\n }, [scopeName, get, source, JSON.stringify(query), parentClient]);\n },\n);\n\n/**\n * Resource for all derived scopes\n * Builds stable scope functions with source and query metadata\n */\nconst DerivedScopesResource = resource(\n ({\n scopes,\n parentClient,\n }: {\n scopes: ScopesInput;\n parentClient: AssistantClient;\n }) => {\n const resultEntries = tapResources(\n Object.entries(scopes).map(([scopeName, element]) =>\n DerivedScopeResource(\n {\n scopeName: scopeName as keyof AssistantScopes,\n element: element as ScopeInput<\n AssistantScopes[keyof AssistantScopes]\n >,\n parentClient,\n },\n { key: scopeName },\n ),\n ),\n );\n\n return tapMemo(() => {\n return Object.fromEntries(resultEntries) as {\n [K in keyof typeof scopes]: ScopeField<AssistantScopes[K]>;\n };\n }, [...resultEntries]);\n },\n);\n\n/**\n * Hook to mount and access derived scopes\n */\nexport const useDerivedScopes = (\n derivedScopes: ScopesInput,\n parentClient: AssistantClient,\n) => {\n return useResource(\n DerivedScopesResource({ scopes: derivedScopes, parentClient }),\n );\n};\n\nconst useExtendedAssistantClientImpl = (\n scopes: ScopesInput,\n): AssistantClient => {\n const baseClient = useAssistantContextValue();\n const { rootScopes, derivedScopes } = splitScopes(scopes);\n\n // Mount the scopes to keep them alive\n const rootFields = useRootScopes(rootScopes);\n const derivedFields = useDerivedScopes(derivedScopes, baseClient);\n\n return useMemo(() => {\n // Merge base client with extended client\n // If baseClient is the default proxy, spreading it will be a no-op\n return {\n ...baseClient,\n ...rootFields.scopes,\n ...derivedFields,\n subscribe: rootFields.subscribe ?? baseClient.subscribe,\n flushSync: rootFields.flushSync ?? baseClient.flushSync,\n } as AssistantClient;\n }, [baseClient, rootFields, derivedFields]);\n};\n\n/**\n * Hook to access or extend the AssistantClient\n *\n * @example Without config - returns the client from context:\n * ```typescript\n * const client = useAssistantClient();\n * const fooState = client.foo.getState();\n * ```\n *\n * @example With config - creates a new client with additional scopes:\n * ```typescript\n * const client = useAssistantClient({\n * message: DerivedScope({\n * source: \"thread\",\n * query: { type: \"index\", index: 0 },\n * get: () => messageApi,\n * }),\n * });\n * ```\n */\nexport function useAssistantClient(): AssistantClient;\nexport function useAssistantClient(scopes: ScopesInput): AssistantClient;\nexport function useAssistantClient(scopes?: ScopesInput): AssistantClient {\n if (scopes) {\n // eslint-disable-next-line react-hooks/rules-of-hooks\n return useExtendedAssistantClientImpl(scopes);\n } else {\n // eslint-disable-next-line react-hooks/rules-of-hooks\n return useAssistantContextValue();\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { AssistantState } from "./types";
|
|
2
|
+
/**
|
|
3
|
+
* Hook to access a slice of the assistant state with automatic subscription
|
|
4
|
+
*
|
|
5
|
+
* @param selector - Function to select a slice of the state
|
|
6
|
+
* @returns The selected state slice
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```typescript
|
|
10
|
+
* const client = useAssistantClient({
|
|
11
|
+
* foo: RootScope({ ... }),
|
|
12
|
+
* });
|
|
13
|
+
*
|
|
14
|
+
* const bar = useAssistantState((state) => state.foo.bar);
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
export declare const useAssistantState: <T>(selector: (state: AssistantState) => T) => T;
|
|
18
|
+
//# sourceMappingURL=useAssistantState.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useAssistantState.d.ts","sourceRoot":"","sources":["../src/useAssistantState.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAmB,cAAc,EAAE,MAAM,SAAS,CAAC;AAsC/D;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,iBAAiB,GAAI,CAAC,EACjC,UAAU,CAAC,KAAK,EAAE,cAAc,KAAK,CAAC,KACrC,CAuBF,CAAC"}
|