@assistant-ui/store
Advanced tools
| // @vitest-environment jsdom | ||
| import type { ReactNode } from "react"; | ||
| import { useEffect, useLayoutEffect, useState } from "react"; | ||
| import { act, cleanup, render } from "@testing-library/react"; | ||
| import { afterEach, describe, expect, it } from "vitest"; | ||
| import { flushTapSync, resource } from "@assistant-ui/tap"; | ||
| import { AuiProvider } from "../utils/react-assistant-context"; | ||
| import { useAui } from "../useAui"; | ||
| import { useAuiState } from "../useAuiState"; | ||
| const makeTestClient = (log: string[]) => { | ||
| // Runs inside the tap host; "react" imports route to tap's dispatcher. | ||
| const useTestClient = () => { | ||
| const [count, setCount] = useState(0); | ||
| useEffect(() => { | ||
| log.push("tap effect"); | ||
| return () => { | ||
| log.push("tap cleanup"); | ||
| }; | ||
| }, []); | ||
| return { | ||
| getState: () => ({ count }), | ||
| setCount: (n: number) => setCount(n), | ||
| }; | ||
| }; | ||
| return resource(useTestClient); | ||
| }; | ||
| const Provider = ({ | ||
| client, | ||
| children, | ||
| }: { | ||
| client: ReturnType<ReturnType<typeof makeTestClient>>; | ||
| children: ReactNode; | ||
| }) => { | ||
| const aui = useAui({ thread: client } as unknown as useAui.Props); | ||
| return <AuiProvider value={aui}>{children}</AuiProvider>; | ||
| }; | ||
| describe("useAui tap host", () => { | ||
| afterEach(() => { | ||
| cleanup(); | ||
| }); | ||
| it("commits client effects passively, ahead of consumer effects", () => { | ||
| const log: string[] = []; | ||
| const TestClient = makeTestClient(log); | ||
| function Consumer() { | ||
| useLayoutEffect(() => { | ||
| log.push("consumer layout"); | ||
| }, []); | ||
| useEffect(() => { | ||
| log.push("consumer effect"); | ||
| }, []); | ||
| return null; | ||
| } | ||
| render( | ||
| <Provider client={TestClient()}> | ||
| <Consumer /> | ||
| </Provider>, | ||
| ); | ||
| // "consumer layout" first: the commit no longer blocks paint. | ||
| // "tap effect" before "consumer effect": AuiProvider mounts the host's | ||
| // commit ahead of its children's effects, with no opt-in by the child. | ||
| expect(log).toEqual(["consumer layout", "tap effect", "consumer effect"]); | ||
| }); | ||
| it("commits via the host's own fallback without an AuiProvider", () => { | ||
| const log: string[] = []; | ||
| const TestClient = makeTestClient(log); | ||
| const client = TestClient(); | ||
| function HostOnly() { | ||
| useAui({ thread: client } as unknown as useAui.Props); | ||
| return null; | ||
| } | ||
| render(<HostOnly />); | ||
| expect(log).toEqual(["tap effect"]); | ||
| }); | ||
| it("updates flow through to useAuiState consumers", () => { | ||
| const log: string[] = []; | ||
| const TestClient = makeTestClient(log); | ||
| let api!: { setCount: (n: number) => void }; | ||
| let observed!: number; | ||
| function Consumer() { | ||
| const aui = useAui(); | ||
| api = (aui as any).thread(); | ||
| observed = useAuiState((s) => (s as any).thread.count); | ||
| return null; | ||
| } | ||
| render( | ||
| <Provider client={TestClient()}> | ||
| <Consumer /> | ||
| </Provider>, | ||
| ); | ||
| expect(observed).toBe(0); | ||
| act(() => flushTapSync(() => api.setCount(7))); | ||
| expect(observed).toBe(7); | ||
| }); | ||
| it("cleans up client effects when the host unmounts", () => { | ||
| const log: string[] = []; | ||
| const TestClient = makeTestClient(log); | ||
| const { unmount } = render( | ||
| <Provider client={TestClient()}>{null}</Provider>, | ||
| ); | ||
| unmount(); | ||
| expect(log).toEqual(["tap effect", "tap cleanup"]); | ||
| }); | ||
| }); |
| import { AssistantClient, ClientElement, ClientNames } from "./types/client.js"; | ||
| import { DerivedElement } from "./Derived.js"; | ||
| import { ResourceElement } from "@assistant-ui/tap"; | ||
@@ -8,7 +7,8 @@ //#region src/attachTransformScopes.d.ts | ||
| type TransformScopesFn = (scopes: ScopesConfig, parent: AssistantClient) => void; | ||
| declare function attachTransformScopes<T extends (...args: any[]) => ResourceElement<any>>(resource: T, transform: TransformScopesFn): void; | ||
| declare function forwardTransformScopes<T extends (...args: any[]) => ResourceElement<any>, S extends (...args: any[]) => ResourceElement<any>>(target: T, source: S): void; | ||
| declare function getTransformScopes<T extends (...args: any[]) => ResourceElement<any>>(resource: T): TransformScopesFn | undefined; | ||
| type Hook = (...args: any[]) => any; | ||
| declare function attachTransformScopes(hook: Hook, transform: TransformScopesFn): void; | ||
| declare function forwardTransformScopes(target: Hook, source: Hook): void; | ||
| declare function getTransformScopes(hook: Hook): TransformScopesFn | undefined; | ||
| //#endregion | ||
| export { ScopesConfig, attachTransformScopes, forwardTransformScopes, getTransformScopes }; | ||
| //# sourceMappingURL=attachTransformScopes.d.ts.map |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"attachTransformScopes.d.ts","names":[],"sources":["../src/attachTransformScopes.ts"],"mappings":";;;;;KAUY,YAAA,WACJ,WAAA,IAAe,aAAA,CAAc,CAAA,IAAK,cAAA,CAAe,CAAA;AAAA,KAGpD,iBAAA,IACH,MAAA,EAAQ,YAAA,EACR,MAAA,EAAQ,eAAe;AAAA,iBAOT,qBAAA,eACA,IAAA,YAAgB,eAAA,OAC9B,QAAA,EAAU,CAAA,EAAG,SAAA,EAAW,iBAAA;AAAA,iBAQV,sBAAA,eACA,IAAA,YAAgB,eAAA,qBAChB,IAAA,YAAgB,eAAA,OAC9B,MAAA,EAAQ,CAAA,EAAG,MAAA,EAAQ,CAAA;AAAA,iBAgBL,kBAAA,eACA,IAAA,YAAgB,eAAA,OAC9B,QAAA,EAAU,CAAA,GAAI,iBAAA"} | ||
| {"version":3,"file":"attachTransformScopes.d.ts","names":[],"sources":["../src/attachTransformScopes.ts"],"mappings":";;;;KASY,YAAA,WACJ,WAAA,IAAe,aAAA,CAAc,CAAA,IAAK,cAAA,CAAe,CAAA;AAAA,KAGpD,iBAAA,IACH,MAAA,EAAQ,YAAA,EACR,MAAA,EAAQ,eAAe;AAAA,KAKpB,IAAA,OAAW,IAAI;AAAA,iBAKJ,qBAAA,CACd,IAAA,EAAM,IAAA,EACN,SAAA,EAAW,iBAAiB;AAAA,iBASd,sBAAA,CAAuB,MAAA,EAAQ,IAAA,EAAM,MAAA,EAAQ,IAAI;AAAA,iBAgBjD,kBAAA,CAAmB,IAAA,EAAM,IAAA,GAAO,iBAAiB"} |
| //#region src/attachTransformScopes.ts | ||
| const TRANSFORM_SCOPES = Symbol("assistant-ui.transform-scopes"); | ||
| function attachTransformScopes(resource, transform) { | ||
| const r = resource; | ||
| if (r[TRANSFORM_SCOPES]) throw new Error("transformScopes is already attached to this resource"); | ||
| r[TRANSFORM_SCOPES] = transform; | ||
| function attachTransformScopes(hook, transform) { | ||
| const h = hook; | ||
| if (h[TRANSFORM_SCOPES]) throw new Error("transformScopes is already attached to this resource"); | ||
| h[TRANSFORM_SCOPES] = transform; | ||
| } | ||
@@ -11,12 +11,12 @@ function forwardTransformScopes(target, source) { | ||
| if (!sourceTransform) return; | ||
| const r = target; | ||
| const existingTransform = r[TRANSFORM_SCOPES]; | ||
| if (existingTransform) r[TRANSFORM_SCOPES] = (scopes, parent) => { | ||
| const t = target; | ||
| const existingTransform = t[TRANSFORM_SCOPES]; | ||
| if (existingTransform) t[TRANSFORM_SCOPES] = (scopes, parent) => { | ||
| sourceTransform(scopes, parent); | ||
| existingTransform(scopes, parent); | ||
| }; | ||
| else r[TRANSFORM_SCOPES] = sourceTransform; | ||
| else t[TRANSFORM_SCOPES] = sourceTransform; | ||
| } | ||
| function getTransformScopes(resource) { | ||
| return resource[TRANSFORM_SCOPES]; | ||
| function getTransformScopes(hook) { | ||
| return hook[TRANSFORM_SCOPES]; | ||
| } | ||
@@ -23,0 +23,0 @@ //#endregion |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"attachTransformScopes.js","names":[],"sources":["../src/attachTransformScopes.ts"],"sourcesContent":["import { type ResourceElement } from \"@assistant-ui/tap\";\nimport type {\n AssistantClient,\n ClientElement,\n ClientNames,\n} from \"./types/client\";\nimport type { DerivedElement } from \"./Derived\";\n\nconst TRANSFORM_SCOPES = Symbol(\"assistant-ui.transform-scopes\");\n\nexport type ScopesConfig = {\n [K in ClientNames]?: ClientElement<K> | DerivedElement<K>;\n};\n\ntype TransformScopesFn = (\n scopes: ScopesConfig,\n parent: AssistantClient,\n) => void;\n\ntype ResourceWithTransformScopes = {\n [TRANSFORM_SCOPES]?: TransformScopesFn;\n};\n\nexport function attachTransformScopes<\n T extends (...args: any[]) => ResourceElement<any>,\n>(resource: T, transform: TransformScopesFn): void {\n const r = resource as T & ResourceWithTransformScopes;\n if (r[TRANSFORM_SCOPES]) {\n throw new Error(\"transformScopes is already attached to this resource\");\n }\n r[TRANSFORM_SCOPES] = transform;\n}\n\nexport function forwardTransformScopes<\n T extends (...args: any[]) => ResourceElement<any>,\n S extends (...args: any[]) => ResourceElement<any>,\n>(target: T, source: S): void {\n const sourceTransform = getTransformScopes(source);\n if (!sourceTransform) return;\n\n const r = target as T & ResourceWithTransformScopes;\n const existingTransform = r[TRANSFORM_SCOPES];\n if (existingTransform) {\n r[TRANSFORM_SCOPES] = (scopes, parent) => {\n sourceTransform(scopes, parent);\n existingTransform(scopes, parent);\n };\n } else {\n r[TRANSFORM_SCOPES] = sourceTransform;\n }\n}\n\nexport function getTransformScopes<\n T extends (...args: any[]) => ResourceElement<any>,\n>(resource: T): TransformScopesFn | undefined {\n return (resource as T & ResourceWithTransformScopes)[TRANSFORM_SCOPES];\n}\n"],"mappings":";AAQA,MAAM,mBAAmB,OAAO,+BAA+B;AAe/D,SAAgB,sBAEd,UAAa,WAAoC;CACjD,MAAM,IAAI;CACV,IAAI,EAAE,mBACJ,MAAM,IAAI,MAAM,sDAAsD;CAExE,EAAE,oBAAoB;AACxB;AAEA,SAAgB,uBAGd,QAAW,QAAiB;CAC5B,MAAM,kBAAkB,mBAAmB,MAAM;CACjD,IAAI,CAAC,iBAAiB;CAEtB,MAAM,IAAI;CACV,MAAM,oBAAoB,EAAE;CAC5B,IAAI,mBACF,EAAE,qBAAqB,QAAQ,WAAW;EACxC,gBAAgB,QAAQ,MAAM;EAC9B,kBAAkB,QAAQ,MAAM;CAClC;MAEA,EAAE,oBAAoB;AAE1B;AAEA,SAAgB,mBAEd,UAA4C;CAC5C,OAAQ,SAA6C;AACvD"} | ||
| {"version":3,"file":"attachTransformScopes.js","names":[],"sources":["../src/attachTransformScopes.ts"],"sourcesContent":["import type {\n AssistantClient,\n ClientElement,\n ClientNames,\n} from \"./types/client\";\nimport type { DerivedElement } from \"./Derived\";\n\nconst TRANSFORM_SCOPES = Symbol(\"assistant-ui.transform-scopes\");\n\nexport type ScopesConfig = {\n [K in ClientNames]?: ClientElement<K> | DerivedElement<K>;\n};\n\ntype TransformScopesFn = (\n scopes: ScopesConfig,\n parent: AssistantClient,\n) => void;\n\n// Transforms are keyed by the resource's underlying hook (the function passed to\n// `resource()`), since that is the identity a `ResourceElement` carries.\ntype Hook = (...args: any[]) => any;\ntype HookWithTransformScopes = Hook & {\n [TRANSFORM_SCOPES]?: TransformScopesFn;\n};\n\nexport function attachTransformScopes(\n hook: Hook,\n transform: TransformScopesFn,\n): void {\n const h = hook as HookWithTransformScopes;\n if (h[TRANSFORM_SCOPES]) {\n throw new Error(\"transformScopes is already attached to this resource\");\n }\n h[TRANSFORM_SCOPES] = transform;\n}\n\nexport function forwardTransformScopes(target: Hook, source: Hook): void {\n const sourceTransform = getTransformScopes(source);\n if (!sourceTransform) return;\n\n const t = target as HookWithTransformScopes;\n const existingTransform = t[TRANSFORM_SCOPES];\n if (existingTransform) {\n t[TRANSFORM_SCOPES] = (scopes, parent) => {\n sourceTransform(scopes, parent);\n existingTransform(scopes, parent);\n };\n } else {\n t[TRANSFORM_SCOPES] = sourceTransform;\n }\n}\n\nexport function getTransformScopes(hook: Hook): TransformScopesFn | undefined {\n return (hook as HookWithTransformScopes)[TRANSFORM_SCOPES];\n}\n"],"mappings":";AAOA,MAAM,mBAAmB,OAAO,+BAA+B;AAkB/D,SAAgB,sBACd,MACA,WACM;CACN,MAAM,IAAI;CACV,IAAI,EAAE,mBACJ,MAAM,IAAI,MAAM,sDAAsD;CAExE,EAAE,oBAAoB;AACxB;AAEA,SAAgB,uBAAuB,QAAc,QAAoB;CACvE,MAAM,kBAAkB,mBAAmB,MAAM;CACjD,IAAI,CAAC,iBAAiB;CAEtB,MAAM,IAAI;CACV,MAAM,oBAAoB,EAAE;CAC5B,IAAI,mBACF,EAAE,qBAAqB,QAAQ,WAAW;EACxC,gBAAgB,QAAQ,MAAM;EAC9B,kBAAkB,QAAQ,MAAM;CAClC;MAEA,EAAE,oBAAoB;AAE1B;AAEA,SAAgB,mBAAmB,MAA2C;CAC5E,OAAQ,KAAiC;AAC3C"} |
@@ -24,4 +24,5 @@ import { AssistantClient, AssistantClientAccessor, ClientMeta, ClientNames } from "./types/client.js"; | ||
| */ | ||
| declare const Derived: <K extends ClientNames>(props: Derived.Props<K>) => ResourceElement<null, Derived.Props<K>>; | ||
| type DerivedElement<K extends ClientNames> = ResourceElement<null, Derived.Props<K>>; | ||
| declare const useDerived: <K extends ClientNames>(_config: Derived.Props<K>) => null; | ||
| declare const Derived: <K extends ClientNames>(_config: Derived.Props<K>) => ResourceElement<null, [_config: Derived.Props<K>]>; | ||
| type DerivedElement<K extends ClientNames> = ResourceElement<null, [Derived.Props<K>]>; | ||
| declare namespace Derived { | ||
@@ -36,3 +37,3 @@ /** | ||
| //#endregion | ||
| export { Derived, DerivedElement }; | ||
| export { Derived, DerivedElement, useDerived }; | ||
| //# sourceMappingURL=Derived.d.ts.map |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"Derived.d.ts","names":[],"sources":["../src/Derived.ts"],"mappings":";;;;;;AA2BA;;;;;;;;;;;;;;;;;cAAa,OAAA,aAA8C,WAAA,EAAW,KAAA,EAAA,OAAA,CAAA,KAAA,CAAA,CAAA,MAAA,eAAA,OAAA,OAAA,CAAA,KAAA,CAAA,CAAA;AAAA,KAM1D,cAAA,WAAyB,WAAA,IAAe,eAAA,OAElD,OAAA,CAAQ,KAAA,CAAM,CAAA;AAAA,kBAGC,OAAA;EAXqD;;AAAA;EAAA,KAexD,KAAA,WAAgB,WAAA;IAC1B,GAAA,GAAM,MAAA,EAAQ,eAAA,KAAoB,UAAA,CAAW,uBAAA,CAAwB,CAAA;EAAA,IACnE,UAAA,CAAW,CAAA;AAAA"} | ||
| {"version":3,"file":"Derived.d.ts","names":[],"sources":["../src/Derived.ts"],"mappings":";;;;;;AA6BA;;;;;;;;;;;;;;;AAC2B;AAK3B;cANa,UAAA,aAAwB,WAAA,EACnC,OAAA,EAAS,OAAA,CAAQ,KAAA,CAAM,CAAA;AAAA,cAKZ,OAAA,aANwB,WAAA,EAAW,OAAA,EAAA,OAAA,CAAA,KAAA,CAAA,CAAA,MAAA,eAAA,QAAA,OAAA,EAAA,OAAA,CAAA,KAAA,CAAA,CAAA;AAAA,KAQpC,cAAA,WAAyB,WAAA,IAAe,eAAA,QAEjD,OAAA,CAAQ,KAAA,CAAM,CAAA;AAAA,kBAGA,OAAA;EAb+B;;;EAAA,KAiBlC,KAAA,WAAgB,WAAA;IAC1B,GAAA,GAAM,MAAA,EAAQ,eAAA,KAAoB,UAAA,CAAW,uBAAA,CAAwB,CAAA;EAAA,IACnE,UAAA,CAAW,CAAA;AAAA"} |
+4
-3
@@ -22,8 +22,9 @@ import { resource } from "@assistant-ui/tap"; | ||
| */ | ||
| const Derived = resource(function Derived(_config) { | ||
| const useDerived = (_config) => { | ||
| return null; | ||
| }); | ||
| }; | ||
| const Derived = resource(useDerived); | ||
| //#endregion | ||
| export { Derived }; | ||
| export { Derived, useDerived }; | ||
| //# sourceMappingURL=Derived.js.map |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"Derived.js","names":[],"sources":["../src/Derived.ts"],"sourcesContent":["import { resource, type ResourceElement } from \"@assistant-ui/tap\";\nimport type {\n AssistantClient,\n ClientNames,\n AssistantClientAccessor,\n ClientMeta,\n} from \"./types/client\";\n\n/**\n * Creates a derived client field that references a client from a parent scope.\n * The get callback always calls the most recent version (useEffectEvent pattern).\n *\n * IMPORTANT: The `get` callback must return a client that was created via\n * `useClientResource` (or `useClientLookup`/`useClientList` which use it internally).\n * This is required for event scoping to work correctly.\n *\n * @example\n * ```typescript\n * const aui = useAui({\n * message: Derived({\n * source: \"thread\",\n * query: { index: 0 },\n * get: (aui) => aui.thread().message({ index: 0 }),\n * }),\n * });\n * ```\n */\nexport const Derived = resource(function Derived<K extends ClientNames>(\n _config: Derived.Props<K>,\n): null {\n return null;\n});\n\nexport type DerivedElement<K extends ClientNames> = ResourceElement<\n null,\n Derived.Props<K>\n>;\n\nexport namespace Derived {\n /**\n * Props passed to a derived client resource element.\n */\n export type Props<K extends ClientNames> = {\n get: (client: AssistantClient) => ReturnType<AssistantClientAccessor<K>>;\n } & ClientMeta<K>;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AA2BA,MAAa,UAAU,SAAS,SAAS,QACvC,SACM;CACN,OAAO;AACT,CAAC"} | ||
| {"version":3,"file":"Derived.js","names":[],"sources":["../src/Derived.ts"],"sourcesContent":["import { resource, type ResourceElement } from \"@assistant-ui/tap\";\nimport type {\n AssistantClient,\n ClientNames,\n AssistantClientAccessor,\n ClientMeta,\n} from \"./types/client\";\n\n/**\n * Creates a derived client field that references a client from a parent scope.\n * The get callback always calls the most recent version (useEffectEvent pattern).\n *\n * IMPORTANT: The `get` callback must return a client that was created via\n * `useClientResource` (or `useClientLookup`/`useClientList` which use it internally).\n * This is required for event scoping to work correctly.\n *\n * @example\n * ```typescript\n * const aui = useAui({\n * message: Derived({\n * source: \"thread\",\n * query: { index: 0 },\n * get: (aui) => aui.thread().message({ index: 0 }),\n * }),\n * });\n * ```\n */\n// Exported so consumers (e.g. splitClients) can identify a derived element by its\n// hook: a `Derived(...)` element carries `hook === useDerived`.\nexport const useDerived = <K extends ClientNames>(\n _config: Derived.Props<K>,\n): null => {\n return null;\n};\n\nexport const Derived = resource(useDerived);\n\nexport type DerivedElement<K extends ClientNames> = ResourceElement<\n null,\n [Derived.Props<K>]\n>;\n\nexport namespace Derived {\n /**\n * Props passed to a derived client resource element.\n */\n export type Props<K extends ClientNames> = {\n get: (client: AssistantClient) => ReturnType<AssistantClientAccessor<K>>;\n } & ClientMeta<K>;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AA6BA,MAAa,cACX,YACS;CACT,OAAO;AACT;AAEA,MAAa,UAAU,SAAS,UAAU"} |
@@ -77,3 +77,3 @@ import { AssistantEventCallback, AssistantEventName, AssistantEventSelector } from "./events.js"; | ||
| * ```typescript | ||
| * const FooResource = resource(function FooResource(): ClientResourceOutput<"foo"> { | ||
| * const useFoo = (): ClientResourceOutput<"foo"> => { | ||
| * const [state, setState] = useState({ bar: "hello" }); | ||
@@ -84,3 +84,5 @@ * return { | ||
| * }; | ||
| * }); | ||
| * }; | ||
| * | ||
| * const FooResource = resource(useFoo); | ||
| * ``` | ||
@@ -87,0 +89,0 @@ */ |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"client.d.ts","names":[],"sources":["../../src/types/client.ts"],"mappings":";;;;;;AAUA;UAAiB,aAAA;EAAA,CACd,GAAA,wBAA2B,IAAI;AAAA;AAAA,KAG7B,cAAA;EAAmB,MAAA,EAAQ,WAAA;EAAa,KAAA,EAAO,MAAM;AAAA;;;;;;;AAAA;KAS9C,YAAA,kBACO,aAAA,GAAgB,aAAA,gBACnB,cAAA,0BACE,MAAA;EAEhB,OAAA,EAAS,QAAA;EACT,IAAA,GAAO,KAAA;EACP,MAAA,GAAS,OAAA;AAAA;;;;;;;;;;;;;;;;;;;;;;AAAO;AAgClB;;;;AAA8B;AAAG;;UAAhB,aAAA;AAAA,KAEZ,gBAAA,WAA2B,WAAA,IAAe,MAAA,IAC1C,CAAA;AAAA,KAIA,WAAA;EACH,OAAA,EAAS,MAAA,CAAO,CAAA,QAAS,CAAA;EACzB,IAAA;IAAQ,MAAA,EAAQ,WAAA;IAAa,KAAA,EAAO,MAAA,CAAO,CAAA,EAAG,CAAA;EAAA;EAC9C,MAAA,EAAQ,MAAA,IAAU,CAAA,KAAM,CAAA;AAAA;AAAA,KAGrB,cAAA,iBAA+B,aAAA,IAAiB,aAAA,CAAc,CAAA;EACjE,OAAA,EAAS,aAAA;AAAA,yBAEc,aAAA,CAAc,CAAA,IACjC,aAAA,CAAc,CAAA,kBAAmB,cAAA,0BACR,aAAA,CAAc,CAAA,IACnC,aAAA,CAAc,CAAA,oBAAqB,gBAAA,CAAiB,CAAA,IAClD,aAAA,CAAc,CAAA,IACd,WAAA,WAAsB,CAAA,uCACxB,aAAA,CAAc,CAAA,IAChB,WAAA,WAAsB,CAAA,4DACD,aAAA,CAAc,CAAA,IACnC,aAAA,CAAc,CAAA,oBAAqB,gBAAA,CAAiB,CAAA,IAClD,aAAA,CAAc,CAAA,IACd,WAAA,WAAsB,CAAA,uCACxB,aAAA,CAAc,CAAA,IAClB,WAAA,WAAsB,CAAA;AAAA,KAErB,aAAA,SAAsB,aAAA;EAErB,gCAAA,EAAkC,WAAA;AAAA,kBAEtB,aAAA,GAAgB,cAAA,CAAe,CAAA;;;;;;;;;;;;;;;KAgBrC,YAAA,WAAuB,WAAA,IAAe,aAAA,CAAc,CAAA,eAC9D,aAAA;AAAA,KAEU,WAAA,SAAoB,aAAa,mBAAmB,CAAA;AAAA,KAEpD,YAAA,WAAuB,WAAA,2BACV,aAAA,CAAc,CAAA,IACjC,aAAA,CAAc,CAAA,oBAAqB,gBAAA,CAAiB,CAAA,IAClD,aAAA,CAAc,CAAA;AAAA,KAIV,UAAA,WAAqB,WAAA,yBACV,aAAA,CAAc,CAAA,IAC/B,IAAA,CACE,aAAA,CAAc,CAAA,kBAAmB,cAAA,GAC7B,aAAA,CAAc,CAAA;AAAA,KAMd,aAAA,WAAwB,WAAA,IAAe,eAAA,CACjD,YAAA,CAAa,CAAA;;;;KAMH,WAAA;;;AAtEe;KA2Ef,cAAA,WACJ,WAAA,GAAc,aAAA,CAAc,CAAA;EAChC,QAAA;AAAA,IAEE,CAAA;;;;;KAQM,uBAAA,WAAkC,WAAA,WACrC,aAAA,CAAc,CAAA,iBAEf,UAAA,CAAW,CAAA;EACT,MAAA;EAAgB,KAAA,EAAO,MAAA;AAAA;EACvB,MAAA;EAAc,KAAA;AAAA;EACd,IAAA,EAAM,CAAA;AAAA;;;;KAKJ,eAAA,WACJ,WAAA,GAAc,uBAAA,CAAwB,CAAA;EAE5C,SAAA,CAAU,QAAA,eAAuB,WAAA;EACjC,EAAA,gBAAkB,kBAAA,EAChB,QAAA,EAAU,sBAAA,CAAuB,MAAA,GACjC,QAAA,EAAU,sBAAA,CAAuB,MAAA,IAChC,WAAA;AAAA"} | ||
| {"version":3,"file":"client.d.ts","names":[],"sources":["../../src/types/client.ts"],"mappings":";;;;;;AAUA;UAAiB,aAAA;EAAA,CACd,GAAA,wBAA2B,IAAI;AAAA;AAAA,KAG7B,cAAA;EAAmB,MAAA,EAAQ,WAAA;EAAa,KAAA,EAAO,MAAM;AAAA;;;;;;;AAAA;KAS9C,YAAA,kBACO,aAAA,GAAgB,aAAA,gBACnB,cAAA,0BACE,MAAA;EAEhB,OAAA,EAAS,QAAA;EACT,IAAA,GAAO,KAAA;EACP,MAAA,GAAS,OAAA;AAAA;;;;;;;;;;;;;;;;;;;;;;AAAO;AAgClB;;;;AAA8B;AAAG;;UAAhB,aAAA;AAAA,KAEZ,gBAAA,WAA2B,WAAA,IAAe,MAAA,IAC1C,CAAA;AAAA,KAIA,WAAA;EACH,OAAA,EAAS,MAAA,CAAO,CAAA,QAAS,CAAA;EACzB,IAAA;IAAQ,MAAA,EAAQ,WAAA;IAAa,KAAA,EAAO,MAAA,CAAO,CAAA,EAAG,CAAA;EAAA;EAC9C,MAAA,EAAQ,MAAA,IAAU,CAAA,KAAM,CAAA;AAAA;AAAA,KAGrB,cAAA,iBAA+B,aAAA,IAAiB,aAAA,CAAc,CAAA;EACjE,OAAA,EAAS,aAAA;AAAA,yBAEc,aAAA,CAAc,CAAA,IACjC,aAAA,CAAc,CAAA,kBAAmB,cAAA,0BACR,aAAA,CAAc,CAAA,IACnC,aAAA,CAAc,CAAA,oBAAqB,gBAAA,CAAiB,CAAA,IAClD,aAAA,CAAc,CAAA,IACd,WAAA,WAAsB,CAAA,uCACxB,aAAA,CAAc,CAAA,IAChB,WAAA,WAAsB,CAAA,4DACD,aAAA,CAAc,CAAA,IACnC,aAAA,CAAc,CAAA,oBAAqB,gBAAA,CAAiB,CAAA,IAClD,aAAA,CAAc,CAAA,IACd,WAAA,WAAsB,CAAA,uCACxB,aAAA,CAAc,CAAA,IAClB,WAAA,WAAsB,CAAA;AAAA,KAErB,aAAA,SAAsB,aAAA;EAErB,gCAAA,EAAkC,WAAA;AAAA,kBAEtB,aAAA,GAAgB,cAAA,CAAe,CAAA;;;;;;;;;;;;;;;;;KAkBrC,YAAA,WAAuB,WAAA,IAAe,aAAA,CAAc,CAAA,eAC9D,aAAA;AAAA,KAEU,WAAA,SAAoB,aAAa,mBAAmB,CAAA;AAAA,KAEpD,YAAA,WAAuB,WAAA,2BACV,aAAA,CAAc,CAAA,IACjC,aAAA,CAAc,CAAA,oBAAqB,gBAAA,CAAiB,CAAA,IAClD,aAAA,CAAc,CAAA;AAAA,KAIV,UAAA,WAAqB,WAAA,yBACV,aAAA,CAAc,CAAA,IAC/B,IAAA,CACE,aAAA,CAAc,CAAA,kBAAmB,cAAA,GAC7B,aAAA,CAAc,CAAA;AAAA,KAMd,aAAA,WAAwB,WAAA,IAAe,eAAA,CACjD,YAAA,CAAa,CAAA;;;;KAMH,WAAA;AAxEe;AAAA;;AAAA,KA6Ef,cAAA,WACJ,WAAA,GAAc,aAAA,CAAc,CAAA;EAChC,QAAA;AAAA,IAEE,CAAA;;;;;KAQM,uBAAA,WAAkC,WAAA,WACrC,aAAA,CAAc,CAAA,iBAEf,UAAA,CAAW,CAAA;EACT,MAAA;EAAgB,KAAA,EAAO,MAAA;AAAA;EACvB,MAAA;EAAc,KAAA;AAAA;EACd,IAAA,EAAM,CAAA;AAAA;;;;KAKJ,eAAA,WACJ,WAAA,GAAc,uBAAA,CAAwB,CAAA;EAE5C,SAAA,CAAU,QAAA,eAAuB,WAAA;EACjC,EAAA,gBAAkB,kBAAA,EAChB,QAAA,EAAU,sBAAA,CAAuB,MAAA,GACjC,QAAA,EAAU,sBAAA,CAAuB,MAAA,IAChC,WAAA;AAAA"} |
+1
-11
@@ -5,12 +5,2 @@ import { AssistantClient, ClientElement, ClientNames } from "./types/client.js"; | ||
| //#region src/useAui.d.ts | ||
| /** | ||
| * Resource that creates an extended AssistantClient. | ||
| */ | ||
| declare const AssistantClientResource: (props: { | ||
| parent: AssistantClient; | ||
| clients: useAui.Props; | ||
| }) => import("@assistant-ui/tap").ResourceElement<AssistantClient, { | ||
| parent: AssistantClient; | ||
| clients: useAui.Props; | ||
| }>; | ||
| declare namespace useAui { | ||
@@ -84,3 +74,3 @@ type Props = { [K in ClientNames]?: ClientElement<K> | DerivedElement<K> }; | ||
| //#endregion | ||
| export { AssistantClientResource, useAui }; | ||
| export { useAui }; | ||
| //# sourceMappingURL=useAui.d.ts.map |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"useAui.d.ts","names":[],"sources":["../src/useAui.ts"],"mappings":";;;;;;AA6SA;cAAa,uBAAA,GAAuB,KAAA;UAKxB,eAAA;WACC,MAAA,CAAO,KAAA;AAAA;UADR,eAAA;WACC,MAAA,CAAO,KAAA;AAAA;AAAA,kBAuDH,MAAA;EAAA,KACH,KAAA,WACJ,WAAA,IAAe,aAAA,CAAc,CAAA,IAAK,cAAA,CAAe,CAAA;AAAA;;;;;;;;;;;;AAzDlC;AAuDzB;;;;;;;;;;;;;;;;;;AAE4D;AAuC5D;;;;iBAAgB,MAAA,IAAU,eAAe;AAsBzC;;;;;;;;;AAA8D;AAI9D;;;;;;;;;;;AAJA,iBAAgB,MAAA,CAAO,OAAA,EAAS,MAAA,CAAO,KAAA,GAAQ,eAAe;;;;iBAI9C,MAAA,CACd,OAAA,EAAS,MAAA,CAAO,KAAA,EAChB,MAAA;EAAU,MAAA,SAAe,eAAA;AAAA,IACxB,eAAA"} | ||
| {"version":3,"file":"useAui.d.ts","names":[],"sources":["../src/useAui.ts"],"mappings":";;;;kBA2XiB,MAAA;EAAA,KACH,KAAA,WACJ,WAAA,IAAe,aAAA,CAAc,CAAA,IAAK,cAAA,CAAe,CAAA;AAAA;;;;;;;;;;;;;;;;AAAC;AAuC5D;;;;AAAyC;AAsBzC;;;;;;;;;AAA8D;AAI9D;;;;iBA1BgB,MAAA,IAAU,eAAe;;;;;;;;;;;;AA6BvB;;;;;;;;;;iBAPF,MAAA,CAAO,OAAA,EAAS,MAAA,CAAO,KAAA,GAAQ,eAAe;;;;iBAI9C,MAAA,CACd,OAAA,EAAS,MAAA,CAAO,KAAA,EAChB,MAAA;EAAU,MAAA,SAAe,eAAA;AAAA,IACxB,eAAA"} |
+45
-30
@@ -5,3 +5,3 @@ "use client"; | ||
| import { PROXIED_ASSISTANT_STATE_SYMBOL, createProxiedAssistantState } from "./utils/proxied-assistant-state.js"; | ||
| import { DefaultAssistantClient, createRootAssistantClient, useAssistantContextValue } from "./utils/react-assistant-context.js"; | ||
| import { AUI_USE_EFFECTS_SYMBOL, DefaultAssistantClient, createRootAssistantClient, useAssistantContextValue } from "./utils/react-assistant-context.js"; | ||
| import { useSplitClients } from "./utils/splitClients.js"; | ||
@@ -12,3 +12,3 @@ import { normalizeEventSelector } from "./types/events.js"; | ||
| import { useEffect, useMemo, useRef } from "@assistant-ui/tap/react-shim"; | ||
| import { resource, useResource, useResourceRoot, useResources, withKey } from "@assistant-ui/tap"; | ||
| import { resource, useResource, useResources, useTapHost, useTapRoot, withKey } from "@assistant-ui/tap"; | ||
| //#region src/useAui.ts | ||
@@ -18,7 +18,9 @@ const useShallowMemoArray = (array) => { | ||
| }; | ||
| const RootClientResource = resource(function RootClientResource({ element, emit, clientRef }) { | ||
| const useRootClientResource = ({ element, emit, clientRef }) => { | ||
| const { methods, state } = withAssistantTapContextProvider({ | ||
| clientRef, | ||
| emit | ||
| }, () => useClientResource(element)); | ||
| }, function WithTapContext() { | ||
| return useClientResource(element); | ||
| }); | ||
| return useMemo(() => ({ | ||
@@ -28,9 +30,11 @@ state, | ||
| }), [methods, state]); | ||
| }); | ||
| const RootClientAccessorResource = resource(function RootClientAccessorResource({ element, notifications, clientRef, name }) { | ||
| const store = useResourceRoot(RootClientResource({ | ||
| element, | ||
| emit: notifications.emit, | ||
| clientRef | ||
| })); | ||
| }; | ||
| const useRootClientAccessorResource = ({ element, notifications, clientRef, name }) => { | ||
| const store = useTapRoot(function RootClient() { | ||
| return useRootClientResource({ | ||
| element, | ||
| emit: notifications.emit, | ||
| clientRef | ||
| }); | ||
| }); | ||
| useEffect(() => { | ||
@@ -57,4 +61,5 @@ return store.subscribe(notifications.notifySubscribers); | ||
| }, [store, name]); | ||
| }); | ||
| const NoOpRootClientsAccessorsResource = resource(function NoOpRootClientsAccessorsResource() { | ||
| }; | ||
| const RootClientAccessorResource = resource(useRootClientAccessorResource); | ||
| const useNoOpRootClientsAccessorsResource = () => { | ||
| return useMemo(() => ({ | ||
@@ -65,4 +70,5 @@ clients: [], | ||
| }), []); | ||
| }); | ||
| const RootClientsAccessorsResource = resource(function RootClientsAccessorsResource({ clients: inputClients, clientRef }) { | ||
| }; | ||
| const NoOpRootClientsAccessorsResource = resource(useNoOpRootClientsAccessorsResource); | ||
| const useRootClientsAccessorsResource = ({ clients: inputClients, clientRef }) => { | ||
| const notifications = useResource(NotificationManager()); | ||
@@ -111,6 +117,7 @@ useEffect(() => clientRef.parent.subscribe(notifications.notifySubscribers), [clientRef, notifications]); | ||
| ]); | ||
| }); | ||
| const DerivedClientAccessorResource = resource(function DerivedClientAccessorResource({ element, clientRef, name }) { | ||
| const propsRef = useRef(element.props); | ||
| propsRef.current = element.props; | ||
| }; | ||
| const RootClientsAccessorsResource = resource(useRootClientsAccessorsResource); | ||
| const useDerivedClientAccessorResource = ({ element, clientRef, name }) => { | ||
| const propsRef = useRef(element.args[0]); | ||
| propsRef.current = element.args[0]; | ||
| return useMemo(() => { | ||
@@ -128,3 +135,4 @@ const clientFunction = () => propsRef.current.get(clientRef.current); | ||
| }, [clientRef, name]); | ||
| }); | ||
| }; | ||
| const DerivedClientAccessorResource = resource(useDerivedClientAccessorResource); | ||
| const serializeMeta = (name, meta) => { | ||
@@ -141,7 +149,7 @@ let queryKey; | ||
| }; | ||
| const DerivedClientsAccessorsResource = resource(function DerivedClientsAccessorsResource({ clients, clientRef }) { | ||
| const useDerivedClientsAccessorsResource = ({ clients, clientRef }) => { | ||
| return useShallowMemoArray(useResources(() => Object.keys(clients).map((key) => { | ||
| const name = key; | ||
| const element = clients[name]; | ||
| return withKey(serializeMeta(name, element.props), DerivedClientAccessorResource({ | ||
| return withKey(serializeMeta(name, element.args[0]), DerivedClientAccessorResource({ | ||
| element, | ||
@@ -152,7 +160,7 @@ clientRef, | ||
| }), [clients, clientRef])); | ||
| }); | ||
| }; | ||
| /** | ||
| * Resource that creates an extended AssistantClient. | ||
| */ | ||
| const AssistantClientResource = resource(function AssistantClientResource({ parent, clients }) { | ||
| const useAssistantClient = ({ parent, clients }) => { | ||
| const { rootClients, derivedClients } = useSplitClients(clients, parent); | ||
@@ -170,6 +178,6 @@ const clientRef = useRef({ | ||
| }) : NoOpRootClientsAccessorsResource()); | ||
| const derivedFields = useResource(DerivedClientsAccessorsResource({ | ||
| const derivedFields = useDerivedClientsAccessorsResource({ | ||
| clients: derivedClients, | ||
| clientRef | ||
| })); | ||
| }); | ||
| const client = useMemo(() => { | ||
@@ -193,9 +201,16 @@ const proto = parent === DefaultAssistantClient ? createRootAssistantClient() : parent; | ||
| return client; | ||
| }); | ||
| }; | ||
| const useHostedAssistantClient = (props) => { | ||
| const { value: client, effects } = useTapHost(function AssistantClientHost() { | ||
| return useAssistantClient(props); | ||
| }); | ||
| client[AUI_USE_EFFECTS_SYMBOL] = effects; | ||
| return client; | ||
| }; | ||
| /** @deprecated This API is highly experimental and may be changed in a minor release */ | ||
| function useAui(clients, { parent } = { parent: useAssistantContextValue() }) { | ||
| if (clients) return useResource(AssistantClientResource({ | ||
| if (clients) return useHostedAssistantClient({ | ||
| parent: parent ?? DefaultAssistantClient, | ||
| clients | ||
| })); | ||
| }); | ||
| if (parent === null) throw new Error("received null parent, this usage is not allowed"); | ||
@@ -205,4 +220,4 @@ return parent; | ||
| //#endregion | ||
| export { AssistantClientResource, useAui }; | ||
| export { useAui }; | ||
| //# sourceMappingURL=useAui.js.map |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"useAui.js","names":[],"sources":["../src/useAui.ts"],"sourcesContent":["\"use client\";\n\nimport {\n useResource,\n useResources,\n useResourceRoot,\n resource,\n withKey,\n} from \"@assistant-ui/tap\";\nimport { useMemo, useEffect, useRef } from \"react\";\n\nimport type {\n AssistantClient,\n AssistantClientAccessor,\n ClientNames,\n ClientElement,\n ClientMeta,\n} from \"./types/client\";\nimport type { DerivedElement } from \"./Derived\";\nimport {\n useAssistantContextValue,\n DefaultAssistantClient,\n createRootAssistantClient,\n} from \"./utils/react-assistant-context\";\nimport {\n type DerivedClients,\n type RootClients,\n useSplitClients,\n} from \"./utils/splitClients\";\nimport {\n normalizeEventSelector,\n type AssistantEventName,\n type AssistantEventCallback,\n type AssistantEventSelector,\n} from \"./types/events\";\nimport { NotificationManager } from \"./utils/NotificationManager\";\nimport { withAssistantTapContextProvider } from \"./utils/tap-assistant-context\";\nimport { useClientResource } from \"./useClientResource\";\nimport { getClientIndex } from \"./utils/tap-client-stack-context\";\nimport {\n PROXIED_ASSISTANT_STATE_SYMBOL,\n createProxiedAssistantState,\n} from \"./utils/proxied-assistant-state\";\n\nconst useShallowMemoArray = <T>(array: readonly T[]) => {\n // oxlint-disable-next-line react/exhaustive-deps -- shallow memo over the array itself\n return useMemo(() => array, array);\n};\n\nconst RootClientResource = resource(function RootClientResource<\n K extends ClientNames,\n>({\n element,\n emit,\n clientRef,\n}: {\n element: ClientElement<K>;\n emit: NotificationManager[\"emit\"];\n clientRef: { parent: AssistantClient; current: AssistantClient | null };\n}) {\n const { methods, state } = withAssistantTapContextProvider(\n { clientRef, emit },\n // oxlint-disable-next-line react/rules-of-hooks -- withAssistantTapContextProvider runs this callback synchronously during render, so hook order is preserved\n () => useClientResource(element),\n );\n return useMemo(() => ({ state, methods }), [methods, state]);\n});\n\nconst RootClientAccessorResource = resource(function RootClientAccessorResource<\n K extends ClientNames,\n>({\n element,\n notifications,\n clientRef,\n name,\n}: {\n element: ClientElement<K>;\n notifications: NotificationManager;\n clientRef: { parent: AssistantClient; current: AssistantClient | null };\n name: K;\n}): AssistantClientAccessor<K> {\n const store = useResourceRoot(\n RootClientResource({ element, emit: notifications.emit, clientRef }),\n );\n\n useEffect(() => {\n return store.subscribe(notifications.notifySubscribers);\n }, [store, notifications]);\n\n return useMemo(() => {\n const clientFunction = () => store.getValue().methods;\n Object.defineProperties(clientFunction, {\n source: {\n value: \"root\" as const,\n writable: false,\n },\n query: {\n value: {} as Record<string, never>,\n writable: false,\n },\n name: {\n value: name,\n configurable: true,\n },\n });\n return clientFunction as AssistantClientAccessor<K>;\n }, [store, name]);\n});\n\nconst NoOpRootClientsAccessorsResource = resource(\n function NoOpRootClientsAccessorsResource() {\n return useMemo(\n () => ({\n clients: [] as AssistantClientAccessor<ClientNames>[],\n subscribe: undefined,\n on: undefined,\n }),\n [],\n );\n },\n);\n\nconst RootClientsAccessorsResource = resource(\n function RootClientsAccessorsResource({\n clients: inputClients,\n clientRef,\n }: {\n clients: RootClients;\n clientRef: { parent: AssistantClient; current: AssistantClient | null };\n }) {\n const notifications = useResource(NotificationManager());\n\n useEffect(\n () => clientRef.parent.subscribe(notifications.notifySubscribers),\n [clientRef, notifications],\n );\n\n const results = useShallowMemoArray(\n useResources(\n () =>\n Object.keys(inputClients).map((key) =>\n withKey(\n key,\n RootClientAccessorResource({\n element: inputClients[key as keyof typeof inputClients]!,\n notifications,\n clientRef,\n name: key as keyof typeof inputClients,\n }),\n ),\n ),\n [inputClients, notifications, clientRef],\n ),\n );\n\n return useMemo(() => {\n return {\n clients: results,\n subscribe: notifications.subscribe,\n on: function <TEvent extends AssistantEventName>(\n this: AssistantClient,\n selector: AssistantEventSelector<TEvent>,\n callback: AssistantEventCallback<TEvent>,\n ) {\n if (!this) {\n throw new Error(\n \"const { on } = useAui() is not supported. Use aui.on() instead.\",\n );\n }\n\n const { scope, event } = normalizeEventSelector(selector);\n\n if (scope !== \"*\") {\n const source = this[scope as ClientNames].source;\n if (source === null) {\n throw new Error(\n `Scope \"${scope}\" is not available. Use { scope: \"*\", event: \"${event}\" } to listen globally.`,\n );\n }\n }\n\n const localUnsub = notifications.on(event, (payload, clientStack) => {\n if (scope === \"*\") {\n callback(payload);\n return;\n }\n\n const scopeClient = this[scope as ClientNames]();\n const index = getClientIndex(scopeClient);\n if (scopeClient === clientStack[index]) {\n callback(payload);\n }\n });\n if (\n scope !== \"*\" &&\n clientRef.parent[scope as ClientNames].source === null\n )\n return localUnsub;\n\n const parentUnsub = clientRef.parent.on(selector, callback);\n\n return () => {\n localUnsub();\n parentUnsub();\n };\n },\n };\n }, [results, notifications, clientRef]);\n },\n);\n\nconst DerivedClientAccessorResource = resource(\n function DerivedClientAccessorResource<K extends ClientNames>({\n element,\n clientRef,\n name,\n }: {\n element: DerivedElement<K>;\n clientRef: { parent: AssistantClient; current: AssistantClient | null };\n name: K;\n }) {\n // Track the latest props on a ref updated in render. The fiber is\n // keyed on the scope's meta by DerivedClientsAccessorsResource, so\n // source/query are stable for this fiber's lifetime and the only\n // value that can change between renders for the same fiber is the\n // identity of the `get` closure. Routing reads through the ref so\n // they take effect without a one-commit lag.\n const propsRef = useRef(element.props);\n propsRef.current = element.props;\n\n return useMemo(() => {\n const clientFunction = () => propsRef.current.get(clientRef.current!);\n Object.defineProperties(clientFunction, {\n source: {\n value: propsRef.current.source,\n },\n query: {\n value: propsRef.current.query,\n },\n name: {\n value: name,\n configurable: true,\n },\n });\n return clientFunction as AssistantClientAccessor<K>;\n }, [clientRef, name]);\n },\n);\n\nconst serializeMeta = <K extends ClientNames>(\n name: K,\n meta: ClientMeta<K>,\n): string => {\n // Sort top-level keys so {a, b} and {b, a} hash to the same fiber\n // identity, and guard JSON.stringify against unusual values (BigInt,\n // circular refs) so render never throws here.\n let queryKey: string;\n try {\n const sorted: Record<string, unknown> = {};\n for (const k of Object.keys(meta.query as object).sort()) {\n sorted[k] = (meta.query as Record<string, unknown>)[k];\n }\n queryKey = JSON.stringify(sorted);\n } catch {\n queryKey = String(meta.query);\n }\n return `${name}::${meta.source}::${queryKey}`;\n};\n\nconst DerivedClientsAccessorsResource = resource(\n function DerivedClientsAccessorsResource({\n clients,\n clientRef,\n }: {\n clients: DerivedClients;\n clientRef: { parent: AssistantClient; current: AssistantClient | null };\n }) {\n return useShallowMemoArray(\n useResources(\n () =>\n Object.keys(clients).map((key) => {\n const name = key as keyof typeof clients;\n const element = clients[name]!;\n return withKey(\n serializeMeta(name, element.props),\n DerivedClientAccessorResource({\n element,\n clientRef,\n name,\n }),\n );\n }),\n [clients, clientRef],\n ),\n );\n },\n);\n\n/**\n * Resource that creates an extended AssistantClient.\n */\nexport const AssistantClientResource = resource(\n function AssistantClientResource({\n parent,\n clients,\n }: {\n parent: AssistantClient;\n clients: useAui.Props;\n }): AssistantClient {\n const { rootClients, derivedClients } = useSplitClients(clients, parent);\n\n const clientRef = useRef({\n parent: parent,\n current: null as AssistantClient | null,\n }).current;\n\n useEffect(() => {\n clientRef.current = client;\n });\n\n const rootFields = useResource(\n Object.keys(rootClients).length > 0\n ? RootClientsAccessorsResource({ clients: rootClients, clientRef })\n : NoOpRootClientsAccessorsResource(),\n );\n\n const derivedFields = useResource(\n DerivedClientsAccessorsResource({ clients: derivedClients, clientRef }),\n );\n\n const client = useMemo(() => {\n // Swap DefaultAssistantClient -> createRootAssistantClient at root to change error message\n const proto =\n parent === DefaultAssistantClient\n ? createRootAssistantClient()\n : parent;\n\n const client = Object.create(proto) as AssistantClient;\n Object.assign(client, {\n subscribe: rootFields.subscribe ?? parent.subscribe,\n on: rootFields.on ?? parent.on,\n [PROXIED_ASSISTANT_STATE_SYMBOL]: createProxiedAssistantState(client),\n });\n\n for (const field of rootFields.clients) {\n (client as any)[field.name] = field;\n }\n for (const field of derivedFields) {\n (client as any)[field.name] = field;\n }\n\n return client;\n }, [parent, rootFields, derivedFields]);\n\n if (clientRef.current === null) {\n clientRef.current = client;\n }\n\n return client;\n },\n);\n\nexport namespace useAui {\n export type Props = {\n [K in ClientNames]?: ClientElement<K> | DerivedElement<K>;\n };\n}\n\n/**\n * Returns the current `AssistantClient` from context.\n *\n * Read the client supplied by the nearest {@link AuiProvider} or\n * {@link AssistantRuntimeProvider}, then access a scope on it —\n * `aui.thread()`, `aui.composer()`, `aui.message()`, and so on. Pair\n * with {@link useAuiState} to read reactive state and {@link useAuiEvent}\n * to subscribe to events. The returned client also exposes lower-level\n * methods such as `aui.on(...)` and `aui.subscribe(...)`; prefer\n * `useAuiEvent` for React event subscriptions.\n *\n * Rendered outside a provider, the returned client's scope accessors\n * throw a descriptive error whenever they are called.\n *\n * @example\n * ```tsx\n * const aui = useAui();\n *\n * const onSend = () => aui.composer().send();\n * const onCancel = () => aui.thread().cancelRun();\n * ```\n *\n * @example\n * ```tsx\n * // Combine with useAuiState to drive disabled state.\n * const aui = useAui();\n * const isRunning = useAuiState((s) => s.thread.isRunning);\n *\n * return (\n * <button disabled={isRunning} onClick={() => aui.composer().send()}>\n * Send\n * </button>\n * );\n * ```\n */\nexport function useAui(): AssistantClient;\n/**\n * Extends the parent `AssistantClient` with additional scopes.\n *\n * Advanced overload used when building primitives or providers — for example,\n * when a custom provider needs to register a `message`, `part`, or other scope\n * onto the client visible to its descendants. Application code rarely reaches\n * for this; use {@link useAui} with no arguments to read the existing client.\n *\n * @example\n * ```tsx\n * const aui = useAui({\n * message: Derived({\n * source: \"thread\",\n * query: { index: 0 },\n * get: (aui) => aui.thread().message({ index: 0 }),\n * }),\n * });\n *\n * const role = useAuiState((s) => s.message.role);\n * ```\n */\nexport function useAui(clients: useAui.Props): AssistantClient;\n/**\n * Extends an explicit parent `AssistantClient` with additional scopes.\n */\nexport function useAui(\n clients: useAui.Props,\n config: { parent: null | AssistantClient },\n): AssistantClient;\n/** @deprecated This API is highly experimental and may be changed in a minor release */\nexport function useAui(\n clients?: useAui.Props,\n { parent }: { parent: null | AssistantClient } = {\n parent: useAssistantContextValue(),\n },\n): AssistantClient {\n if (clients) {\n return useResource(\n AssistantClientResource({\n parent: parent ?? DefaultAssistantClient,\n clients,\n }),\n );\n }\n if (parent === null)\n throw new Error(\"received null parent, this usage is not allowed\");\n return parent;\n}\n"],"mappings":";;;;;;;;;;;;AA4CA,MAAM,uBAA0B,UAAwB;CAEtD,OAAO,cAAc,OAAO,KAAK;AACnC;AAEA,MAAM,qBAAqB,SAAS,SAAS,mBAE3C,EACA,SACA,MACA,aAKC;CACD,MAAM,EAAE,SAAS,UAAU,gCACzB;EAAE;EAAW;CAAK,SAEZ,kBAAkB,OAAO,CACjC;CACA,OAAO,eAAe;EAAE;EAAO;CAAQ,IAAI,CAAC,SAAS,KAAK,CAAC;AAC7D,CAAC;AAED,MAAM,6BAA6B,SAAS,SAAS,2BAEnD,EACA,SACA,eACA,WACA,QAM6B;CAC7B,MAAM,QAAQ,gBACZ,mBAAmB;EAAE;EAAS,MAAM,cAAc;EAAM;CAAU,CAAC,CACrE;CAEA,gBAAgB;EACd,OAAO,MAAM,UAAU,cAAc,iBAAiB;CACxD,GAAG,CAAC,OAAO,aAAa,CAAC;CAEzB,OAAO,cAAc;EACnB,MAAM,uBAAuB,MAAM,SAAS,CAAC,CAAC;EAC9C,OAAO,iBAAiB,gBAAgB;GACtC,QAAQ;IACN,OAAO;IACP,UAAU;GACZ;GACA,OAAO;IACL,OAAO,CAAC;IACR,UAAU;GACZ;GACA,MAAM;IACJ,OAAO;IACP,cAAc;GAChB;EACF,CAAC;EACD,OAAO;CACT,GAAG,CAAC,OAAO,IAAI,CAAC;AAClB,CAAC;AAED,MAAM,mCAAmC,SACvC,SAAS,mCAAmC;CAC1C,OAAO,eACE;EACL,SAAS,CAAC;EACV,WAAW,KAAA;EACX,IAAI,KAAA;CACN,IACA,CAAC,CACH;AACF,CACF;AAEA,MAAM,+BAA+B,SACnC,SAAS,6BAA6B,EACpC,SAAS,cACT,aAIC;CACD,MAAM,gBAAgB,YAAY,oBAAoB,CAAC;CAEvD,gBACQ,UAAU,OAAO,UAAU,cAAc,iBAAiB,GAChE,CAAC,WAAW,aAAa,CAC3B;CAEA,MAAM,UAAU,oBACd,mBAEI,OAAO,KAAK,YAAY,CAAC,CAAC,KAAK,QAC7B,QACE,KACA,2BAA2B;EACzB,SAAS,aAAa;EACtB;EACA;EACA,MAAM;CACR,CAAC,CACH,CACF,GACF;EAAC;EAAc;EAAe;CAAS,CACzC,CACF;CAEA,OAAO,cAAc;EACnB,OAAO;GACL,SAAS;GACT,WAAW,cAAc;GACzB,IAAI,SAEF,UACA,UACA;IACA,IAAI,CAAC,MACH,MAAM,IAAI,MACR,iEACF;IAGF,MAAM,EAAE,OAAO,UAAU,uBAAuB,QAAQ;IAExD,IAAI,UAAU;SACG,KAAK,MAAqB,CAAC,WAC3B,MACb,MAAM,IAAI,MACR,UAAU,MAAM,gDAAgD,MAAM,wBACxE;IAAA;IAIJ,MAAM,aAAa,cAAc,GAAG,QAAQ,SAAS,gBAAgB;KACnE,IAAI,UAAU,KAAK;MACjB,SAAS,OAAO;MAChB;KACF;KAEA,MAAM,cAAc,KAAK,MAAqB,CAAC;KAE/C,IAAI,gBAAgB,YADN,eAAe,WACO,IAClC,SAAS,OAAO;IAEpB,CAAC;IACD,IACE,UAAU,OACV,UAAU,OAAO,MAAqB,CAAC,WAAW,MAElD,OAAO;IAET,MAAM,cAAc,UAAU,OAAO,GAAG,UAAU,QAAQ;IAE1D,aAAa;KACX,WAAW;KACX,YAAY;IACd;GACF;EACF;CACF,GAAG;EAAC;EAAS;EAAe;CAAS,CAAC;AACxC,CACF;AAEA,MAAM,gCAAgC,SACpC,SAAS,8BAAqD,EAC5D,SACA,WACA,QAKC;CAOD,MAAM,WAAW,OAAO,QAAQ,KAAK;CACrC,SAAS,UAAU,QAAQ;CAE3B,OAAO,cAAc;EACnB,MAAM,uBAAuB,SAAS,QAAQ,IAAI,UAAU,OAAQ;EACpE,OAAO,iBAAiB,gBAAgB;GACtC,QAAQ,EACN,OAAO,SAAS,QAAQ,OAC1B;GACA,OAAO,EACL,OAAO,SAAS,QAAQ,MAC1B;GACA,MAAM;IACJ,OAAO;IACP,cAAc;GAChB;EACF,CAAC;EACD,OAAO;CACT,GAAG,CAAC,WAAW,IAAI,CAAC;AACtB,CACF;AAEA,MAAM,iBACJ,MACA,SACW;CAIX,IAAI;CACJ,IAAI;EACF,MAAM,SAAkC,CAAC;EACzC,KAAK,MAAM,KAAK,OAAO,KAAK,KAAK,KAAe,CAAC,CAAC,KAAK,GACrD,OAAO,KAAM,KAAK,MAAkC;EAEtD,WAAW,KAAK,UAAU,MAAM;CAClC,QAAQ;EACN,WAAW,OAAO,KAAK,KAAK;CAC9B;CACA,OAAO,GAAG,KAAK,IAAI,KAAK,OAAO,IAAI;AACrC;AAEA,MAAM,kCAAkC,SACtC,SAAS,gCAAgC,EACvC,SACA,aAIC;CACD,OAAO,oBACL,mBAEI,OAAO,KAAK,OAAO,CAAC,CAAC,KAAK,QAAQ;EAChC,MAAM,OAAO;EACb,MAAM,UAAU,QAAQ;EACxB,OAAO,QACL,cAAc,MAAM,QAAQ,KAAK,GACjC,8BAA8B;GAC5B;GACA;GACA;EACF,CAAC,CACH;CACF,CAAC,GACH,CAAC,SAAS,SAAS,CACrB,CACF;AACF,CACF;;;;AAKA,MAAa,0BAA0B,SACrC,SAAS,wBAAwB,EAC/B,QACA,WAIkB;CAClB,MAAM,EAAE,aAAa,mBAAmB,gBAAgB,SAAS,MAAM;CAEvE,MAAM,YAAY,OAAO;EACf;EACR,SAAS;CACX,CAAC,CAAC,CAAC;CAEH,gBAAgB;EACd,UAAU,UAAU;CACtB,CAAC;CAED,MAAM,aAAa,YACjB,OAAO,KAAK,WAAW,CAAC,CAAC,SAAS,IAC9B,6BAA6B;EAAE,SAAS;EAAa;CAAU,CAAC,IAChE,iCAAiC,CACvC;CAEA,MAAM,gBAAgB,YACpB,gCAAgC;EAAE,SAAS;EAAgB;CAAU,CAAC,CACxE;CAEA,MAAM,SAAS,cAAc;EAE3B,MAAM,QACJ,WAAW,yBACP,0BAA0B,IAC1B;EAEN,MAAM,SAAS,OAAO,OAAO,KAAK;EAClC,OAAO,OAAO,QAAQ;GACpB,WAAW,WAAW,aAAa,OAAO;GAC1C,IAAI,WAAW,MAAM,OAAO;IAC3B,iCAAiC,4BAA4B,MAAM;EACtE,CAAC;EAED,KAAK,MAAM,SAAS,WAAW,SAC7B,OAAgB,MAAM,QAAQ;EAEhC,KAAK,MAAM,SAAS,eAClB,OAAgB,MAAM,QAAQ;EAGhC,OAAO;CACT,GAAG;EAAC;EAAQ;EAAY;CAAa,CAAC;CAEtC,IAAI,UAAU,YAAY,MACxB,UAAU,UAAU;CAGtB,OAAO;AACT,CACF;;AA0EA,SAAgB,OACd,SACA,EAAE,WAA+C,EAC/C,QAAQ,yBAAyB,EACnC,GACiB;CACjB,IAAI,SACF,OAAO,YACL,wBAAwB;EACtB,QAAQ,UAAU;EAClB;CACF,CAAC,CACH;CAEF,IAAI,WAAW,MACb,MAAM,IAAI,MAAM,iDAAiD;CACnE,OAAO;AACT"} | ||
| {"version":3,"file":"useAui.js","names":[],"sources":["../src/useAui.ts"],"sourcesContent":["\"use client\";\n\nimport {\n useResource,\n useResources,\n useTapHost,\n useTapRoot,\n resource,\n withKey,\n} from \"@assistant-ui/tap\";\nimport { useMemo, useEffect, useRef } from \"react\";\n\nimport type {\n AssistantClient,\n AssistantClientAccessor,\n ClientNames,\n ClientElement,\n ClientMeta,\n} from \"./types/client\";\nimport type { DerivedElement } from \"./Derived\";\nimport {\n useAssistantContextValue,\n DefaultAssistantClient,\n createRootAssistantClient,\n AUI_USE_EFFECTS_SYMBOL,\n} from \"./utils/react-assistant-context\";\nimport {\n type DerivedClients,\n type RootClients,\n useSplitClients,\n} from \"./utils/splitClients\";\nimport {\n normalizeEventSelector,\n type AssistantEventName,\n type AssistantEventCallback,\n type AssistantEventSelector,\n} from \"./types/events\";\nimport { NotificationManager } from \"./utils/NotificationManager\";\nimport { withAssistantTapContextProvider } from \"./utils/tap-assistant-context\";\nimport { useClientResource } from \"./useClientResource\";\nimport { getClientIndex } from \"./utils/tap-client-stack-context\";\nimport {\n PROXIED_ASSISTANT_STATE_SYMBOL,\n createProxiedAssistantState,\n} from \"./utils/proxied-assistant-state\";\n\nconst useShallowMemoArray = <T>(array: readonly T[]) => {\n // oxlint-disable-next-line react/exhaustive-deps -- shallow memo over the array itself\n return useMemo(() => array, array);\n};\n\nconst useRootClientResource = <K extends ClientNames>({\n element,\n emit,\n clientRef,\n}: {\n element: ClientElement<K>;\n emit: NotificationManager[\"emit\"];\n clientRef: { parent: AssistantClient; current: AssistantClient | null };\n}) => {\n const { methods, state } = withAssistantTapContextProvider(\n { clientRef, emit },\n function WithTapContext() {\n return useClientResource(element);\n },\n );\n return useMemo(() => ({ state, methods }), [methods, state]);\n};\n\nconst useRootClientAccessorResource = <K extends ClientNames>({\n element,\n notifications,\n clientRef,\n name,\n}: {\n element: ClientElement<K>;\n notifications: NotificationManager;\n clientRef: { parent: AssistantClient; current: AssistantClient | null };\n name: K;\n}): AssistantClientAccessor<K> => {\n const store = useTapRoot(function RootClient() {\n return useRootClientResource({\n element,\n emit: notifications.emit,\n clientRef,\n });\n });\n\n useEffect(() => {\n return store.subscribe(notifications.notifySubscribers);\n }, [store, notifications]);\n\n return useMemo(() => {\n const clientFunction = () => store.getValue().methods;\n Object.defineProperties(clientFunction, {\n source: {\n value: \"root\" as const,\n writable: false,\n },\n query: {\n value: {} as Record<string, never>,\n writable: false,\n },\n name: {\n value: name,\n configurable: true,\n },\n });\n return clientFunction as AssistantClientAccessor<K>;\n }, [store, name]);\n};\n\nconst RootClientAccessorResource = resource(useRootClientAccessorResource);\n\nconst useNoOpRootClientsAccessorsResource = () => {\n return useMemo(\n () => ({\n clients: [] as AssistantClientAccessor<ClientNames>[],\n subscribe: undefined,\n on: undefined,\n }),\n [],\n );\n};\n\nconst NoOpRootClientsAccessorsResource = resource(\n useNoOpRootClientsAccessorsResource,\n);\n\nconst useRootClientsAccessorsResource = ({\n clients: inputClients,\n clientRef,\n}: {\n clients: RootClients;\n clientRef: { parent: AssistantClient; current: AssistantClient | null };\n}) => {\n const notifications = useResource(NotificationManager());\n\n useEffect(\n () => clientRef.parent.subscribe(notifications.notifySubscribers),\n [clientRef, notifications],\n );\n\n const results = useShallowMemoArray(\n useResources(\n () =>\n Object.keys(inputClients).map((key) =>\n withKey(\n key,\n RootClientAccessorResource({\n element: inputClients[key as keyof typeof inputClients]!,\n notifications,\n clientRef,\n name: key as keyof typeof inputClients,\n }),\n ),\n ),\n [inputClients, notifications, clientRef],\n ),\n );\n\n return useMemo(() => {\n return {\n clients: results,\n subscribe: notifications.subscribe,\n on: function <TEvent extends AssistantEventName>(\n this: AssistantClient,\n selector: AssistantEventSelector<TEvent>,\n callback: AssistantEventCallback<TEvent>,\n ) {\n if (!this) {\n throw new Error(\n \"const { on } = useAui() is not supported. Use aui.on() instead.\",\n );\n }\n\n const { scope, event } = normalizeEventSelector(selector);\n\n if (scope !== \"*\") {\n const source = this[scope as ClientNames].source;\n if (source === null) {\n throw new Error(\n `Scope \"${scope}\" is not available. Use { scope: \"*\", event: \"${event}\" } to listen globally.`,\n );\n }\n }\n\n const localUnsub = notifications.on(event, (payload, clientStack) => {\n if (scope === \"*\") {\n callback(payload);\n return;\n }\n\n const scopeClient = this[scope as ClientNames]();\n const index = getClientIndex(scopeClient);\n if (scopeClient === clientStack[index]) {\n callback(payload);\n }\n });\n if (\n scope !== \"*\" &&\n clientRef.parent[scope as ClientNames].source === null\n )\n return localUnsub;\n\n const parentUnsub = clientRef.parent.on(selector, callback);\n\n return () => {\n localUnsub();\n parentUnsub();\n };\n },\n };\n }, [results, notifications, clientRef]);\n};\n\nconst RootClientsAccessorsResource = resource(useRootClientsAccessorsResource);\n\nconst useDerivedClientAccessorResource = <K extends ClientNames>({\n element,\n clientRef,\n name,\n}: {\n element: DerivedElement<K>;\n clientRef: { parent: AssistantClient; current: AssistantClient | null };\n name: K;\n}) => {\n // Track the latest props on a ref updated in render. The fiber is\n // keyed on the scope's meta by DerivedClientsAccessorsResource, so\n // source/query are stable for this fiber's lifetime and the only\n // value that can change between renders for the same fiber is the\n // identity of the `get` closure. Routing reads through the ref so\n // they take effect without a one-commit lag.\n const propsRef = useRef(element.args[0]);\n propsRef.current = element.args[0];\n\n return useMemo(() => {\n const clientFunction = () => propsRef.current.get(clientRef.current!);\n Object.defineProperties(clientFunction, {\n source: {\n value: propsRef.current.source,\n },\n query: {\n value: propsRef.current.query,\n },\n name: {\n value: name,\n configurable: true,\n },\n });\n return clientFunction as AssistantClientAccessor<K>;\n }, [clientRef, name]);\n};\n\nconst DerivedClientAccessorResource = resource(\n useDerivedClientAccessorResource,\n);\n\nconst serializeMeta = <K extends ClientNames>(\n name: K,\n meta: ClientMeta<K>,\n): string => {\n // Sort top-level keys so {a, b} and {b, a} hash to the same fiber\n // identity, and guard JSON.stringify against unusual values (BigInt,\n // circular refs) so render never throws here.\n let queryKey: string;\n try {\n const sorted: Record<string, unknown> = {};\n for (const k of Object.keys(meta.query as object).sort()) {\n sorted[k] = (meta.query as Record<string, unknown>)[k];\n }\n queryKey = JSON.stringify(sorted);\n } catch {\n queryKey = String(meta.query);\n }\n return `${name}::${meta.source}::${queryKey}`;\n};\n\nconst useDerivedClientsAccessorsResource = ({\n clients,\n clientRef,\n}: {\n clients: DerivedClients;\n clientRef: { parent: AssistantClient; current: AssistantClient | null };\n}) => {\n return useShallowMemoArray(\n useResources(\n () =>\n Object.keys(clients).map((key) => {\n const name = key as keyof typeof clients;\n const element = clients[name]!;\n return withKey(\n serializeMeta(name, element.args[0]),\n DerivedClientAccessorResource({\n element,\n clientRef,\n name,\n }),\n );\n }),\n [clients, clientRef],\n ),\n );\n};\n\n/**\n * Resource that creates an extended AssistantClient.\n */\nconst useAssistantClient = ({\n parent,\n clients,\n}: {\n parent: AssistantClient;\n clients: useAui.Props;\n}): AssistantClient => {\n const { rootClients, derivedClients } = useSplitClients(clients, parent);\n\n const clientRef = useRef({\n parent: parent,\n current: null as AssistantClient | null,\n }).current;\n\n useEffect(() => {\n clientRef.current = client;\n });\n\n const rootFields = useResource(\n Object.keys(rootClients).length > 0\n ? RootClientsAccessorsResource({ clients: rootClients, clientRef })\n : NoOpRootClientsAccessorsResource(),\n );\n\n const derivedFields = useDerivedClientsAccessorsResource({\n clients: derivedClients,\n clientRef,\n });\n\n const client = useMemo(() => {\n // Swap DefaultAssistantClient -> createRootAssistantClient at root to change error message\n const proto =\n parent === DefaultAssistantClient ? createRootAssistantClient() : parent;\n\n const client = Object.create(proto) as AssistantClient;\n Object.assign(client, {\n subscribe: rootFields.subscribe ?? parent.subscribe,\n on: rootFields.on ?? parent.on,\n [PROXIED_ASSISTANT_STATE_SYMBOL]: createProxiedAssistantState(client),\n });\n\n for (const field of rootFields.clients) {\n (client as any)[field.name] = field;\n }\n for (const field of derivedFields) {\n (client as any)[field.name] = field;\n }\n\n return client;\n }, [parent, rootFields, derivedFields]);\n\n if (clientRef.current === null) {\n clientRef.current = client;\n }\n\n return client;\n};\n\nconst useHostedAssistantClient = (props: {\n parent: AssistantClient;\n clients: useAui.Props;\n}): AssistantClient => {\n const { value: client, effects } = useTapHost(function AssistantClientHost() {\n return useAssistantClient(props);\n });\n\n (client as Record<symbol, unknown>)[AUI_USE_EFFECTS_SYMBOL] = effects;\n\n return client;\n};\n\nexport namespace useAui {\n export type Props = {\n [K in ClientNames]?: ClientElement<K> | DerivedElement<K>;\n };\n}\n\n/**\n * Returns the current `AssistantClient` from context.\n *\n * Read the client supplied by the nearest {@link AuiProvider} or\n * {@link AssistantRuntimeProvider}, then access a scope on it —\n * `aui.thread()`, `aui.composer()`, `aui.message()`, and so on. Pair\n * with {@link useAuiState} to read reactive state and {@link useAuiEvent}\n * to subscribe to events. The returned client also exposes lower-level\n * methods such as `aui.on(...)` and `aui.subscribe(...)`; prefer\n * `useAuiEvent` for React event subscriptions.\n *\n * Rendered outside a provider, the returned client's scope accessors\n * throw a descriptive error whenever they are called.\n *\n * @example\n * ```tsx\n * const aui = useAui();\n *\n * const onSend = () => aui.composer().send();\n * const onCancel = () => aui.thread().cancelRun();\n * ```\n *\n * @example\n * ```tsx\n * // Combine with useAuiState to drive disabled state.\n * const aui = useAui();\n * const isRunning = useAuiState((s) => s.thread.isRunning);\n *\n * return (\n * <button disabled={isRunning} onClick={() => aui.composer().send()}>\n * Send\n * </button>\n * );\n * ```\n */\nexport function useAui(): AssistantClient;\n/**\n * Extends the parent `AssistantClient` with additional scopes.\n *\n * Advanced overload used when building primitives or providers — for example,\n * when a custom provider needs to register a `message`, `part`, or other scope\n * onto the client visible to its descendants. Application code rarely reaches\n * for this; use {@link useAui} with no arguments to read the existing client.\n *\n * @example\n * ```tsx\n * const aui = useAui({\n * message: Derived({\n * source: \"thread\",\n * query: { index: 0 },\n * get: (aui) => aui.thread().message({ index: 0 }),\n * }),\n * });\n *\n * const role = useAuiState((s) => s.message.role);\n * ```\n */\nexport function useAui(clients: useAui.Props): AssistantClient;\n/**\n * Extends an explicit parent `AssistantClient` with additional scopes.\n */\nexport function useAui(\n clients: useAui.Props,\n config: { parent: null | AssistantClient },\n): AssistantClient;\n/** @deprecated This API is highly experimental and may be changed in a minor release */\nexport function useAui(\n clients?: useAui.Props,\n { parent }: { parent: null | AssistantClient } = {\n parent: useAssistantContextValue(),\n },\n): AssistantClient {\n if (clients) {\n return useHostedAssistantClient({\n parent: parent ?? DefaultAssistantClient,\n clients,\n });\n }\n if (parent === null)\n throw new Error(\"received null parent, this usage is not allowed\");\n return parent;\n}\n"],"mappings":"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"} |
@@ -26,3 +26,3 @@ import { ClientMethods } from "./types/client.js"; | ||
| getKey: (data: TData) => string; | ||
| resource: ContravariantResource<TMethods, ResourceProps<TData>>; | ||
| resource: ContravariantResource<TMethods, [ResourceProps<TData>]>; | ||
| }; | ||
@@ -29,0 +29,0 @@ } |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"useClientList.d.ts","names":[],"sources":["../src/useClientList.ts"],"mappings":";;;;KAMK,gBAAA,aAA6B,QAAQ;EACxC,QAAA;AAAA,IAEE,CAAA;AAAA,cAwBS,aAAA,2BAAyC,aAAA,EACpD,KAAA,EAAO,aAAA,CAAc,KAAA,CAAM,KAAA,EAAO,QAAA;EAElC,KAAA,EAAO,gBAAA,CAAiB,QAAA;EACxB,GAAA,GAAM,MAAA;IAAU,KAAA;EAAA;IAAoB,GAAA;EAAA,MAAkB,QAAA;EACtD,GAAA,GAAM,WAAA,EAAa,KAAA;AAAA;AAAA,kBAuEJ,aAAA;EAAA,KACH,aAAA;IACV,GAAA;IACA,cAAA,QAAsB,KAAA;IACtB,MAAA;EAAA;EAAA,KAGU,KAAA,yBAA8B,aAAA;IACxC,aAAA,EAAe,KAAA;IACf,MAAA,GAAS,IAAA,EAAM,KAAA;IACf,QAAA,EAAU,qBAAA,CAAsB,QAAA,EAAU,aAAA,CAAc,KAAA;EAAA;AAAA"} | ||
| {"version":3,"file":"useClientList.d.ts","names":[],"sources":["../src/useClientList.ts"],"mappings":";;;;KAMK,gBAAA,aAA6B,QAAQ;EACxC,QAAA;AAAA,IAEE,CAAA;AAAA,cAwBS,aAAA,2BAAyC,aAAA,EACpD,KAAA,EAAO,aAAA,CAAc,KAAA,CAAM,KAAA,EAAO,QAAA;EAElC,KAAA,EAAO,gBAAA,CAAiB,QAAA;EACxB,GAAA,GAAM,MAAA;IAAU,KAAA;EAAA;IAAoB,GAAA;EAAA,MAAkB,QAAA;EACtD,GAAA,GAAM,WAAA,EAAa,KAAA;AAAA;AAAA,kBAuEJ,aAAA;EAAA,KACH,aAAA;IACV,GAAA;IACA,cAAA,QAAsB,KAAA;IACtB,MAAA;EAAA;EAAA,KAGU,KAAA,yBAA8B,aAAA;IACxC,aAAA,EAAe,KAAA;IACf,MAAA,GAAS,IAAA,EAAM,KAAA;IACf,QAAA,EAAU,qBAAA,CAAsB,QAAA,GAAW,aAAA,CAAc,KAAA;EAAA;AAAA"} |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"useClientList.js","names":[],"sources":["../src/useClientList.ts"],"sourcesContent":["import { useMemo, useState } from \"react\";\nimport { withKey, type ContravariantResource } from \"@assistant-ui/tap\";\n\nimport { useClientLookup } from \"./useClientLookup\";\nimport type { ClientMethods } from \"./types/client\";\n\ntype InferClientState<TMethods> = TMethods extends {\n getState: () => infer S;\n}\n ? S\n : unknown;\n\ntype DataHandle<TData> = { data: TData | undefined; hasData: boolean };\n\nconst createProps = <TData>(\n key: string,\n data: DataHandle<TData>,\n remove: () => void,\n): useClientList.ResourceProps<TData> => {\n return {\n key,\n getInitialData: () => {\n if (!data.hasData) {\n throw new Error(\n \"getInitialData may only be called during initial render\",\n );\n }\n return data.data!;\n },\n remove,\n };\n};\n\nexport const useClientList = <TData, TMethods extends ClientMethods>(\n props: useClientList.Props<TData, TMethods>,\n): {\n state: InferClientState<TMethods>[];\n get: (lookup: { index: number } | { key: string }) => TMethods;\n add: (initialData: TData) => void;\n} => {\n const { initialValues, getKey, resource: Resource } = props;\n\n type Props = useClientList.ResourceProps<TData>;\n\n const initialDataHandles: DataHandle<TData>[] = useMemo(() => [], []);\n\n const [items, setItems] = useState<Record<string, Props>>(() => {\n const entries: [string, Props][] = [];\n for (const data of initialValues) {\n const key = getKey(data);\n const handle = { data, hasData: true };\n entries.push([\n key,\n createProps(key, handle, () => {\n setItems((items) => {\n const newItems = { ...items };\n delete newItems[key];\n return newItems;\n });\n }),\n ]);\n initialDataHandles.push(handle);\n }\n return Object.fromEntries(entries);\n });\n\n const lookup = useClientLookup<TMethods>(\n () =>\n Object.values(items).map((props) => withKey(props.key, Resource(props))),\n [items, Resource],\n );\n\n initialDataHandles.forEach((handle) => {\n handle.data = undefined;\n handle.hasData = false;\n });\n\n const add = (data: TData) => {\n const key = getKey(data);\n setItems((items) => {\n if (key in items) {\n throw new Error(\n `Tried to add item with a key ${key} that already exists`,\n );\n }\n\n const handle = { data, hasData: true };\n initialDataHandles.push(handle);\n\n return {\n ...items,\n [key]: createProps(key, handle, () => {\n setItems((items) => {\n const newItems = { ...items };\n delete newItems[key];\n return newItems;\n });\n }),\n };\n });\n };\n\n return {\n state: lookup.state,\n get: lookup.get,\n add,\n };\n};\n\nexport namespace useClientList {\n export type ResourceProps<TData> = {\n key: string;\n getInitialData: () => TData;\n remove: () => void;\n };\n\n export type Props<TData, TMethods extends ClientMethods> = {\n initialValues: TData[];\n getKey: (data: TData) => string;\n resource: ContravariantResource<TMethods, ResourceProps<TData>>;\n };\n}\n"],"mappings":";;;;AAcA,MAAM,eACJ,KACA,MACA,WACuC;CACvC,OAAO;EACL;EACA,sBAAsB;GACpB,IAAI,CAAC,KAAK,SACR,MAAM,IAAI,MACR,yDACF;GAEF,OAAO,KAAK;EACd;EACA;CACF;AACF;AAEA,MAAa,iBACX,UAKG;CACH,MAAM,EAAE,eAAe,QAAQ,UAAU,aAAa;CAItD,MAAM,qBAA0C,cAAc,CAAC,GAAG,CAAC,CAAC;CAEpE,MAAM,CAAC,OAAO,YAAY,eAAsC;EAC9D,MAAM,UAA6B,CAAC;EACpC,KAAK,MAAM,QAAQ,eAAe;GAChC,MAAM,MAAM,OAAO,IAAI;GACvB,MAAM,SAAS;IAAE;IAAM,SAAS;GAAK;GACrC,QAAQ,KAAK,CACX,KACA,YAAY,KAAK,cAAc;IAC7B,UAAU,UAAU;KAClB,MAAM,WAAW,EAAE,GAAG,MAAM;KAC5B,OAAO,SAAS;KAChB,OAAO;IACT,CAAC;GACH,CAAC,CACH,CAAC;GACD,mBAAmB,KAAK,MAAM;EAChC;EACA,OAAO,OAAO,YAAY,OAAO;CACnC,CAAC;CAED,MAAM,SAAS,sBAEX,OAAO,OAAO,KAAK,CAAC,CAAC,KAAK,UAAU,QAAQ,MAAM,KAAK,SAAS,KAAK,CAAC,CAAC,GACzE,CAAC,OAAO,QAAQ,CAClB;CAEA,mBAAmB,SAAS,WAAW;EACrC,OAAO,OAAO,KAAA;EACd,OAAO,UAAU;CACnB,CAAC;CAED,MAAM,OAAO,SAAgB;EAC3B,MAAM,MAAM,OAAO,IAAI;EACvB,UAAU,UAAU;GAClB,IAAI,OAAO,OACT,MAAM,IAAI,MACR,gCAAgC,IAAI,qBACtC;GAGF,MAAM,SAAS;IAAE;IAAM,SAAS;GAAK;GACrC,mBAAmB,KAAK,MAAM;GAE9B,OAAO;IACL,GAAG;KACF,MAAM,YAAY,KAAK,cAAc;KACpC,UAAU,UAAU;MAClB,MAAM,WAAW,EAAE,GAAG,MAAM;MAC5B,OAAO,SAAS;MAChB,OAAO;KACT,CAAC;IACH,CAAC;GACH;EACF,CAAC;CACH;CAEA,OAAO;EACL,OAAO,OAAO;EACd,KAAK,OAAO;EACZ;CACF;AACF"} | ||
| {"version":3,"file":"useClientList.js","names":[],"sources":["../src/useClientList.ts"],"sourcesContent":["import { useMemo, useState } from \"react\";\nimport { withKey, type ContravariantResource } from \"@assistant-ui/tap\";\n\nimport { useClientLookup } from \"./useClientLookup\";\nimport type { ClientMethods } from \"./types/client\";\n\ntype InferClientState<TMethods> = TMethods extends {\n getState: () => infer S;\n}\n ? S\n : unknown;\n\ntype DataHandle<TData> = { data: TData | undefined; hasData: boolean };\n\nconst createProps = <TData>(\n key: string,\n data: DataHandle<TData>,\n remove: () => void,\n): useClientList.ResourceProps<TData> => {\n return {\n key,\n getInitialData: () => {\n if (!data.hasData) {\n throw new Error(\n \"getInitialData may only be called during initial render\",\n );\n }\n return data.data!;\n },\n remove,\n };\n};\n\nexport const useClientList = <TData, TMethods extends ClientMethods>(\n props: useClientList.Props<TData, TMethods>,\n): {\n state: InferClientState<TMethods>[];\n get: (lookup: { index: number } | { key: string }) => TMethods;\n add: (initialData: TData) => void;\n} => {\n const { initialValues, getKey, resource: Resource } = props;\n\n type Props = useClientList.ResourceProps<TData>;\n\n const initialDataHandles: DataHandle<TData>[] = useMemo(() => [], []);\n\n const [items, setItems] = useState<Record<string, Props>>(() => {\n const entries: [string, Props][] = [];\n for (const data of initialValues) {\n const key = getKey(data);\n const handle = { data, hasData: true };\n entries.push([\n key,\n createProps(key, handle, () => {\n setItems((items) => {\n const newItems = { ...items };\n delete newItems[key];\n return newItems;\n });\n }),\n ]);\n initialDataHandles.push(handle);\n }\n return Object.fromEntries(entries);\n });\n\n const lookup = useClientLookup<TMethods>(\n () =>\n Object.values(items).map((props) => withKey(props.key, Resource(props))),\n [items, Resource],\n );\n\n initialDataHandles.forEach((handle) => {\n handle.data = undefined;\n handle.hasData = false;\n });\n\n const add = (data: TData) => {\n const key = getKey(data);\n setItems((items) => {\n if (key in items) {\n throw new Error(\n `Tried to add item with a key ${key} that already exists`,\n );\n }\n\n const handle = { data, hasData: true };\n initialDataHandles.push(handle);\n\n return {\n ...items,\n [key]: createProps(key, handle, () => {\n setItems((items) => {\n const newItems = { ...items };\n delete newItems[key];\n return newItems;\n });\n }),\n };\n });\n };\n\n return {\n state: lookup.state,\n get: lookup.get,\n add,\n };\n};\n\nexport namespace useClientList {\n export type ResourceProps<TData> = {\n key: string;\n getInitialData: () => TData;\n remove: () => void;\n };\n\n export type Props<TData, TMethods extends ClientMethods> = {\n initialValues: TData[];\n getKey: (data: TData) => string;\n resource: ContravariantResource<TMethods, [ResourceProps<TData>]>;\n };\n}\n"],"mappings":";;;;AAcA,MAAM,eACJ,KACA,MACA,WACuC;CACvC,OAAO;EACL;EACA,sBAAsB;GACpB,IAAI,CAAC,KAAK,SACR,MAAM,IAAI,MACR,yDACF;GAEF,OAAO,KAAK;EACd;EACA;CACF;AACF;AAEA,MAAa,iBACX,UAKG;CACH,MAAM,EAAE,eAAe,QAAQ,UAAU,aAAa;CAItD,MAAM,qBAA0C,cAAc,CAAC,GAAG,CAAC,CAAC;CAEpE,MAAM,CAAC,OAAO,YAAY,eAAsC;EAC9D,MAAM,UAA6B,CAAC;EACpC,KAAK,MAAM,QAAQ,eAAe;GAChC,MAAM,MAAM,OAAO,IAAI;GACvB,MAAM,SAAS;IAAE;IAAM,SAAS;GAAK;GACrC,QAAQ,KAAK,CACX,KACA,YAAY,KAAK,cAAc;IAC7B,UAAU,UAAU;KAClB,MAAM,WAAW,EAAE,GAAG,MAAM;KAC5B,OAAO,SAAS;KAChB,OAAO;IACT,CAAC;GACH,CAAC,CACH,CAAC;GACD,mBAAmB,KAAK,MAAM;EAChC;EACA,OAAO,OAAO,YAAY,OAAO;CACnC,CAAC;CAED,MAAM,SAAS,sBAEX,OAAO,OAAO,KAAK,CAAC,CAAC,KAAK,UAAU,QAAQ,MAAM,KAAK,SAAS,KAAK,CAAC,CAAC,GACzE,CAAC,OAAO,QAAQ,CAClB;CAEA,mBAAmB,SAAS,WAAW;EACrC,OAAO,OAAO,KAAA;EACd,OAAO,UAAU;CACnB,CAAC;CAED,MAAM,OAAO,SAAgB;EAC3B,MAAM,MAAM,OAAO,IAAI;EACvB,UAAU,UAAU;GAClB,IAAI,OAAO,OACT,MAAM,IAAI,MACR,gCAAgC,IAAI,qBACtC;GAGF,MAAM,SAAS;IAAE;IAAM,SAAS;GAAK;GACrC,mBAAmB,KAAK,MAAM;GAE9B,OAAO;IACL,GAAG;KACF,MAAM,YAAY,KAAK,cAAc;KACpC,UAAU,UAAU;MAClB,MAAM,WAAW,EAAE,GAAG,MAAM;MAC5B,OAAO,SAAS;MAChB,OAAO;KACT,CAAC;IACH,CAAC;GACH;EACF,CAAC;CACH;CAEA,OAAO;EACL,OAAO,OAAO;EACd,KAAK,OAAO;EACZ;CACF;AACF"} |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"useClientLookup.d.ts","names":[],"sources":["../src/useClientLookup.ts"],"mappings":";;;;KAUK,gBAAA,aAA6B,QAAQ;EACxC,QAAA;AAAA,IAEE,CAAA;AAAA,iBAgBY,eAAA,kBAAiC,aAAA,EAC/C,WAAA,iBAA4B,eAAA,CAAgB,QAAA,KAC5C,eAAA;EAEA,KAAA,EAAO,gBAAA,CAAiB,QAAA;EACxB,GAAA,GAAM,MAAA;IAAU,KAAA;EAAA;IAAoB,GAAA;EAAA,MAAkB,QAAA;AAAA"} | ||
| {"version":3,"file":"useClientLookup.d.ts","names":[],"sources":["../src/useClientLookup.ts"],"mappings":";;;;KAKK,gBAAA,aAA6B,QAAQ;EACxC,QAAA;AAAA,IAEE,CAAA;AAAA,iBAUY,eAAA,kBAAiC,aAAA,EAC/C,WAAA,iBAA4B,eAAA,CAAgB,QAAA,KAC5C,eAAA;EAEA,KAAA,EAAO,gBAAA,CAAiB,QAAA;EACxB,GAAA,GAAM,MAAA;IAAU,KAAA;EAAA;IAAoB,GAAA;EAAA,MAAkB,QAAA;AAAA"} |
@@ -1,12 +0,11 @@ | ||
| import { wrapperResource } from "./wrapperResource.js"; | ||
| import { ClientResource } from "./useClientResource.js"; | ||
| import { useMemo } from "@assistant-ui/tap/react-shim"; | ||
| import { useResource, useResources } from "@assistant-ui/tap"; | ||
| import { useResources, withKey } from "@assistant-ui/tap"; | ||
| //#region src/useClientLookup.ts | ||
| const ClientResourceWithKey = wrapperResource((el) => { | ||
| if (el.key === void 0) throw new Error("useClientResource: Element has no key"); | ||
| return useResource(ClientResource(el)); | ||
| }); | ||
| const getElementKey = (el) => { | ||
| if (el.key === void 0) throw new Error("useClientLookup: Element has no key"); | ||
| return el.key; | ||
| }; | ||
| function useClientLookup(getElements, getElementsDeps) { | ||
| const resources = useResources(() => getElements().map((el) => ClientResourceWithKey(el)), getElementsDeps); | ||
| const resources = useResources(() => getElements().map((el) => withKey(getElementKey(el), ClientResource(el))), getElementsDeps); | ||
| const keys = useMemo(() => Object.keys(resources), [resources]); | ||
@@ -13,0 +12,0 @@ const keyToIndex = useMemo(() => { |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"useClientLookup.js","names":[],"sources":["../src/useClientLookup.ts"],"sourcesContent":["import { useMemo } from \"react\";\nimport {\n useResource,\n useResources,\n type ResourceElement,\n} from \"@assistant-ui/tap\";\nimport type { ClientMethods } from \"./types/client\";\nimport { ClientResource } from \"./useClientResource\";\nimport { wrapperResource } from \"./wrapperResource\";\n\ntype InferClientState<TMethods> = TMethods extends {\n getState: () => infer S;\n}\n ? S\n : unknown;\n\nconst ClientResourceWithKey = wrapperResource(\n <TMethods extends ClientMethods>(el: ResourceElement<TMethods>) => {\n if (el.key === undefined) {\n throw new Error(\"useClientResource: Element has no key\");\n }\n return useResource(ClientResource(el)) as {\n methods: TMethods;\n state: InferClientState<TMethods>;\n key: string | number;\n };\n },\n);\n\nexport function useClientLookup<TMethods extends ClientMethods>(\n getElements: () => readonly ResourceElement<TMethods>[],\n getElementsDeps: readonly unknown[],\n): {\n state: InferClientState<TMethods>[];\n get: (lookup: { index: number } | { key: string }) => TMethods;\n} {\n const resources = useResources(\n () => getElements().map((el) => ClientResourceWithKey(el)),\n // oxlint-disable-next-line react/exhaustive-deps -- caller-supplied deps array\n getElementsDeps,\n );\n\n const keys = useMemo(() => Object.keys(resources), [resources]);\n\n // For arrays, track element key -> index mapping\n const keyToIndex = useMemo(() => {\n return resources.reduce(\n (acc, resource, index) => {\n acc[resource.key] = index;\n return acc;\n },\n {} as Record<string, number>,\n );\n }, [resources]);\n\n const state = useMemo(() => {\n return resources.map((r) => r.state);\n }, [resources]);\n\n return {\n state,\n get: (lookup: { index: number } | { key: string }) => {\n if (\"index\" in lookup) {\n if (lookup.index < 0 || lookup.index >= keys.length) {\n throw new Error(\n `useClientLookup: Index ${lookup.index} out of bounds (length: ${keys.length})`,\n );\n }\n return resources[lookup.index]!.methods;\n }\n\n const index = keyToIndex[lookup.key];\n if (index === undefined) {\n throw new Error(`useClientLookup: Key \"${lookup.key}\" not found`);\n }\n return resources[index]!.methods;\n },\n };\n}\n"],"mappings":";;;;;AAgBA,MAAM,wBAAwB,iBACK,OAAkC;CACjE,IAAI,GAAG,QAAQ,KAAA,GACb,MAAM,IAAI,MAAM,uCAAuC;CAEzD,OAAO,YAAY,eAAe,EAAE,CAAC;AAKvC,CACF;AAEA,SAAgB,gBACd,aACA,iBAIA;CACA,MAAM,YAAY,mBACV,YAAY,CAAC,CAAC,KAAK,OAAO,sBAAsB,EAAE,CAAC,GAEzD,eACF;CAEA,MAAM,OAAO,cAAc,OAAO,KAAK,SAAS,GAAG,CAAC,SAAS,CAAC;CAG9D,MAAM,aAAa,cAAc;EAC/B,OAAO,UAAU,QACd,KAAK,UAAU,UAAU;GACxB,IAAI,SAAS,OAAO;GACpB,OAAO;EACT,GACA,CAAC,CACH;CACF,GAAG,CAAC,SAAS,CAAC;CAMd,OAAO;EACL,OALY,cAAc;GAC1B,OAAO,UAAU,KAAK,MAAM,EAAE,KAAK;EACrC,GAAG,CAAC,SAAS,CAGP;EACJ,MAAM,WAAgD;GACpD,IAAI,WAAW,QAAQ;IACrB,IAAI,OAAO,QAAQ,KAAK,OAAO,SAAS,KAAK,QAC3C,MAAM,IAAI,MACR,0BAA0B,OAAO,MAAM,0BAA0B,KAAK,OAAO,EAC/E;IAEF,OAAO,UAAU,OAAO,MAAM,CAAE;GAClC;GAEA,MAAM,QAAQ,WAAW,OAAO;GAChC,IAAI,UAAU,KAAA,GACZ,MAAM,IAAI,MAAM,yBAAyB,OAAO,IAAI,YAAY;GAElE,OAAO,UAAU,MAAM,CAAE;EAC3B;CACF;AACF"} | ||
| {"version":3,"file":"useClientLookup.js","names":[],"sources":["../src/useClientLookup.ts"],"sourcesContent":["import { useMemo } from \"react\";\nimport { useResources, withKey, type ResourceElement } from \"@assistant-ui/tap\";\nimport type { ClientMethods } from \"./types/client\";\nimport { ClientResource } from \"./useClientResource\";\n\ntype InferClientState<TMethods> = TMethods extends {\n getState: () => infer S;\n}\n ? S\n : unknown;\n\nconst getElementKey = (el: ResourceElement<unknown>) => {\n if (el.key === undefined) {\n throw new Error(\"useClientLookup: Element has no key\");\n }\n return el.key;\n};\n\nexport function useClientLookup<TMethods extends ClientMethods>(\n getElements: () => readonly ResourceElement<TMethods>[],\n getElementsDeps: readonly unknown[],\n): {\n state: InferClientState<TMethods>[];\n get: (lookup: { index: number } | { key: string }) => TMethods;\n} {\n const resources = useResources(\n () =>\n getElements().map((el) => withKey(getElementKey(el), ClientResource(el))),\n // oxlint-disable-next-line react/exhaustive-deps -- caller-supplied deps array\n getElementsDeps,\n );\n\n const keys = useMemo(() => Object.keys(resources), [resources]);\n\n // For arrays, track element key -> index mapping\n const keyToIndex = useMemo(() => {\n return resources.reduce(\n (acc, resource, index) => {\n acc[resource.key!] = index;\n return acc;\n },\n {} as Record<string, number>,\n );\n }, [resources]);\n\n const state = useMemo(() => {\n return resources.map((r) => r.state);\n }, [resources]);\n\n return {\n state,\n get: (lookup: { index: number } | { key: string }) => {\n if (\"index\" in lookup) {\n if (lookup.index < 0 || lookup.index >= keys.length) {\n throw new Error(\n `useClientLookup: Index ${lookup.index} out of bounds (length: ${keys.length})`,\n );\n }\n return resources[lookup.index]!.methods;\n }\n\n const index = keyToIndex[lookup.key];\n if (index === undefined) {\n throw new Error(`useClientLookup: Key \"${lookup.key}\" not found`);\n }\n return resources[index]!.methods;\n },\n };\n}\n"],"mappings":";;;;AAWA,MAAM,iBAAiB,OAAiC;CACtD,IAAI,GAAG,QAAQ,KAAA,GACb,MAAM,IAAI,MAAM,qCAAqC;CAEvD,OAAO,GAAG;AACZ;AAEA,SAAgB,gBACd,aACA,iBAIA;CACA,MAAM,YAAY,mBAEd,YAAY,CAAC,CAAC,KAAK,OAAO,QAAQ,cAAc,EAAE,GAAG,eAAe,EAAE,CAAC,CAAC,GAE1E,eACF;CAEA,MAAM,OAAO,cAAc,OAAO,KAAK,SAAS,GAAG,CAAC,SAAS,CAAC;CAG9D,MAAM,aAAa,cAAc;EAC/B,OAAO,UAAU,QACd,KAAK,UAAU,UAAU;GACxB,IAAI,SAAS,OAAQ;GACrB,OAAO;EACT,GACA,CAAC,CACH;CACF,GAAG,CAAC,SAAS,CAAC;CAMd,OAAO;EACL,OALY,cAAc;GAC1B,OAAO,UAAU,KAAK,MAAM,EAAE,KAAK;EACrC,GAAG,CAAC,SAAS,CAGP;EACJ,MAAM,WAAgD;GACpD,IAAI,WAAW,QAAQ;IACrB,IAAI,OAAO,QAAQ,KAAK,OAAO,SAAS,KAAK,QAC3C,MAAM,IAAI,MACR,0BAA0B,OAAO,MAAM,0BAA0B,KAAK,OAAO,EAC/E;IAEF,OAAO,UAAU,OAAO,MAAM,CAAE;GAClC;GAEA,MAAM,QAAQ,WAAW,OAAO;GAChC,IAAI,UAAU,KAAA,GACZ,MAAM,IAAI,MAAM,yBAAyB,OAAO,IAAI,YAAY;GAElE,OAAO,UAAU,MAAM,CAAE;EAC3B;CACF;AACF"} |
@@ -14,4 +14,9 @@ import { ClientMethods } from "./types/client.js"; | ||
| }; | ||
| declare const ClientResource: <TMethods extends ClientMethods>(element: ResourceElement<TMethods>) => ResourceElement<{ | ||
| state: InferClientState<TMethods>; | ||
| methods: TMethods; | ||
| key: string | number | undefined; | ||
| }, [element: ResourceElement<TMethods>]>; | ||
| //#endregion | ||
| export { getClientState, useClientResource }; | ||
| export { ClientResource, getClientState, useClientResource }; | ||
| //# sourceMappingURL=useClientResource.d.ts.map |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"useClientResource.d.ts","names":[],"sources":["../src/useClientResource.ts"],"mappings":";;;;cAwBa,cAAA,GAAkB,MAAqB,EAAb,aAAa;AAAA,KA6I/C,gBAAA,aAA6B,QAAQ;EACxC,QAAA;AAAA,IAEE,CAAA;AAAA,cAGS,iBAAA,oBAAsC,aAAA,EACjD,OAAA,EAAS,eAAA,CAAgB,QAAA;EAEzB,KAAA,EAAO,gBAAA,CAAiB,QAAA;EACxB,OAAA,EAAS,QAAA;EACT,GAAA;AAAA"} | ||
| {"version":3,"file":"useClientResource.d.ts","names":[],"sources":["../src/useClientResource.ts"],"mappings":";;;;cAuBa,cAAA,GAAkB,MAAqB,EAAb,aAAa;AAAA,KAkG/C,gBAAA,aAA6B,QAAQ;EACxC,QAAA;AAAA,IAEE,CAAA;AAAA,cAGS,iBAAA,oBAAsC,aAAA,EACjD,OAAA,EAAS,eAAA,CAAgB,QAAA;EAEzB,KAAA,EAAO,gBAAA,CAAiB,QAAA;EACxB,OAAA,EAAS,QAAA;EACT,GAAA;AAAA;AAAA,cA8BW,cAAA,oBAnCsC,aAAA,EAAa,OAAA,EAAA,eAAA,CAAA,QAAA,MAAA,eAAA;SAGvD,gBAAA,CAAiB,QAAA;WACf,QAAA"} |
| import { SYMBOL_CLIENT_INDEX, useClientStack, useWithClientStack } from "./utils/tap-client-stack-context.js"; | ||
| import { BaseProxyHandler, handleIntrospectionProp } from "./utils/BaseProxyHandler.js"; | ||
| import { wrapperResource } from "./wrapperResource.js"; | ||
| import { useEffect, useMemo, useRef } from "@assistant-ui/tap/react-shim"; | ||
| import { useResource } from "@assistant-ui/tap"; | ||
| import { resource, useResource } from "@assistant-ui/tap"; | ||
| //#region src/useClientResource.ts | ||
@@ -73,16 +72,9 @@ /** | ||
| }; | ||
| /** | ||
| * Resource that wraps a plain resource element to create a stable client proxy. | ||
| * | ||
| * Takes a ResourceElement that returns methods (with optional getState()) and | ||
| * wraps it to produce a stable client proxy. This adds the client to the | ||
| * client stack, enabling event scoping. | ||
| * | ||
| * @internal | ||
| */ | ||
| const ClientResource = wrapperResource((element) => { | ||
| const useClientResource = (element) => { | ||
| const valueRef = useRef(null); | ||
| const index = useClientStack().length; | ||
| const methods = useMemo(() => new Proxy({}, new ClientProxyHandler(valueRef, index)), [index]); | ||
| const value = useWithClientStack(methods, () => useResource(element)); | ||
| const value = useWithClientStack(methods, function WithClientStack() { | ||
| return useResource(element); | ||
| }); | ||
| if (!valueRef.current) valueRef.current = value; | ||
@@ -97,6 +89,4 @@ useEffect(() => { | ||
| }; | ||
| }); | ||
| const useClientResource = (element) => { | ||
| return useResource(ClientResource(element)); | ||
| }; | ||
| const ClientResource = resource(useClientResource); | ||
| //#endregion | ||
@@ -103,0 +93,0 @@ export { ClientResource, getClientState, useClientResource }; |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"useClientResource.js","names":[],"sources":["../src/useClientResource.ts"],"sourcesContent":["import { useEffect, useMemo, useRef } from \"react\";\nimport { useResource, type ResourceElement } from \"@assistant-ui/tap\";\nimport type { ClientMethods } from \"./types/client\";\nimport {\n useClientStack,\n useWithClientStack,\n SYMBOL_CLIENT_INDEX,\n} from \"./utils/tap-client-stack-context\";\nimport {\n BaseProxyHandler,\n handleIntrospectionProp,\n} from \"./utils/BaseProxyHandler\";\nimport { wrapperResource } from \"./wrapperResource\";\n\n/**\n * Symbol used internally to get state from ClientProxy.\n * This allows getState() to be optional in the user-facing client.\n */\nconst SYMBOL_GET_OUTPUT = Symbol(\"assistant-ui.store.getValue\");\n\ntype ClientInternal = {\n [SYMBOL_GET_OUTPUT]: ClientMethods;\n};\n\nexport const getClientState = (client: ClientMethods) => {\n const output = (client as unknown as ClientInternal)[SYMBOL_GET_OUTPUT];\n if (!output) {\n throw new Error(\n \"Client scope contains a non-client resource. \" +\n \"Ensure your Derived get() returns a client created with useClientResource(), not a plain resource.\",\n );\n }\n return (output as any).getState?.();\n};\n\n// Global cache for function templates by field name\nconst fieldAccessFns = new Map<\n string | symbol,\n (this: unknown, ...args: unknown[]) => unknown\n>();\n\nfunction getOrCreateProxyFn(prop: string | symbol) {\n let template = fieldAccessFns.get(prop);\n if (!template) {\n template = function (this: unknown, ...args: unknown[]) {\n if (!this || typeof this !== \"object\") {\n throw new Error(\n `Method \"${String(prop)}\" called without proper context. ` +\n `This may indicate the function was called incorrectly.`,\n );\n }\n\n const output = (this as ClientInternal)[SYMBOL_GET_OUTPUT];\n if (!output) {\n throw new Error(\n `Method \"${String(prop)}\" called on invalid client proxy. ` +\n `Ensure you are calling this method on a valid client instance.`,\n );\n }\n\n const method = output[prop];\n if (!method)\n throw new Error(`Method \"${String(prop)}\" is not implemented.`);\n if (typeof method !== \"function\")\n throw new Error(`\"${String(prop)}\" is not a function.`);\n return method(...args);\n };\n fieldAccessFns.set(prop, template);\n }\n return template;\n}\n\nclass ClientProxyHandler\n extends BaseProxyHandler\n implements ProxyHandler<object>\n{\n private boundFns:\n | Map<string | symbol, (...args: never) => unknown>\n | undefined;\n private cachedReceiver: unknown;\n\n constructor(\n private readonly outputRef: {\n current: ClientMethods;\n },\n private readonly index: number,\n ) {\n super();\n }\n\n get(_: unknown, prop: string | symbol, receiver: unknown) {\n if (prop === SYMBOL_GET_OUTPUT) return this.outputRef.current;\n if (prop === SYMBOL_CLIENT_INDEX) return this.index;\n const introspection = handleIntrospectionProp(prop, \"ClientProxy\");\n if (introspection !== false) return introspection;\n const value = this.outputRef.current[prop];\n if (typeof value === \"function\") {\n if (this.cachedReceiver !== receiver) {\n this.boundFns = new Map();\n this.cachedReceiver = receiver;\n }\n let bound = this.boundFns!.get(prop);\n if (!bound) {\n bound = getOrCreateProxyFn(prop).bind(receiver);\n this.boundFns!.set(prop, bound);\n }\n return bound;\n }\n return value;\n }\n\n ownKeys(): ArrayLike<string | symbol> {\n return Object.keys(this.outputRef.current);\n }\n\n has(_: unknown, prop: string | symbol) {\n if (prop === SYMBOL_GET_OUTPUT) return true;\n if (prop === SYMBOL_CLIENT_INDEX) return true;\n return prop in this.outputRef.current;\n }\n}\n\n/**\n * Resource that wraps a plain resource element to create a stable client proxy.\n *\n * Takes a ResourceElement that returns methods (with optional getState()) and\n * wraps it to produce a stable client proxy. This adds the client to the\n * client stack, enabling event scoping.\n *\n * @internal\n */\nexport const ClientResource = wrapperResource(\n <TMethods extends ClientMethods>(\n element: ResourceElement<TMethods>,\n ): {\n methods: TMethods;\n state: unknown;\n key: string | number | undefined;\n } => {\n const valueRef = useRef(null as unknown as TMethods);\n\n const index = useClientStack().length;\n const methods = useMemo(\n () =>\n new Proxy<TMethods>(\n {} as TMethods,\n new ClientProxyHandler(valueRef, index),\n ),\n [index],\n );\n\n const value = useWithClientStack(methods, () => useResource(element));\n if (!valueRef.current) {\n valueRef.current = value;\n }\n\n useEffect(() => {\n valueRef.current = value;\n });\n\n const state = (value as any).getState?.();\n return { methods, state, key: element.key };\n },\n);\n\ntype InferClientState<TMethods> = TMethods extends {\n getState: () => infer S;\n}\n ? S\n : undefined;\n\nexport const useClientResource = <TMethods extends ClientMethods>(\n element: ResourceElement<TMethods>,\n): {\n state: InferClientState<TMethods>;\n methods: TMethods;\n key: string | number | undefined;\n} => {\n return useResource(ClientResource(element)) as {\n state: InferClientState<TMethods>;\n methods: TMethods;\n key: string | number | undefined;\n };\n};\n"],"mappings":";;;;;;;;;;AAkBA,MAAM,oBAAoB,OAAO,6BAA6B;AAM9D,MAAa,kBAAkB,WAA0B;CACvD,MAAM,SAAU,OAAqC;CACrD,IAAI,CAAC,QACH,MAAM,IAAI,MACR,iJAEF;CAEF,OAAQ,OAAe,WAAW;AACpC;AAGA,MAAM,iCAAiB,IAAI,IAGzB;AAEF,SAAS,mBAAmB,MAAuB;CACjD,IAAI,WAAW,eAAe,IAAI,IAAI;CACtC,IAAI,CAAC,UAAU;EACb,WAAW,SAAyB,GAAG,MAAiB;GACtD,IAAI,CAAC,QAAQ,OAAO,SAAS,UAC3B,MAAM,IAAI,MACR,WAAW,OAAO,IAAI,EAAE,wFAE1B;GAGF,MAAM,SAAU,KAAwB;GACxC,IAAI,CAAC,QACH,MAAM,IAAI,MACR,WAAW,OAAO,IAAI,EAAE,iGAE1B;GAGF,MAAM,SAAS,OAAO;GACtB,IAAI,CAAC,QACH,MAAM,IAAI,MAAM,WAAW,OAAO,IAAI,EAAE,sBAAsB;GAChE,IAAI,OAAO,WAAW,YACpB,MAAM,IAAI,MAAM,IAAI,OAAO,IAAI,EAAE,qBAAqB;GACxD,OAAO,OAAO,GAAG,IAAI;EACvB;EACA,eAAe,IAAI,MAAM,QAAQ;CACnC;CACA,OAAO;AACT;AAEA,IAAM,qBAAN,cACU,iBAEV;CAOqB;CAGA;CATnB;CAGA;CAEA,YACE,WAGA,OACA;EACA,MAAM;EALW,KAAA,YAAA;EAGA,KAAA,QAAA;CAGnB;CAEA,IAAI,GAAY,MAAuB,UAAmB;EACxD,IAAI,SAAS,mBAAmB,OAAO,KAAK,UAAU;EACtD,IAAI,SAAS,qBAAqB,OAAO,KAAK;EAC9C,MAAM,gBAAgB,wBAAwB,MAAM,aAAa;EACjE,IAAI,kBAAkB,OAAO,OAAO;EACpC,MAAM,QAAQ,KAAK,UAAU,QAAQ;EACrC,IAAI,OAAO,UAAU,YAAY;GAC/B,IAAI,KAAK,mBAAmB,UAAU;IACpC,KAAK,2BAAW,IAAI,IAAI;IACxB,KAAK,iBAAiB;GACxB;GACA,IAAI,QAAQ,KAAK,SAAU,IAAI,IAAI;GACnC,IAAI,CAAC,OAAO;IACV,QAAQ,mBAAmB,IAAI,CAAC,CAAC,KAAK,QAAQ;IAC9C,KAAK,SAAU,IAAI,MAAM,KAAK;GAChC;GACA,OAAO;EACT;EACA,OAAO;CACT;CAEA,UAAsC;EACpC,OAAO,OAAO,KAAK,KAAK,UAAU,OAAO;CAC3C;CAEA,IAAI,GAAY,MAAuB;EACrC,IAAI,SAAS,mBAAmB,OAAO;EACvC,IAAI,SAAS,qBAAqB,OAAO;EACzC,OAAO,QAAQ,KAAK,UAAU;CAChC;AACF;;;;;;;;;;AAWA,MAAa,iBAAiB,iBAE1B,YAKG;CACH,MAAM,WAAW,OAAO,IAA2B;CAEnD,MAAM,QAAQ,eAAe,CAAC,CAAC;CAC/B,MAAM,UAAU,cAEZ,IAAI,MACF,CAAC,GACD,IAAI,mBAAmB,UAAU,KAAK,CACxC,GACF,CAAC,KAAK,CACR;CAEA,MAAM,QAAQ,mBAAmB,eAAe,YAAY,OAAO,CAAC;CACpE,IAAI,CAAC,SAAS,SACZ,SAAS,UAAU;CAGrB,gBAAgB;EACd,SAAS,UAAU;CACrB,CAAC;CAGD,OAAO;EAAE;EAAS,OADH,MAAc,WAAW;EACf,KAAK,QAAQ;CAAI;AAC5C,CACF;AAQA,MAAa,qBACX,YAKG;CACH,OAAO,YAAY,eAAe,OAAO,CAAC;AAK5C"} | ||
| {"version":3,"file":"useClientResource.js","names":[],"sources":["../src/useClientResource.ts"],"sourcesContent":["import { useEffect, useMemo, useRef } from \"react\";\nimport { resource, useResource, type ResourceElement } from \"@assistant-ui/tap\";\nimport type { ClientMethods } from \"./types/client\";\nimport {\n useClientStack,\n useWithClientStack,\n SYMBOL_CLIENT_INDEX,\n} from \"./utils/tap-client-stack-context\";\nimport {\n BaseProxyHandler,\n handleIntrospectionProp,\n} from \"./utils/BaseProxyHandler\";\n\n/**\n * Symbol used internally to get state from ClientProxy.\n * This allows getState() to be optional in the user-facing client.\n */\nconst SYMBOL_GET_OUTPUT = Symbol(\"assistant-ui.store.getValue\");\n\ntype ClientInternal = {\n [SYMBOL_GET_OUTPUT]: ClientMethods;\n};\n\nexport const getClientState = (client: ClientMethods) => {\n const output = (client as unknown as ClientInternal)[SYMBOL_GET_OUTPUT];\n if (!output) {\n throw new Error(\n \"Client scope contains a non-client resource. \" +\n \"Ensure your Derived get() returns a client created with useClientResource(), not a plain resource.\",\n );\n }\n return (output as any).getState?.();\n};\n\n// Global cache for function templates by field name\nconst fieldAccessFns = new Map<\n string | symbol,\n (this: unknown, ...args: unknown[]) => unknown\n>();\n\nfunction getOrCreateProxyFn(prop: string | symbol) {\n let template = fieldAccessFns.get(prop);\n if (!template) {\n template = function (this: unknown, ...args: unknown[]) {\n if (!this || typeof this !== \"object\") {\n throw new Error(\n `Method \"${String(prop)}\" called without proper context. ` +\n `This may indicate the function was called incorrectly.`,\n );\n }\n\n const output = (this as ClientInternal)[SYMBOL_GET_OUTPUT];\n if (!output) {\n throw new Error(\n `Method \"${String(prop)}\" called on invalid client proxy. ` +\n `Ensure you are calling this method on a valid client instance.`,\n );\n }\n\n const method = output[prop];\n if (!method)\n throw new Error(`Method \"${String(prop)}\" is not implemented.`);\n if (typeof method !== \"function\")\n throw new Error(`\"${String(prop)}\" is not a function.`);\n return method(...args);\n };\n fieldAccessFns.set(prop, template);\n }\n return template;\n}\n\nclass ClientProxyHandler\n extends BaseProxyHandler\n implements ProxyHandler<object>\n{\n private boundFns:\n | Map<string | symbol, (...args: never) => unknown>\n | undefined;\n private cachedReceiver: unknown;\n\n constructor(\n private readonly outputRef: {\n current: ClientMethods;\n },\n private readonly index: number,\n ) {\n super();\n }\n\n get(_: unknown, prop: string | symbol, receiver: unknown) {\n if (prop === SYMBOL_GET_OUTPUT) return this.outputRef.current;\n if (prop === SYMBOL_CLIENT_INDEX) return this.index;\n const introspection = handleIntrospectionProp(prop, \"ClientProxy\");\n if (introspection !== false) return introspection;\n const value = this.outputRef.current[prop];\n if (typeof value === \"function\") {\n if (this.cachedReceiver !== receiver) {\n this.boundFns = new Map();\n this.cachedReceiver = receiver;\n }\n let bound = this.boundFns!.get(prop);\n if (!bound) {\n bound = getOrCreateProxyFn(prop).bind(receiver);\n this.boundFns!.set(prop, bound);\n }\n return bound;\n }\n return value;\n }\n\n ownKeys(): ArrayLike<string | symbol> {\n return Object.keys(this.outputRef.current);\n }\n\n has(_: unknown, prop: string | symbol) {\n if (prop === SYMBOL_GET_OUTPUT) return true;\n if (prop === SYMBOL_CLIENT_INDEX) return true;\n return prop in this.outputRef.current;\n }\n}\n\ntype InferClientState<TMethods> = TMethods extends {\n getState: () => infer S;\n}\n ? S\n : undefined;\n\nexport const useClientResource = <TMethods extends ClientMethods>(\n element: ResourceElement<TMethods>,\n): {\n state: InferClientState<TMethods>;\n methods: TMethods;\n key: string | number | undefined;\n} => {\n const valueRef = useRef(null as unknown as TMethods);\n\n const index = useClientStack().length;\n const methods = useMemo(\n () =>\n new Proxy<TMethods>(\n {} as TMethods,\n new ClientProxyHandler(valueRef, index),\n ),\n [index],\n );\n\n const value = useWithClientStack(methods, function WithClientStack() {\n return useResource(element);\n });\n\n if (!valueRef.current) {\n valueRef.current = value;\n }\n\n useEffect(() => {\n valueRef.current = value;\n });\n\n const state = (value as any).getState?.();\n return { methods, state, key: element.key };\n};\n\nexport const ClientResource = resource(useClientResource);\n"],"mappings":";;;;;;;;;AAiBA,MAAM,oBAAoB,OAAO,6BAA6B;AAM9D,MAAa,kBAAkB,WAA0B;CACvD,MAAM,SAAU,OAAqC;CACrD,IAAI,CAAC,QACH,MAAM,IAAI,MACR,iJAEF;CAEF,OAAQ,OAAe,WAAW;AACpC;AAGA,MAAM,iCAAiB,IAAI,IAGzB;AAEF,SAAS,mBAAmB,MAAuB;CACjD,IAAI,WAAW,eAAe,IAAI,IAAI;CACtC,IAAI,CAAC,UAAU;EACb,WAAW,SAAyB,GAAG,MAAiB;GACtD,IAAI,CAAC,QAAQ,OAAO,SAAS,UAC3B,MAAM,IAAI,MACR,WAAW,OAAO,IAAI,EAAE,wFAE1B;GAGF,MAAM,SAAU,KAAwB;GACxC,IAAI,CAAC,QACH,MAAM,IAAI,MACR,WAAW,OAAO,IAAI,EAAE,iGAE1B;GAGF,MAAM,SAAS,OAAO;GACtB,IAAI,CAAC,QACH,MAAM,IAAI,MAAM,WAAW,OAAO,IAAI,EAAE,sBAAsB;GAChE,IAAI,OAAO,WAAW,YACpB,MAAM,IAAI,MAAM,IAAI,OAAO,IAAI,EAAE,qBAAqB;GACxD,OAAO,OAAO,GAAG,IAAI;EACvB;EACA,eAAe,IAAI,MAAM,QAAQ;CACnC;CACA,OAAO;AACT;AAEA,IAAM,qBAAN,cACU,iBAEV;CAOqB;CAGA;CATnB;CAGA;CAEA,YACE,WAGA,OACA;EACA,MAAM;EALW,KAAA,YAAA;EAGA,KAAA,QAAA;CAGnB;CAEA,IAAI,GAAY,MAAuB,UAAmB;EACxD,IAAI,SAAS,mBAAmB,OAAO,KAAK,UAAU;EACtD,IAAI,SAAS,qBAAqB,OAAO,KAAK;EAC9C,MAAM,gBAAgB,wBAAwB,MAAM,aAAa;EACjE,IAAI,kBAAkB,OAAO,OAAO;EACpC,MAAM,QAAQ,KAAK,UAAU,QAAQ;EACrC,IAAI,OAAO,UAAU,YAAY;GAC/B,IAAI,KAAK,mBAAmB,UAAU;IACpC,KAAK,2BAAW,IAAI,IAAI;IACxB,KAAK,iBAAiB;GACxB;GACA,IAAI,QAAQ,KAAK,SAAU,IAAI,IAAI;GACnC,IAAI,CAAC,OAAO;IACV,QAAQ,mBAAmB,IAAI,CAAC,CAAC,KAAK,QAAQ;IAC9C,KAAK,SAAU,IAAI,MAAM,KAAK;GAChC;GACA,OAAO;EACT;EACA,OAAO;CACT;CAEA,UAAsC;EACpC,OAAO,OAAO,KAAK,KAAK,UAAU,OAAO;CAC3C;CAEA,IAAI,GAAY,MAAuB;EACrC,IAAI,SAAS,mBAAmB,OAAO;EACvC,IAAI,SAAS,qBAAqB,OAAO;EACzC,OAAO,QAAQ,KAAK,UAAU;CAChC;AACF;AAQA,MAAa,qBACX,YAKG;CACH,MAAM,WAAW,OAAO,IAA2B;CAEnD,MAAM,QAAQ,eAAe,CAAC,CAAC;CAC/B,MAAM,UAAU,cAEZ,IAAI,MACF,CAAC,GACD,IAAI,mBAAmB,UAAU,KAAK,CACxC,GACF,CAAC,KAAK,CACR;CAEA,MAAM,QAAQ,mBAAmB,SAAS,SAAS,kBAAkB;EACnE,OAAO,YAAY,OAAO;CAC5B,CAAC;CAED,IAAI,CAAC,SAAS,SACZ,SAAS,UAAU;CAGrB,gBAAgB;EACd,SAAS,UAAU;CACrB,CAAC;CAGD,OAAO;EAAE;EAAS,OADH,MAAc,WAAW;EACf,KAAK,QAAQ;CAAI;AAC5C;AAEA,MAAa,iBAAiB,SAAS,iBAAiB"} |
@@ -12,5 +12,5 @@ import { AssistantEventName, AssistantEventPayload } from "../types/events.js"; | ||
| }; | ||
| declare const NotificationManager: () => import("@assistant-ui/tap").ResourceElement<NotificationManager, undefined>; | ||
| declare const NotificationManager: import("@assistant-ui/tap").Resource<NotificationManager, []>; | ||
| //#endregion | ||
| export { NotificationManager }; | ||
| //# sourceMappingURL=NotificationManager.d.ts.map |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"NotificationManager.d.ts","names":[],"sources":["../../src/utils/NotificationManager.ts"],"mappings":";;;;;KAWY,mBAAA;EACV,EAAA,gBAAkB,kBAAA,EAChB,KAAA,EAAO,MAAA,EACP,QAAA,GACE,OAAA,EAAS,qBAAA,CAAsB,MAAA,GAC/B,WAAA,EAAa,WAAA,YAEd,WAAA;EACH,IAAA,gBAAoB,OAAA,CAAQ,kBAAA,QAC1B,KAAA,EAAO,MAAA,EACP,OAAA,EAAS,qBAAA,CAAsB,MAAA,GAC/B,WAAA,EAAa,WAAA;EAEf,SAAA,CAAU,QAAA,eAAuB,WAAA;EACjC,iBAAA;AAAA;AAAA,cAGW,mBAAA,oCAAmB,eAAA,CAAA,mBAAA"} | ||
| {"version":3,"file":"NotificationManager.d.ts","names":[],"sources":["../../src/utils/NotificationManager.ts"],"mappings":";;;;;KAWY,mBAAA;EACV,EAAA,gBAAkB,kBAAA,EAChB,KAAA,EAAO,MAAA,EACP,QAAA,GACE,OAAA,EAAS,qBAAA,CAAsB,MAAA,GAC/B,WAAA,EAAa,WAAA,YAEd,WAAA;EACH,IAAA,gBAAoB,OAAA,CAAQ,kBAAA,QAC1B,KAAA,EAAO,MAAA,EACP,OAAA,EAAS,qBAAA,CAAsB,MAAA,GAC/B,WAAA,EAAa,WAAA;EAEf,SAAA,CAAU,QAAA,eAAuB,WAAA;EACjC,iBAAA;AAAA;AAAA,cA0FW,mBAAA,8BAAmB,QAAA,CAAA,mBAAA"} |
| import { useMemo } from "@assistant-ui/tap/react-shim"; | ||
| import { resource } from "@assistant-ui/tap"; | ||
| //#region src/utils/NotificationManager.ts | ||
| const NotificationManager = resource(function NotificationManager() { | ||
| const useNotificationManager = () => { | ||
| return useMemo(() => { | ||
@@ -68,3 +68,4 @@ const listeners = /* @__PURE__ */ new Map(); | ||
| }, []); | ||
| }); | ||
| }; | ||
| const NotificationManager = resource(useNotificationManager); | ||
| //#endregion | ||
@@ -71,0 +72,0 @@ export { NotificationManager }; |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"NotificationManager.js","names":[],"sources":["../../src/utils/NotificationManager.ts"],"sourcesContent":["import { useMemo } from \"react\";\nimport { resource } from \"@assistant-ui/tap\";\nimport type { ClientStack } from \"./tap-client-stack-context\";\nimport type {\n AssistantEventName,\n AssistantEventPayload,\n} from \"../types/events\";\nimport type { Unsubscribe } from \"../types/client\";\n\ntype InternalCallback = (payload: unknown, clientStack: ClientStack) => void;\n\nexport type NotificationManager = {\n on<TEvent extends AssistantEventName>(\n event: TEvent,\n callback: (\n payload: AssistantEventPayload[TEvent],\n clientStack: ClientStack,\n ) => void,\n ): Unsubscribe;\n emit<TEvent extends Exclude<AssistantEventName, \"*\">>(\n event: TEvent,\n payload: AssistantEventPayload[TEvent],\n clientStack: ClientStack,\n ): void;\n subscribe(callback: () => void): Unsubscribe;\n notifySubscribers(): void;\n};\n\nexport const NotificationManager = resource(\n function NotificationManager(): NotificationManager {\n return useMemo(() => {\n const listeners = new Map<string, Set<InternalCallback>>();\n const wildcardListeners = new Set<InternalCallback>();\n const subscribers = new Set<() => void>();\n\n return {\n on(event, callback) {\n const cb = callback as InternalCallback;\n if (event === \"*\") {\n wildcardListeners.add(cb);\n return () => wildcardListeners.delete(cb);\n }\n\n let set = listeners.get(event);\n if (!set) {\n set = new Set();\n listeners.set(event, set);\n }\n set.add(cb);\n\n return () => {\n set!.delete(cb);\n if (set!.size === 0) listeners.delete(event);\n };\n },\n\n emit(event, payload, clientStack) {\n const eventListeners = listeners.get(event);\n if (!eventListeners && wildcardListeners.size === 0) return;\n\n queueMicrotask(() => {\n const errors = [];\n if (eventListeners) {\n for (const cb of eventListeners) {\n try {\n cb(payload, clientStack);\n } catch (e) {\n errors.push(e);\n }\n }\n }\n if (wildcardListeners.size > 0) {\n const wrapped = { event, payload };\n for (const cb of wildcardListeners) {\n try {\n cb(wrapped, clientStack);\n } catch (e) {\n errors.push(e);\n }\n }\n }\n\n if (errors.length > 0) {\n if (errors.length === 1) {\n throw errors[0];\n } else {\n for (const error of errors) {\n console.error(error);\n }\n throw new AggregateError(\n errors,\n \"Errors occurred during event emission\",\n );\n }\n }\n });\n },\n\n subscribe(callback) {\n subscribers.add(callback);\n return () => subscribers.delete(callback);\n },\n\n notifySubscribers() {\n for (const cb of subscribers) {\n try {\n cb();\n } catch (e) {\n console.error(\n \"NotificationManager: subscriber callback error\",\n e,\n );\n }\n }\n },\n };\n }, []);\n },\n);\n"],"mappings":";;;AA4BA,MAAa,sBAAsB,SACjC,SAAS,sBAA2C;CAClD,OAAO,cAAc;EACnB,MAAM,4BAAY,IAAI,IAAmC;EACzD,MAAM,oCAAoB,IAAI,IAAsB;EACpD,MAAM,8BAAc,IAAI,IAAgB;EAExC,OAAO;GACL,GAAG,OAAO,UAAU;IAClB,MAAM,KAAK;IACX,IAAI,UAAU,KAAK;KACjB,kBAAkB,IAAI,EAAE;KACxB,aAAa,kBAAkB,OAAO,EAAE;IAC1C;IAEA,IAAI,MAAM,UAAU,IAAI,KAAK;IAC7B,IAAI,CAAC,KAAK;KACR,sBAAM,IAAI,IAAI;KACd,UAAU,IAAI,OAAO,GAAG;IAC1B;IACA,IAAI,IAAI,EAAE;IAEV,aAAa;KACX,IAAK,OAAO,EAAE;KACd,IAAI,IAAK,SAAS,GAAG,UAAU,OAAO,KAAK;IAC7C;GACF;GAEA,KAAK,OAAO,SAAS,aAAa;IAChC,MAAM,iBAAiB,UAAU,IAAI,KAAK;IAC1C,IAAI,CAAC,kBAAkB,kBAAkB,SAAS,GAAG;IAErD,qBAAqB;KACnB,MAAM,SAAS,CAAC;KAChB,IAAI,gBACF,KAAK,MAAM,MAAM,gBACf,IAAI;MACF,GAAG,SAAS,WAAW;KACzB,SAAS,GAAG;MACV,OAAO,KAAK,CAAC;KACf;KAGJ,IAAI,kBAAkB,OAAO,GAAG;MAC9B,MAAM,UAAU;OAAE;OAAO;MAAQ;MACjC,KAAK,MAAM,MAAM,mBACf,IAAI;OACF,GAAG,SAAS,WAAW;MACzB,SAAS,GAAG;OACV,OAAO,KAAK,CAAC;MACf;KAEJ;KAEA,IAAI,OAAO,SAAS,GAClB,IAAI,OAAO,WAAW,GACpB,MAAM,OAAO;UACR;MACL,KAAK,MAAM,SAAS,QAClB,QAAQ,MAAM,KAAK;MAErB,MAAM,IAAI,eACR,QACA,uCACF;KACF;IAEJ,CAAC;GACH;GAEA,UAAU,UAAU;IAClB,YAAY,IAAI,QAAQ;IACxB,aAAa,YAAY,OAAO,QAAQ;GAC1C;GAEA,oBAAoB;IAClB,KAAK,MAAM,MAAM,aACf,IAAI;KACF,GAAG;IACL,SAAS,GAAG;KACV,QAAQ,MACN,kDACA,CACF;IACF;GAEJ;EACF;CACF,GAAG,CAAC,CAAC;AACP,CACF"} | ||
| {"version":3,"file":"NotificationManager.js","names":[],"sources":["../../src/utils/NotificationManager.ts"],"sourcesContent":["import { useMemo } from \"react\";\nimport { resource } from \"@assistant-ui/tap\";\nimport type { ClientStack } from \"./tap-client-stack-context\";\nimport type {\n AssistantEventName,\n AssistantEventPayload,\n} from \"../types/events\";\nimport type { Unsubscribe } from \"../types/client\";\n\ntype InternalCallback = (payload: unknown, clientStack: ClientStack) => void;\n\nexport type NotificationManager = {\n on<TEvent extends AssistantEventName>(\n event: TEvent,\n callback: (\n payload: AssistantEventPayload[TEvent],\n clientStack: ClientStack,\n ) => void,\n ): Unsubscribe;\n emit<TEvent extends Exclude<AssistantEventName, \"*\">>(\n event: TEvent,\n payload: AssistantEventPayload[TEvent],\n clientStack: ClientStack,\n ): void;\n subscribe(callback: () => void): Unsubscribe;\n notifySubscribers(): void;\n};\n\nconst useNotificationManager = (): NotificationManager => {\n return useMemo(() => {\n const listeners = new Map<string, Set<InternalCallback>>();\n const wildcardListeners = new Set<InternalCallback>();\n const subscribers = new Set<() => void>();\n\n return {\n on(event, callback) {\n const cb = callback as InternalCallback;\n if (event === \"*\") {\n wildcardListeners.add(cb);\n return () => wildcardListeners.delete(cb);\n }\n\n let set = listeners.get(event);\n if (!set) {\n set = new Set();\n listeners.set(event, set);\n }\n set.add(cb);\n\n return () => {\n set!.delete(cb);\n if (set!.size === 0) listeners.delete(event);\n };\n },\n\n emit(event, payload, clientStack) {\n const eventListeners = listeners.get(event);\n if (!eventListeners && wildcardListeners.size === 0) return;\n\n queueMicrotask(() => {\n const errors = [];\n if (eventListeners) {\n for (const cb of eventListeners) {\n try {\n cb(payload, clientStack);\n } catch (e) {\n errors.push(e);\n }\n }\n }\n if (wildcardListeners.size > 0) {\n const wrapped = { event, payload };\n for (const cb of wildcardListeners) {\n try {\n cb(wrapped, clientStack);\n } catch (e) {\n errors.push(e);\n }\n }\n }\n\n if (errors.length > 0) {\n if (errors.length === 1) {\n throw errors[0];\n } else {\n for (const error of errors) {\n console.error(error);\n }\n throw new AggregateError(\n errors,\n \"Errors occurred during event emission\",\n );\n }\n }\n });\n },\n\n subscribe(callback) {\n subscribers.add(callback);\n return () => subscribers.delete(callback);\n },\n\n notifySubscribers() {\n for (const cb of subscribers) {\n try {\n cb();\n } catch (e) {\n console.error(\"NotificationManager: subscriber callback error\", e);\n }\n }\n },\n };\n }, []);\n};\n\nexport const NotificationManager = resource(useNotificationManager);\n"],"mappings":";;;AA4BA,MAAM,+BAAoD;CACxD,OAAO,cAAc;EACnB,MAAM,4BAAY,IAAI,IAAmC;EACzD,MAAM,oCAAoB,IAAI,IAAsB;EACpD,MAAM,8BAAc,IAAI,IAAgB;EAExC,OAAO;GACL,GAAG,OAAO,UAAU;IAClB,MAAM,KAAK;IACX,IAAI,UAAU,KAAK;KACjB,kBAAkB,IAAI,EAAE;KACxB,aAAa,kBAAkB,OAAO,EAAE;IAC1C;IAEA,IAAI,MAAM,UAAU,IAAI,KAAK;IAC7B,IAAI,CAAC,KAAK;KACR,sBAAM,IAAI,IAAI;KACd,UAAU,IAAI,OAAO,GAAG;IAC1B;IACA,IAAI,IAAI,EAAE;IAEV,aAAa;KACX,IAAK,OAAO,EAAE;KACd,IAAI,IAAK,SAAS,GAAG,UAAU,OAAO,KAAK;IAC7C;GACF;GAEA,KAAK,OAAO,SAAS,aAAa;IAChC,MAAM,iBAAiB,UAAU,IAAI,KAAK;IAC1C,IAAI,CAAC,kBAAkB,kBAAkB,SAAS,GAAG;IAErD,qBAAqB;KACnB,MAAM,SAAS,CAAC;KAChB,IAAI,gBACF,KAAK,MAAM,MAAM,gBACf,IAAI;MACF,GAAG,SAAS,WAAW;KACzB,SAAS,GAAG;MACV,OAAO,KAAK,CAAC;KACf;KAGJ,IAAI,kBAAkB,OAAO,GAAG;MAC9B,MAAM,UAAU;OAAE;OAAO;MAAQ;MACjC,KAAK,MAAM,MAAM,mBACf,IAAI;OACF,GAAG,SAAS,WAAW;MACzB,SAAS,GAAG;OACV,OAAO,KAAK,CAAC;MACf;KAEJ;KAEA,IAAI,OAAO,SAAS,GAClB,IAAI,OAAO,WAAW,GACpB,MAAM,OAAO;UACR;MACL,KAAK,MAAM,SAAS,QAClB,QAAQ,MAAM,KAAK;MAErB,MAAM,IAAI,eACR,QACA,uCACF;KACF;IAEJ,CAAC;GACH;GAEA,UAAU,UAAU;IAClB,YAAY,IAAI,QAAQ;IACxB,aAAa,YAAY,OAAO,QAAQ;GAC1C;GAEA,oBAAoB;IAClB,KAAK,MAAM,MAAM,aACf,IAAI;KACF,GAAG;IACL,SAAS,GAAG;KACV,QAAQ,MAAM,kDAAkD,CAAC;IACnE;GAEJ;EACF;CACF,GAAG,CAAC,CAAC;AACP;AAEA,MAAa,sBAAsB,SAAS,sBAAsB"} |
@@ -9,2 +9,7 @@ import { AssistantClient } from "../types/client.js"; | ||
| declare const createRootAssistantClient: () => AssistantClient; | ||
| /** | ||
| * Carries the tap host's effects callback on the client so AuiProvider can | ||
| * mount the host's commit ahead of its children's effects. | ||
| */ | ||
| declare const AUI_USE_EFFECTS_SYMBOL: unique symbol; | ||
| declare const useAssistantContextValue: () => AssistantClient; | ||
@@ -40,3 +45,3 @@ /** | ||
| //#endregion | ||
| export { AuiProvider, DefaultAssistantClient, createRootAssistantClient, useAssistantContextValue }; | ||
| export { AUI_USE_EFFECTS_SYMBOL, AuiProvider, DefaultAssistantClient, createRootAssistantClient, useAssistantContextValue }; | ||
| //# sourceMappingURL=react-assistant-context.d.ts.map |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"react-assistant-context.d.ts","names":[],"sources":["../../src/utils/react-assistant-context.tsx"],"mappings":";;;;;cAsDa,sBAAA,EAAwB,eAIlC;AAJH;AAAA,cAWa,yBAAA,QAAgC,eAUzC;AAAA,cAOS,wBAAA,QAA+B,eAE3C;;AA1BE;AAOH;;;;AAUI;AAOJ;;;;AAEC;AAuBD;;;;;;;;;cAAa,WAAA;EAAe,KAAA;EAAA;AAAA;EAAA,iDAK1B,KAAA,EAAO,eAAA,EALmB;EAO1B,QAAA,EAAU,KAAA,CAAM,SAAA;AAAA,MACd,KAAA,CAAM,YAAA"} | ||
| {"version":3,"file":"react-assistant-context.d.ts","names":[],"sources":["../../src/utils/react-assistant-context.tsx"],"mappings":";;;;;cAsDa,sBAAA,EAAwB,eAIlC;AAJH;AAAA,cAWa,yBAAA,QAAgC,eAUzC;;;AAjBD;AAOH;cAqBa,sBAAA;AAAA,cAmBA,wBAAA,QAA+B,eAE3C;;AAhCG;AAWJ;;;;AAA6E;AAmB7E;;;;AAEC;AAuBD;;;;;;;;;cAAa,WAAA;EAAe,KAAA;EAAA;AAAA;EAAA,iDAK1B,KAAA,EAAO,eAAA,EALmB;EAO1B,QAAA,EAAU,KAAA,CAAM,SAAA;AAAA,MACd,KAAA,CAAM,YAAA"} |
| import { BaseProxyHandler, handleIntrospectionProp } from "./BaseProxyHandler.js"; | ||
| import { PROXIED_ASSISTANT_STATE_SYMBOL, createProxiedAssistantState } from "./proxied-assistant-state.js"; | ||
| import { createContext, useContext } from "@assistant-ui/tap/react-shim"; | ||
| import { jsx } from "react/jsx-runtime"; | ||
| import { createContext, useContext, useEffect } from "@assistant-ui/tap/react-shim"; | ||
| import { jsx, jsxs } from "react/jsx-runtime"; | ||
| //#region src/utils/react-assistant-context.tsx | ||
@@ -48,2 +48,16 @@ const NO_OP_SUBSCRIBE = () => () => {}; | ||
| const AssistantContext = createContext(DefaultAssistantClient); | ||
| /** | ||
| * Carries the tap host's effects callback on the client so AuiProvider can | ||
| * mount the host's commit ahead of its children's effects. | ||
| */ | ||
| const AUI_USE_EFFECTS_SYMBOL = Symbol("assistant-ui.store.useEffects"); | ||
| const NOOP_EFFECT = () => {}; | ||
| const getTapEffects = (client) => { | ||
| return client[AUI_USE_EFFECTS_SYMBOL] ?? NOOP_EFFECT; | ||
| }; | ||
| const UseTapEffects = () => { | ||
| "use no memo"; | ||
| useEffect(getTapEffects(useAssistantContextValue())); | ||
| return null; | ||
| }; | ||
| const useAssistantContextValue = () => { | ||
@@ -74,10 +88,11 @@ return useContext(AssistantContext); | ||
| const AuiProvider = ({ value, children }) => { | ||
| return /* @__PURE__ */ jsx(AssistantContext.Provider, { | ||
| "use no memo"; | ||
| return /* @__PURE__ */ jsxs(AssistantContext.Provider, { | ||
| value, | ||
| children | ||
| children: [/* @__PURE__ */ jsx(UseTapEffects, {}), children] | ||
| }); | ||
| }; | ||
| //#endregion | ||
| export { AuiProvider, DefaultAssistantClient, createRootAssistantClient, useAssistantContextValue }; | ||
| export { AUI_USE_EFFECTS_SYMBOL, AuiProvider, DefaultAssistantClient, createRootAssistantClient, useAssistantContextValue }; | ||
| //# sourceMappingURL=react-assistant-context.js.map |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"react-assistant-context.js","names":[],"sources":["../../src/utils/react-assistant-context.tsx"],"sourcesContent":["import type React from \"react\";\nimport { createContext, useContext } from \"react\";\nimport type { AssistantClient, AssistantClientAccessor } from \"../types/client\";\nimport {\n createProxiedAssistantState,\n PROXIED_ASSISTANT_STATE_SYMBOL,\n} from \"./proxied-assistant-state\";\nimport { BaseProxyHandler, handleIntrospectionProp } from \"./BaseProxyHandler\";\n\nconst NO_OP_SUBSCRIBE = () => () => {};\n\nconst createErrorClientField = (\n message: string,\n): AssistantClientAccessor<never> => {\n const fn = (() => {\n throw new Error(message);\n }) as AssistantClientAccessor<never>;\n fn.source = null;\n fn.query = null;\n return fn;\n};\n\nclass DefaultAssistantClientProxyHandler\n extends BaseProxyHandler\n implements ProxyHandler<AssistantClient>\n{\n get(_: unknown, prop: string | symbol) {\n if (prop === \"subscribe\") return NO_OP_SUBSCRIBE;\n if (prop === \"on\") return NO_OP_SUBSCRIBE;\n if (prop === PROXIED_ASSISTANT_STATE_SYMBOL)\n return DefaultAssistantClientProxiedAssistantState;\n const introspection = handleIntrospectionProp(\n prop,\n \"DefaultAssistantClient\",\n );\n if (introspection !== false) return introspection;\n return createErrorClientField(\n \"You are using a component or hook that requires an AuiProvider. Wrap your component in an <AuiProvider> component.\",\n );\n }\n\n ownKeys(): ArrayLike<string | symbol> {\n return [\"subscribe\", \"on\", PROXIED_ASSISTANT_STATE_SYMBOL];\n }\n\n has(_: unknown, prop: string | symbol): boolean {\n return (\n prop === \"subscribe\" ||\n prop === \"on\" ||\n prop === PROXIED_ASSISTANT_STATE_SYMBOL\n );\n }\n}\n/** Default context value - throws \"wrap in AuiProvider\" error */\nexport const DefaultAssistantClient: AssistantClient =\n new Proxy<AssistantClient>(\n {} as AssistantClient,\n new DefaultAssistantClientProxyHandler(),\n );\n\nconst DefaultAssistantClientProxiedAssistantState = createProxiedAssistantState(\n DefaultAssistantClient,\n);\n\n/** Root prototype for created clients - throws \"scope not defined\" error */\nexport const createRootAssistantClient = (): AssistantClient =>\n new Proxy<AssistantClient>({} as AssistantClient, {\n get(_: AssistantClient, prop: string | symbol) {\n const introspection = handleIntrospectionProp(prop, \"AssistantClient\");\n if (introspection !== false) return introspection;\n\n return createErrorClientField(\n `The current scope does not have a \"${String(prop)}\" property.`,\n );\n },\n });\n\n/**\n * React Context for the AssistantClient\n */\nconst AssistantContext = createContext<AssistantClient>(DefaultAssistantClient);\n\nexport const useAssistantContextValue = (): AssistantClient => {\n return useContext(AssistantContext);\n};\n\n/**\n * Supplies an `AssistantClient` to the React tree.\n *\n * Place near the root of any subtree that uses {@link useAui} or the\n * primitives built on it. Components rendered outside an `AuiProvider`\n * receive a default client whose scope accessors throw on use, so\n * missing-provider mistakes surface at the point of use.\n *\n * When mounting a runtime built with one of the runtime hooks, use\n * {@link AssistantRuntimeProvider} — it installs an `AuiProvider`\n * internally — rather than wiring `AuiProvider` yourself.\n *\n * @example\n * ```tsx\n * function ScopedAssistant({ children, scopes }) {\n * const aui = useAui(scopes);\n *\n * return <AuiProvider value={aui}>{children}</AuiProvider>;\n * }\n * ```\n */\nexport const AuiProvider = ({\n value,\n children,\n}: {\n /** Assistant client to expose to descendants. */\n value: AssistantClient;\n /** Subtree that may read from the client. */\n children: React.ReactNode;\n}): React.ReactElement => {\n return (\n <AssistantContext.Provider value={value}>\n {children}\n </AssistantContext.Provider>\n );\n};\n"],"mappings":";;;;;AASA,MAAM,8BAA8B,CAAC;AAErC,MAAM,0BACJ,YACmC;CACnC,MAAM,YAAY;EAChB,MAAM,IAAI,MAAM,OAAO;CACzB;CACA,GAAG,SAAS;CACZ,GAAG,QAAQ;CACX,OAAO;AACT;AAEA,IAAM,qCAAN,cACU,iBAEV;CACE,IAAI,GAAY,MAAuB;EACrC,IAAI,SAAS,aAAa,OAAO;EACjC,IAAI,SAAS,MAAM,OAAO;EAC1B,IAAI,SAAS,gCACX,OAAO;EACT,MAAM,gBAAgB,wBACpB,MACA,wBACF;EACA,IAAI,kBAAkB,OAAO,OAAO;EACpC,OAAO,uBACL,oHACF;CACF;CAEA,UAAsC;EACpC,OAAO;GAAC;GAAa;GAAM;EAA8B;CAC3D;CAEA,IAAI,GAAY,MAAgC;EAC9C,OACE,SAAS,eACT,SAAS,QACT,SAAS;CAEb;AACF;;AAEA,MAAa,yBACX,IAAI,MACF,CAAC,GACD,IAAI,mCAAmC,CACzC;AAEF,MAAM,8CAA8C,4BAClD,sBACF;;AAGA,MAAa,kCACX,IAAI,MAAuB,CAAC,GAAsB,EAChD,IAAI,GAAoB,MAAuB;CAC7C,MAAM,gBAAgB,wBAAwB,MAAM,iBAAiB;CACrE,IAAI,kBAAkB,OAAO,OAAO;CAEpC,OAAO,uBACL,sCAAsC,OAAO,IAAI,EAAE,YACrD;AACF,EACF,CAAC;;;;AAKH,MAAM,mBAAmB,cAA+B,sBAAsB;AAE9E,MAAa,iCAAkD;CAC7D,OAAO,WAAW,gBAAgB;AACpC;;;;;;;;;;;;;;;;;;;;;;AAuBA,MAAa,eAAe,EAC1B,OACA,eAMwB;CACxB,OACE,oBAAC,iBAAiB,UAAlB;EAAkC;EAC/B;CACwB,CAAA;AAE/B"} | ||
| {"version":3,"file":"react-assistant-context.js","names":[],"sources":["../../src/utils/react-assistant-context.tsx"],"sourcesContent":["import type React from \"react\";\nimport { createContext, useContext, useEffect } from \"react\";\nimport type { AssistantClient, AssistantClientAccessor } from \"../types/client\";\nimport {\n createProxiedAssistantState,\n PROXIED_ASSISTANT_STATE_SYMBOL,\n} from \"./proxied-assistant-state\";\nimport { BaseProxyHandler, handleIntrospectionProp } from \"./BaseProxyHandler\";\n\nconst NO_OP_SUBSCRIBE = () => () => {};\n\nconst createErrorClientField = (\n message: string,\n): AssistantClientAccessor<never> => {\n const fn = (() => {\n throw new Error(message);\n }) as AssistantClientAccessor<never>;\n fn.source = null;\n fn.query = null;\n return fn;\n};\n\nclass DefaultAssistantClientProxyHandler\n extends BaseProxyHandler\n implements ProxyHandler<AssistantClient>\n{\n get(_: unknown, prop: string | symbol) {\n if (prop === \"subscribe\") return NO_OP_SUBSCRIBE;\n if (prop === \"on\") return NO_OP_SUBSCRIBE;\n if (prop === PROXIED_ASSISTANT_STATE_SYMBOL)\n return DefaultAssistantClientProxiedAssistantState;\n const introspection = handleIntrospectionProp(\n prop,\n \"DefaultAssistantClient\",\n );\n if (introspection !== false) return introspection;\n return createErrorClientField(\n \"You are using a component or hook that requires an AuiProvider. Wrap your component in an <AuiProvider> component.\",\n );\n }\n\n ownKeys(): ArrayLike<string | symbol> {\n return [\"subscribe\", \"on\", PROXIED_ASSISTANT_STATE_SYMBOL];\n }\n\n has(_: unknown, prop: string | symbol): boolean {\n return (\n prop === \"subscribe\" ||\n prop === \"on\" ||\n prop === PROXIED_ASSISTANT_STATE_SYMBOL\n );\n }\n}\n/** Default context value - throws \"wrap in AuiProvider\" error */\nexport const DefaultAssistantClient: AssistantClient =\n new Proxy<AssistantClient>(\n {} as AssistantClient,\n new DefaultAssistantClientProxyHandler(),\n );\n\nconst DefaultAssistantClientProxiedAssistantState = createProxiedAssistantState(\n DefaultAssistantClient,\n);\n\n/** Root prototype for created clients - throws \"scope not defined\" error */\nexport const createRootAssistantClient = (): AssistantClient =>\n new Proxy<AssistantClient>({} as AssistantClient, {\n get(_: AssistantClient, prop: string | symbol) {\n const introspection = handleIntrospectionProp(prop, \"AssistantClient\");\n if (introspection !== false) return introspection;\n\n return createErrorClientField(\n `The current scope does not have a \"${String(prop)}\" property.`,\n );\n },\n });\n\n/**\n * React Context for the AssistantClient\n */\nconst AssistantContext = createContext<AssistantClient>(DefaultAssistantClient);\n\n/**\n * Carries the tap host's effects callback on the client so AuiProvider can\n * mount the host's commit ahead of its children's effects.\n */\nexport const AUI_USE_EFFECTS_SYMBOL = Symbol(\"assistant-ui.store.useEffects\");\n\nconst NOOP_EFFECT = () => {};\n\nconst getTapEffects = (client: AssistantClient): (() => void) => {\n return (\n (client as Record<symbol, never>)[AUI_USE_EFFECTS_SYMBOL] ?? NOOP_EFFECT\n );\n};\n\nconst UseTapEffects = () => {\n \"use no memo\";\n\n const aui = useAssistantContextValue();\n // oxlint-disable-next-line react-hooks/exhaustive-deps\n useEffect(getTapEffects(aui));\n return null;\n};\n\nexport const useAssistantContextValue = (): AssistantClient => {\n return useContext(AssistantContext);\n};\n\n/**\n * Supplies an `AssistantClient` to the React tree.\n *\n * Place near the root of any subtree that uses {@link useAui} or the\n * primitives built on it. Components rendered outside an `AuiProvider`\n * receive a default client whose scope accessors throw on use, so\n * missing-provider mistakes surface at the point of use.\n *\n * When mounting a runtime built with one of the runtime hooks, use\n * {@link AssistantRuntimeProvider} — it installs an `AuiProvider`\n * internally — rather than wiring `AuiProvider` yourself.\n *\n * @example\n * ```tsx\n * function ScopedAssistant({ children, scopes }) {\n * const aui = useAui(scopes);\n *\n * return <AuiProvider value={aui}>{children}</AuiProvider>;\n * }\n * ```\n */\nexport const AuiProvider = ({\n value,\n children,\n}: {\n /** Assistant client to expose to descendants. */\n value: AssistantClient;\n /** Subtree that may read from the client. */\n children: React.ReactNode;\n}): React.ReactElement => {\n // The <UseTapEffects /> element must be created fresh each render\n \"use no memo\";\n return (\n <AssistantContext.Provider value={value}>\n <UseTapEffects />\n {children}\n </AssistantContext.Provider>\n );\n};\n"],"mappings":";;;;;AASA,MAAM,8BAA8B,CAAC;AAErC,MAAM,0BACJ,YACmC;CACnC,MAAM,YAAY;EAChB,MAAM,IAAI,MAAM,OAAO;CACzB;CACA,GAAG,SAAS;CACZ,GAAG,QAAQ;CACX,OAAO;AACT;AAEA,IAAM,qCAAN,cACU,iBAEV;CACE,IAAI,GAAY,MAAuB;EACrC,IAAI,SAAS,aAAa,OAAO;EACjC,IAAI,SAAS,MAAM,OAAO;EAC1B,IAAI,SAAS,gCACX,OAAO;EACT,MAAM,gBAAgB,wBACpB,MACA,wBACF;EACA,IAAI,kBAAkB,OAAO,OAAO;EACpC,OAAO,uBACL,oHACF;CACF;CAEA,UAAsC;EACpC,OAAO;GAAC;GAAa;GAAM;EAA8B;CAC3D;CAEA,IAAI,GAAY,MAAgC;EAC9C,OACE,SAAS,eACT,SAAS,QACT,SAAS;CAEb;AACF;;AAEA,MAAa,yBACX,IAAI,MACF,CAAC,GACD,IAAI,mCAAmC,CACzC;AAEF,MAAM,8CAA8C,4BAClD,sBACF;;AAGA,MAAa,kCACX,IAAI,MAAuB,CAAC,GAAsB,EAChD,IAAI,GAAoB,MAAuB;CAC7C,MAAM,gBAAgB,wBAAwB,MAAM,iBAAiB;CACrE,IAAI,kBAAkB,OAAO,OAAO;CAEpC,OAAO,uBACL,sCAAsC,OAAO,IAAI,EAAE,YACrD;AACF,EACF,CAAC;;;;AAKH,MAAM,mBAAmB,cAA+B,sBAAsB;;;;;AAM9E,MAAa,yBAAyB,OAAO,+BAA+B;AAE5E,MAAM,oBAAoB,CAAC;AAE3B,MAAM,iBAAiB,WAA0C;CAC/D,OACG,OAAiC,2BAA2B;AAEjE;AAEA,MAAM,sBAAsB;CAC1B;CAIA,UAAU,cAFE,yBAEc,CAAC,CAAC;CAC5B,OAAO;AACT;AAEA,MAAa,iCAAkD;CAC7D,OAAO,WAAW,gBAAgB;AACpC;;;;;;;;;;;;;;;;;;;;;;AAuBA,MAAa,eAAe,EAC1B,OACA,eAMwB;CAExB;CACA,OACE,qBAAC,iBAAiB,UAAlB;EAAkC;YAAlC,CACE,oBAAC,eAAD,CAAgB,CAAA,GACf,QACwB;;AAE/B"} |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"splitClients.d.ts","names":[],"sources":["../../src/utils/splitClients.ts"],"mappings":";;;;;KAWY,WAAA,GAAc,OAAA,CACxB,MAAA,CAAO,WAAA,EAAa,aAAA,CAAc,WAAA;AAAA,KAExB,cAAA,GAAiB,OAAA,CAC3B,MAAA,CAAO,WAAA,EAAa,cAAA,CAAe,WAAA;AAAA,cAyDxB,eAAA,GACX,OAAA,EAAS,MAAA,CAAO,KAAA,EAChB,UAAA,EAAY,eAAA"} | ||
| {"version":3,"file":"splitClients.d.ts","names":[],"sources":["../../src/utils/splitClients.ts"],"mappings":";;;;;KAUY,WAAA,GAAc,OAAA,CACxB,MAAA,CAAO,WAAA,EAAa,aAAA,CAAc,WAAA;AAAA,KAExB,cAAA,GAAiB,OAAA,CAC3B,MAAA,CAAO,WAAA,EAAa,cAAA,CAAe,WAAA;AAAA,cAuDxB,eAAA,GACX,OAAA,EAAS,MAAA,CAAO,KAAA,EAChB,UAAA,EAAY,eAAA"} |
@@ -1,2 +0,2 @@ | ||
| import { Derived } from "../Derived.js"; | ||
| import { useDerived } from "../Derived.js"; | ||
| import { getTransformScopes } from "../attachTransformScopes.js"; | ||
@@ -16,6 +16,6 @@ import { useMemo } from "@assistant-ui/tap/react-shim"; | ||
| for (const clientElement of Object.values(scopes)) { | ||
| if (clientElement.type === Derived) continue; | ||
| if (visited.has(clientElement.type)) continue; | ||
| visited.add(clientElement.type); | ||
| const transform = getTransformScopes(clientElement.type); | ||
| if (clientElement.hook === useDerived) continue; | ||
| if (visited.has(clientElement.hook)) continue; | ||
| visited.add(clientElement.hook); | ||
| const transform = getTransformScopes(clientElement.hook); | ||
| if (transform) { | ||
@@ -30,3 +30,3 @@ transform(scopes, baseClient); | ||
| const derivedClients = {}; | ||
| for (const [key, clientElement] of Object.entries(scopes)) if (clientElement.type === Derived) derivedClients[key] = clientElement; | ||
| for (const [key, clientElement] of Object.entries(scopes)) if (clientElement.hook === useDerived) derivedClients[key] = clientElement; | ||
| else rootClients[key] = clientElement; | ||
@@ -33,0 +33,0 @@ return { |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"splitClients.js","names":[],"sources":["../../src/utils/splitClients.ts"],"sourcesContent":["import { Derived, type DerivedElement } from \"../Derived\";\nimport type {\n AssistantClient,\n ClientElement,\n ClientNames,\n} from \"../types/client\";\nimport { getTransformScopes } from \"../attachTransformScopes\";\nimport type { useAui } from \"../useAui\";\nimport { useMemo } from \"react\";\nimport { type ResourceElement } from \"@assistant-ui/tap\";\n\nexport type RootClients = Partial<\n Record<ClientNames, ClientElement<ClientNames>>\n>;\nexport type DerivedClients = Partial<\n Record<ClientNames, DerivedElement<ClientNames>>\n>;\n\n/**\n * Splits a clients object into root clients and derived clients,\n * applying transformScopes from root client elements.\n */\nfunction splitClients(clients: useAui.Props, baseClient: AssistantClient) {\n // 1. Collect transforms from root elements and run them iteratively\n const scopes = { ...clients } as Record<\n string,\n ClientElement<ClientNames> | DerivedElement<ClientNames>\n >;\n const visited = new Set<(...args: any[]) => any>();\n\n let changed = true;\n while (changed) {\n changed = false;\n for (const clientElement of Object.values(scopes)) {\n if (clientElement.type === (Derived as unknown)) continue;\n if (visited.has(clientElement.type)) continue;\n visited.add(clientElement.type);\n\n const transform = getTransformScopes(\n clientElement.type as (props: any) => ResourceElement<any>,\n );\n if (transform) {\n transform(scopes, baseClient);\n changed = true;\n break; // restart iteration since scopes may have new root elements\n }\n }\n }\n\n // 2. Split result into root/derived\n const rootClients: RootClients = {};\n const derivedClients: DerivedClients = {};\n\n for (const [key, clientElement] of Object.entries(scopes) as [\n ClientNames,\n ClientElement<ClientNames> | DerivedElement<ClientNames>,\n ][]) {\n if (clientElement.type === (Derived as unknown)) {\n derivedClients[key] = clientElement as DerivedElement<ClientNames>;\n } else {\n rootClients[key] = clientElement as ClientElement<ClientNames>;\n }\n }\n\n return { rootClients, derivedClients };\n}\n\nconst useShallowMemoObject = <T extends object>(object: T) => {\n // oxlint-disable-next-line react/exhaustive-deps -- shallow memo over the object's flattened entries\n return useMemo(() => object, [...Object.entries(object).flat()]);\n};\n\nexport const useSplitClients = (\n clients: useAui.Props,\n baseClient: AssistantClient,\n) => {\n const { rootClients, derivedClients } = splitClients(clients, baseClient);\n\n return {\n rootClients: useShallowMemoObject(rootClients),\n derivedClients: useShallowMemoObject(derivedClients),\n };\n};\n"],"mappings":";;;;;;;;AAsBA,SAAS,aAAa,SAAuB,YAA6B;CAExE,MAAM,SAAS,EAAE,GAAG,QAAQ;CAI5B,MAAM,0BAAU,IAAI,IAA6B;CAEjD,IAAI,UAAU;CACd,OAAO,SAAS;EACd,UAAU;EACV,KAAK,MAAM,iBAAiB,OAAO,OAAO,MAAM,GAAG;GACjD,IAAI,cAAc,SAAU,SAAqB;GACjD,IAAI,QAAQ,IAAI,cAAc,IAAI,GAAG;GACrC,QAAQ,IAAI,cAAc,IAAI;GAE9B,MAAM,YAAY,mBAChB,cAAc,IAChB;GACA,IAAI,WAAW;IACb,UAAU,QAAQ,UAAU;IAC5B,UAAU;IACV;GACF;EACF;CACF;CAGA,MAAM,cAA2B,CAAC;CAClC,MAAM,iBAAiC,CAAC;CAExC,KAAK,MAAM,CAAC,KAAK,kBAAkB,OAAO,QAAQ,MAAM,GAItD,IAAI,cAAc,SAAU,SAC1B,eAAe,OAAO;MAEtB,YAAY,OAAO;CAIvB,OAAO;EAAE;EAAa;CAAe;AACvC;AAEA,MAAM,wBAA0C,WAAc;CAE5D,OAAO,cAAc,QAAQ,CAAC,GAAG,OAAO,QAAQ,MAAM,CAAC,CAAC,KAAK,CAAC,CAAC;AACjE;AAEA,MAAa,mBACX,SACA,eACG;CACH,MAAM,EAAE,aAAa,mBAAmB,aAAa,SAAS,UAAU;CAExE,OAAO;EACL,aAAa,qBAAqB,WAAW;EAC7C,gBAAgB,qBAAqB,cAAc;CACrD;AACF"} | ||
| {"version":3,"file":"splitClients.js","names":[],"sources":["../../src/utils/splitClients.ts"],"sourcesContent":["import { useDerived, type DerivedElement } from \"../Derived\";\nimport type {\n AssistantClient,\n ClientElement,\n ClientNames,\n} from \"../types/client\";\nimport { getTransformScopes } from \"../attachTransformScopes\";\nimport type { useAui } from \"../useAui\";\nimport { useMemo } from \"react\";\n\nexport type RootClients = Partial<\n Record<ClientNames, ClientElement<ClientNames>>\n>;\nexport type DerivedClients = Partial<\n Record<ClientNames, DerivedElement<ClientNames>>\n>;\n\n/**\n * Splits a clients object into root clients and derived clients,\n * applying transformScopes from root client elements.\n */\nfunction splitClients(clients: useAui.Props, baseClient: AssistantClient) {\n // 1. Collect transforms from root elements and run them iteratively\n const scopes = { ...clients } as Record<\n string,\n ClientElement<ClientNames> | DerivedElement<ClientNames>\n >;\n const visited = new Set<(...args: any[]) => any>();\n\n let changed = true;\n while (changed) {\n changed = false;\n for (const clientElement of Object.values(scopes)) {\n if (clientElement.hook === (useDerived as unknown)) continue;\n if (visited.has(clientElement.hook)) continue;\n visited.add(clientElement.hook);\n\n const transform = getTransformScopes(clientElement.hook);\n if (transform) {\n transform(scopes, baseClient);\n changed = true;\n break; // restart iteration since scopes may have new root elements\n }\n }\n }\n\n // 2. Split result into root/derived\n const rootClients: RootClients = {};\n const derivedClients: DerivedClients = {};\n\n for (const [key, clientElement] of Object.entries(scopes) as [\n ClientNames,\n ClientElement<ClientNames> | DerivedElement<ClientNames>,\n ][]) {\n if (clientElement.hook === (useDerived as unknown)) {\n derivedClients[key] = clientElement as DerivedElement<ClientNames>;\n } else {\n rootClients[key] = clientElement as ClientElement<ClientNames>;\n }\n }\n\n return { rootClients, derivedClients };\n}\n\nconst useShallowMemoObject = <T extends object>(object: T) => {\n // oxlint-disable-next-line react/exhaustive-deps -- shallow memo over the object's flattened entries\n return useMemo(() => object, [...Object.entries(object).flat()]);\n};\n\nexport const useSplitClients = (\n clients: useAui.Props,\n baseClient: AssistantClient,\n) => {\n const { rootClients, derivedClients } = splitClients(clients, baseClient);\n\n return {\n rootClients: useShallowMemoObject(rootClients),\n derivedClients: useShallowMemoObject(derivedClients),\n };\n};\n"],"mappings":";;;;;;;;AAqBA,SAAS,aAAa,SAAuB,YAA6B;CAExE,MAAM,SAAS,EAAE,GAAG,QAAQ;CAI5B,MAAM,0BAAU,IAAI,IAA6B;CAEjD,IAAI,UAAU;CACd,OAAO,SAAS;EACd,UAAU;EACV,KAAK,MAAM,iBAAiB,OAAO,OAAO,MAAM,GAAG;GACjD,IAAI,cAAc,SAAU,YAAwB;GACpD,IAAI,QAAQ,IAAI,cAAc,IAAI,GAAG;GACrC,QAAQ,IAAI,cAAc,IAAI;GAE9B,MAAM,YAAY,mBAAmB,cAAc,IAAI;GACvD,IAAI,WAAW;IACb,UAAU,QAAQ,UAAU;IAC5B,UAAU;IACV;GACF;EACF;CACF;CAGA,MAAM,cAA2B,CAAC;CAClC,MAAM,iBAAiC,CAAC;CAExC,KAAK,MAAM,CAAC,KAAK,kBAAkB,OAAO,QAAQ,MAAM,GAItD,IAAI,cAAc,SAAU,YAC1B,eAAe,OAAO;MAEtB,YAAY,OAAO;CAIvB,OAAO;EAAE;EAAa;CAAe;AACvC;AAEA,MAAM,wBAA0C,WAAc;CAE5D,OAAO,cAAc,QAAQ,CAAC,GAAG,OAAO,QAAQ,MAAM,CAAC,CAAC,KAAK,CAAC,CAAC;AACjE;AAEA,MAAa,mBACX,SACA,eACG;CACH,MAAM,EAAE,aAAa,mBAAmB,aAAa,SAAS,UAAU;CAExE,OAAO;EACL,aAAa,qBAAqB,WAAW;EAC7C,gBAAgB,qBAAqB,cAAc;CACrD;AACF"} |
+4
-4
| { | ||
| "name": "@assistant-ui/store", | ||
| "version": "0.2.14", | ||
| "version": "0.2.16", | ||
| "description": "Tap-based state management for @assistant-ui", | ||
@@ -33,3 +33,3 @@ "keywords": [ | ||
| "peerDependencies": { | ||
| "@assistant-ui/tap": "^0.6.0", | ||
| "@assistant-ui/tap": "^0.7.0", | ||
| "@types/react": "*", | ||
@@ -50,4 +50,4 @@ "react": "^18 || ^19" | ||
| "vitest": "^4.1.8", | ||
| "@assistant-ui/x-buildutils": "0.0.12", | ||
| "@assistant-ui/tap": "0.6.0" | ||
| "@assistant-ui/tap": "0.7.1", | ||
| "@assistant-ui/x-buildutils": "0.0.12" | ||
| }, | ||
@@ -54,0 +54,0 @@ "publishConfig": { |
+4
-2
@@ -39,3 +39,3 @@ # `@assistant-ui/store` | ||
| const CounterClient = resource(function CounterClient(): ClientOutput<"counter"> { | ||
| const useCounterClient = (): ClientOutput<"counter"> => { | ||
| const [state, setState] = useState({ count: 0 }); | ||
@@ -46,4 +46,6 @@ return { | ||
| }; | ||
| }); | ||
| }; | ||
| const CounterClient = resource(useCounterClient); | ||
| function App() { | ||
@@ -50,0 +52,0 @@ const aui = useAui({ counter: CounterClient() }); |
@@ -1,2 +0,1 @@ | ||
| import { type ResourceElement } from "@assistant-ui/tap"; | ||
| import type { | ||
@@ -20,27 +19,28 @@ AssistantClient, | ||
| type ResourceWithTransformScopes = { | ||
| // Transforms are keyed by the resource's underlying hook (the function passed to | ||
| // `resource()`), since that is the identity a `ResourceElement` carries. | ||
| type Hook = (...args: any[]) => any; | ||
| type HookWithTransformScopes = Hook & { | ||
| [TRANSFORM_SCOPES]?: TransformScopesFn; | ||
| }; | ||
| export function attachTransformScopes< | ||
| T extends (...args: any[]) => ResourceElement<any>, | ||
| >(resource: T, transform: TransformScopesFn): void { | ||
| const r = resource as T & ResourceWithTransformScopes; | ||
| if (r[TRANSFORM_SCOPES]) { | ||
| export function attachTransformScopes( | ||
| hook: Hook, | ||
| transform: TransformScopesFn, | ||
| ): void { | ||
| const h = hook as HookWithTransformScopes; | ||
| if (h[TRANSFORM_SCOPES]) { | ||
| throw new Error("transformScopes is already attached to this resource"); | ||
| } | ||
| r[TRANSFORM_SCOPES] = transform; | ||
| h[TRANSFORM_SCOPES] = transform; | ||
| } | ||
| export function forwardTransformScopes< | ||
| T extends (...args: any[]) => ResourceElement<any>, | ||
| S extends (...args: any[]) => ResourceElement<any>, | ||
| >(target: T, source: S): void { | ||
| export function forwardTransformScopes(target: Hook, source: Hook): void { | ||
| const sourceTransform = getTransformScopes(source); | ||
| if (!sourceTransform) return; | ||
| const r = target as T & ResourceWithTransformScopes; | ||
| const existingTransform = r[TRANSFORM_SCOPES]; | ||
| const t = target as HookWithTransformScopes; | ||
| const existingTransform = t[TRANSFORM_SCOPES]; | ||
| if (existingTransform) { | ||
| r[TRANSFORM_SCOPES] = (scopes, parent) => { | ||
| t[TRANSFORM_SCOPES] = (scopes, parent) => { | ||
| sourceTransform(scopes, parent); | ||
@@ -50,10 +50,8 @@ existingTransform(scopes, parent); | ||
| } else { | ||
| r[TRANSFORM_SCOPES] = sourceTransform; | ||
| t[TRANSFORM_SCOPES] = sourceTransform; | ||
| } | ||
| } | ||
| export function getTransformScopes< | ||
| T extends (...args: any[]) => ResourceElement<any>, | ||
| >(resource: T): TransformScopesFn | undefined { | ||
| return (resource as T & ResourceWithTransformScopes)[TRANSFORM_SCOPES]; | ||
| export function getTransformScopes(hook: Hook): TransformScopesFn | undefined { | ||
| return (hook as HookWithTransformScopes)[TRANSFORM_SCOPES]; | ||
| } |
+8
-4
@@ -28,11 +28,15 @@ import { resource, type ResourceElement } from "@assistant-ui/tap"; | ||
| */ | ||
| export const Derived = resource(function Derived<K extends ClientNames>( | ||
| // Exported so consumers (e.g. splitClients) can identify a derived element by its | ||
| // hook: a `Derived(...)` element carries `hook === useDerived`. | ||
| export const useDerived = <K extends ClientNames>( | ||
| _config: Derived.Props<K>, | ||
| ): null { | ||
| ): null => { | ||
| return null; | ||
| }); | ||
| }; | ||
| export const Derived = resource(useDerived); | ||
| export type DerivedElement<K extends ClientNames> = ResourceElement< | ||
| null, | ||
| Derived.Props<K> | ||
| [Derived.Props<K>] | ||
| >; | ||
@@ -39,0 +43,0 @@ |
@@ -105,3 +105,3 @@ import { type ResourceElement } from "@assistant-ui/tap"; | ||
| * ```typescript | ||
| * const FooResource = resource(function FooResource(): ClientResourceOutput<"foo"> { | ||
| * const useFoo = (): ClientResourceOutput<"foo"> => { | ||
| * const [state, setState] = useState({ bar: "hello" }); | ||
@@ -112,3 +112,5 @@ * return { | ||
| * }; | ||
| * }); | ||
| * }; | ||
| * | ||
| * const FooResource = resource(useFoo); | ||
| * ``` | ||
@@ -115,0 +117,0 @@ */ |
+234
-219
@@ -6,3 +6,4 @@ "use client"; | ||
| useResources, | ||
| useResourceRoot, | ||
| useTapHost, | ||
| useTapRoot, | ||
| resource, | ||
@@ -25,2 +26,3 @@ withKey, | ||
| createRootAssistantClient, | ||
| AUI_USE_EFFECTS_SYMBOL, | ||
| } from "./utils/react-assistant-context"; | ||
@@ -52,5 +54,3 @@ import { | ||
| const RootClientResource = resource(function RootClientResource< | ||
| K extends ClientNames, | ||
| >({ | ||
| const useRootClientResource = <K extends ClientNames>({ | ||
| element, | ||
@@ -63,14 +63,13 @@ emit, | ||
| clientRef: { parent: AssistantClient; current: AssistantClient | null }; | ||
| }) { | ||
| }) => { | ||
| const { methods, state } = withAssistantTapContextProvider( | ||
| { clientRef, emit }, | ||
| // oxlint-disable-next-line react/rules-of-hooks -- withAssistantTapContextProvider runs this callback synchronously during render, so hook order is preserved | ||
| () => useClientResource(element), | ||
| function WithTapContext() { | ||
| return useClientResource(element); | ||
| }, | ||
| ); | ||
| return useMemo(() => ({ state, methods }), [methods, state]); | ||
| }); | ||
| }; | ||
| const RootClientAccessorResource = resource(function RootClientAccessorResource< | ||
| K extends ClientNames, | ||
| >({ | ||
| const useRootClientAccessorResource = <K extends ClientNames>({ | ||
| element, | ||
@@ -85,6 +84,10 @@ notifications, | ||
| name: K; | ||
| }): AssistantClientAccessor<K> { | ||
| const store = useResourceRoot( | ||
| RootClientResource({ element, emit: notifications.emit, clientRef }), | ||
| ); | ||
| }): AssistantClientAccessor<K> => { | ||
| const store = useTapRoot(function RootClient() { | ||
| return useRootClientResource({ | ||
| element, | ||
| emit: notifications.emit, | ||
| clientRef, | ||
| }); | ||
| }); | ||
@@ -113,142 +116,148 @@ useEffect(() => { | ||
| }, [store, name]); | ||
| }); | ||
| }; | ||
| const RootClientAccessorResource = resource(useRootClientAccessorResource); | ||
| const useNoOpRootClientsAccessorsResource = () => { | ||
| return useMemo( | ||
| () => ({ | ||
| clients: [] as AssistantClientAccessor<ClientNames>[], | ||
| subscribe: undefined, | ||
| on: undefined, | ||
| }), | ||
| [], | ||
| ); | ||
| }; | ||
| const NoOpRootClientsAccessorsResource = resource( | ||
| function NoOpRootClientsAccessorsResource() { | ||
| return useMemo( | ||
| () => ({ | ||
| clients: [] as AssistantClientAccessor<ClientNames>[], | ||
| subscribe: undefined, | ||
| on: undefined, | ||
| }), | ||
| [], | ||
| ); | ||
| }, | ||
| useNoOpRootClientsAccessorsResource, | ||
| ); | ||
| const RootClientsAccessorsResource = resource( | ||
| function RootClientsAccessorsResource({ | ||
| clients: inputClients, | ||
| clientRef, | ||
| }: { | ||
| clients: RootClients; | ||
| clientRef: { parent: AssistantClient; current: AssistantClient | null }; | ||
| }) { | ||
| const notifications = useResource(NotificationManager()); | ||
| const useRootClientsAccessorsResource = ({ | ||
| clients: inputClients, | ||
| clientRef, | ||
| }: { | ||
| clients: RootClients; | ||
| clientRef: { parent: AssistantClient; current: AssistantClient | null }; | ||
| }) => { | ||
| const notifications = useResource(NotificationManager()); | ||
| useEffect( | ||
| () => clientRef.parent.subscribe(notifications.notifySubscribers), | ||
| [clientRef, notifications], | ||
| ); | ||
| useEffect( | ||
| () => clientRef.parent.subscribe(notifications.notifySubscribers), | ||
| [clientRef, notifications], | ||
| ); | ||
| const results = useShallowMemoArray( | ||
| useResources( | ||
| () => | ||
| Object.keys(inputClients).map((key) => | ||
| withKey( | ||
| key, | ||
| RootClientAccessorResource({ | ||
| element: inputClients[key as keyof typeof inputClients]!, | ||
| notifications, | ||
| clientRef, | ||
| name: key as keyof typeof inputClients, | ||
| }), | ||
| ), | ||
| const results = useShallowMemoArray( | ||
| useResources( | ||
| () => | ||
| Object.keys(inputClients).map((key) => | ||
| withKey( | ||
| key, | ||
| RootClientAccessorResource({ | ||
| element: inputClients[key as keyof typeof inputClients]!, | ||
| notifications, | ||
| clientRef, | ||
| name: key as keyof typeof inputClients, | ||
| }), | ||
| ), | ||
| [inputClients, notifications, clientRef], | ||
| ), | ||
| ); | ||
| ), | ||
| [inputClients, notifications, clientRef], | ||
| ), | ||
| ); | ||
| return useMemo(() => { | ||
| return { | ||
| clients: results, | ||
| subscribe: notifications.subscribe, | ||
| on: function <TEvent extends AssistantEventName>( | ||
| this: AssistantClient, | ||
| selector: AssistantEventSelector<TEvent>, | ||
| callback: AssistantEventCallback<TEvent>, | ||
| ) { | ||
| if (!this) { | ||
| return useMemo(() => { | ||
| return { | ||
| clients: results, | ||
| subscribe: notifications.subscribe, | ||
| on: function <TEvent extends AssistantEventName>( | ||
| this: AssistantClient, | ||
| selector: AssistantEventSelector<TEvent>, | ||
| callback: AssistantEventCallback<TEvent>, | ||
| ) { | ||
| if (!this) { | ||
| throw new Error( | ||
| "const { on } = useAui() is not supported. Use aui.on() instead.", | ||
| ); | ||
| } | ||
| const { scope, event } = normalizeEventSelector(selector); | ||
| if (scope !== "*") { | ||
| const source = this[scope as ClientNames].source; | ||
| if (source === null) { | ||
| throw new Error( | ||
| "const { on } = useAui() is not supported. Use aui.on() instead.", | ||
| `Scope "${scope}" is not available. Use { scope: "*", event: "${event}" } to listen globally.`, | ||
| ); | ||
| } | ||
| } | ||
| const { scope, event } = normalizeEventSelector(selector); | ||
| const localUnsub = notifications.on(event, (payload, clientStack) => { | ||
| if (scope === "*") { | ||
| callback(payload); | ||
| return; | ||
| } | ||
| if (scope !== "*") { | ||
| const source = this[scope as ClientNames].source; | ||
| if (source === null) { | ||
| throw new Error( | ||
| `Scope "${scope}" is not available. Use { scope: "*", event: "${event}" } to listen globally.`, | ||
| ); | ||
| } | ||
| const scopeClient = this[scope as ClientNames](); | ||
| const index = getClientIndex(scopeClient); | ||
| if (scopeClient === clientStack[index]) { | ||
| callback(payload); | ||
| } | ||
| }); | ||
| if ( | ||
| scope !== "*" && | ||
| clientRef.parent[scope as ClientNames].source === null | ||
| ) | ||
| return localUnsub; | ||
| const localUnsub = notifications.on(event, (payload, clientStack) => { | ||
| if (scope === "*") { | ||
| callback(payload); | ||
| return; | ||
| } | ||
| const parentUnsub = clientRef.parent.on(selector, callback); | ||
| const scopeClient = this[scope as ClientNames](); | ||
| const index = getClientIndex(scopeClient); | ||
| if (scopeClient === clientStack[index]) { | ||
| callback(payload); | ||
| } | ||
| }); | ||
| if ( | ||
| scope !== "*" && | ||
| clientRef.parent[scope as ClientNames].source === null | ||
| ) | ||
| return localUnsub; | ||
| return () => { | ||
| localUnsub(); | ||
| parentUnsub(); | ||
| }; | ||
| }, | ||
| }; | ||
| }, [results, notifications, clientRef]); | ||
| }; | ||
| const parentUnsub = clientRef.parent.on(selector, callback); | ||
| const RootClientsAccessorsResource = resource(useRootClientsAccessorsResource); | ||
| return () => { | ||
| localUnsub(); | ||
| parentUnsub(); | ||
| }; | ||
| }, | ||
| }; | ||
| }, [results, notifications, clientRef]); | ||
| }, | ||
| ); | ||
| const useDerivedClientAccessorResource = <K extends ClientNames>({ | ||
| element, | ||
| clientRef, | ||
| name, | ||
| }: { | ||
| element: DerivedElement<K>; | ||
| clientRef: { parent: AssistantClient; current: AssistantClient | null }; | ||
| name: K; | ||
| }) => { | ||
| // Track the latest props on a ref updated in render. The fiber is | ||
| // keyed on the scope's meta by DerivedClientsAccessorsResource, so | ||
| // source/query are stable for this fiber's lifetime and the only | ||
| // value that can change between renders for the same fiber is the | ||
| // identity of the `get` closure. Routing reads through the ref so | ||
| // they take effect without a one-commit lag. | ||
| const propsRef = useRef(element.args[0]); | ||
| propsRef.current = element.args[0]; | ||
| return useMemo(() => { | ||
| const clientFunction = () => propsRef.current.get(clientRef.current!); | ||
| Object.defineProperties(clientFunction, { | ||
| source: { | ||
| value: propsRef.current.source, | ||
| }, | ||
| query: { | ||
| value: propsRef.current.query, | ||
| }, | ||
| name: { | ||
| value: name, | ||
| configurable: true, | ||
| }, | ||
| }); | ||
| return clientFunction as AssistantClientAccessor<K>; | ||
| }, [clientRef, name]); | ||
| }; | ||
| const DerivedClientAccessorResource = resource( | ||
| function DerivedClientAccessorResource<K extends ClientNames>({ | ||
| element, | ||
| clientRef, | ||
| name, | ||
| }: { | ||
| element: DerivedElement<K>; | ||
| clientRef: { parent: AssistantClient; current: AssistantClient | null }; | ||
| name: K; | ||
| }) { | ||
| // Track the latest props on a ref updated in render. The fiber is | ||
| // keyed on the scope's meta by DerivedClientsAccessorsResource, so | ||
| // source/query are stable for this fiber's lifetime and the only | ||
| // value that can change between renders for the same fiber is the | ||
| // identity of the `get` closure. Routing reads through the ref so | ||
| // they take effect without a one-commit lag. | ||
| const propsRef = useRef(element.props); | ||
| propsRef.current = element.props; | ||
| return useMemo(() => { | ||
| const clientFunction = () => propsRef.current.get(clientRef.current!); | ||
| Object.defineProperties(clientFunction, { | ||
| source: { | ||
| value: propsRef.current.source, | ||
| }, | ||
| query: { | ||
| value: propsRef.current.query, | ||
| }, | ||
| name: { | ||
| value: name, | ||
| configurable: true, | ||
| }, | ||
| }); | ||
| return clientFunction as AssistantClientAccessor<K>; | ||
| }, [clientRef, name]); | ||
| }, | ||
| useDerivedClientAccessorResource, | ||
| ); | ||
@@ -276,30 +285,28 @@ | ||
| const DerivedClientsAccessorsResource = resource( | ||
| function DerivedClientsAccessorsResource({ | ||
| clients, | ||
| clientRef, | ||
| }: { | ||
| clients: DerivedClients; | ||
| clientRef: { parent: AssistantClient; current: AssistantClient | null }; | ||
| }) { | ||
| return useShallowMemoArray( | ||
| useResources( | ||
| () => | ||
| Object.keys(clients).map((key) => { | ||
| const name = key as keyof typeof clients; | ||
| const element = clients[name]!; | ||
| return withKey( | ||
| serializeMeta(name, element.props), | ||
| DerivedClientAccessorResource({ | ||
| element, | ||
| clientRef, | ||
| name, | ||
| }), | ||
| ); | ||
| }), | ||
| [clients, clientRef], | ||
| ), | ||
| ); | ||
| }, | ||
| ); | ||
| const useDerivedClientsAccessorsResource = ({ | ||
| clients, | ||
| clientRef, | ||
| }: { | ||
| clients: DerivedClients; | ||
| clientRef: { parent: AssistantClient; current: AssistantClient | null }; | ||
| }) => { | ||
| return useShallowMemoArray( | ||
| useResources( | ||
| () => | ||
| Object.keys(clients).map((key) => { | ||
| const name = key as keyof typeof clients; | ||
| const element = clients[name]!; | ||
| return withKey( | ||
| serializeMeta(name, element.args[0]), | ||
| DerivedClientAccessorResource({ | ||
| element, | ||
| clientRef, | ||
| name, | ||
| }), | ||
| ); | ||
| }), | ||
| [clients, clientRef], | ||
| ), | ||
| ); | ||
| }; | ||
@@ -309,63 +316,73 @@ /** | ||
| */ | ||
| export const AssistantClientResource = resource( | ||
| function AssistantClientResource({ | ||
| parent, | ||
| clients, | ||
| }: { | ||
| parent: AssistantClient; | ||
| clients: useAui.Props; | ||
| }): AssistantClient { | ||
| const { rootClients, derivedClients } = useSplitClients(clients, parent); | ||
| const useAssistantClient = ({ | ||
| parent, | ||
| clients, | ||
| }: { | ||
| parent: AssistantClient; | ||
| clients: useAui.Props; | ||
| }): AssistantClient => { | ||
| const { rootClients, derivedClients } = useSplitClients(clients, parent); | ||
| const clientRef = useRef({ | ||
| parent: parent, | ||
| current: null as AssistantClient | null, | ||
| }).current; | ||
| const clientRef = useRef({ | ||
| parent: parent, | ||
| current: null as AssistantClient | null, | ||
| }).current; | ||
| useEffect(() => { | ||
| clientRef.current = client; | ||
| useEffect(() => { | ||
| clientRef.current = client; | ||
| }); | ||
| const rootFields = useResource( | ||
| Object.keys(rootClients).length > 0 | ||
| ? RootClientsAccessorsResource({ clients: rootClients, clientRef }) | ||
| : NoOpRootClientsAccessorsResource(), | ||
| ); | ||
| const derivedFields = useDerivedClientsAccessorsResource({ | ||
| clients: derivedClients, | ||
| clientRef, | ||
| }); | ||
| const client = useMemo(() => { | ||
| // Swap DefaultAssistantClient -> createRootAssistantClient at root to change error message | ||
| const proto = | ||
| parent === DefaultAssistantClient ? createRootAssistantClient() : parent; | ||
| const client = Object.create(proto) as AssistantClient; | ||
| Object.assign(client, { | ||
| subscribe: rootFields.subscribe ?? parent.subscribe, | ||
| on: rootFields.on ?? parent.on, | ||
| [PROXIED_ASSISTANT_STATE_SYMBOL]: createProxiedAssistantState(client), | ||
| }); | ||
| const rootFields = useResource( | ||
| Object.keys(rootClients).length > 0 | ||
| ? RootClientsAccessorsResource({ clients: rootClients, clientRef }) | ||
| : NoOpRootClientsAccessorsResource(), | ||
| ); | ||
| for (const field of rootFields.clients) { | ||
| (client as any)[field.name] = field; | ||
| } | ||
| for (const field of derivedFields) { | ||
| (client as any)[field.name] = field; | ||
| } | ||
| const derivedFields = useResource( | ||
| DerivedClientsAccessorsResource({ clients: derivedClients, clientRef }), | ||
| ); | ||
| return client; | ||
| }, [parent, rootFields, derivedFields]); | ||
| const client = useMemo(() => { | ||
| // Swap DefaultAssistantClient -> createRootAssistantClient at root to change error message | ||
| const proto = | ||
| parent === DefaultAssistantClient | ||
| ? createRootAssistantClient() | ||
| : parent; | ||
| if (clientRef.current === null) { | ||
| clientRef.current = client; | ||
| } | ||
| const client = Object.create(proto) as AssistantClient; | ||
| Object.assign(client, { | ||
| subscribe: rootFields.subscribe ?? parent.subscribe, | ||
| on: rootFields.on ?? parent.on, | ||
| [PROXIED_ASSISTANT_STATE_SYMBOL]: createProxiedAssistantState(client), | ||
| }); | ||
| return client; | ||
| }; | ||
| for (const field of rootFields.clients) { | ||
| (client as any)[field.name] = field; | ||
| } | ||
| for (const field of derivedFields) { | ||
| (client as any)[field.name] = field; | ||
| } | ||
| const useHostedAssistantClient = (props: { | ||
| parent: AssistantClient; | ||
| clients: useAui.Props; | ||
| }): AssistantClient => { | ||
| const { value: client, effects } = useTapHost(function AssistantClientHost() { | ||
| return useAssistantClient(props); | ||
| }); | ||
| return client; | ||
| }, [parent, rootFields, derivedFields]); | ||
| (client as Record<symbol, unknown>)[AUI_USE_EFFECTS_SYMBOL] = effects; | ||
| if (clientRef.current === null) { | ||
| clientRef.current = client; | ||
| } | ||
| return client; | ||
| }; | ||
| return client; | ||
| }, | ||
| ); | ||
| export namespace useAui { | ||
@@ -450,8 +467,6 @@ export type Props = { | ||
| if (clients) { | ||
| return useResource( | ||
| AssistantClientResource({ | ||
| parent: parent ?? DefaultAssistantClient, | ||
| clients, | ||
| }), | ||
| ); | ||
| return useHostedAssistantClient({ | ||
| parent: parent ?? DefaultAssistantClient, | ||
| clients, | ||
| }); | ||
| } | ||
@@ -458,0 +473,0 @@ if (parent === null) |
@@ -120,4 +120,4 @@ import { useMemo, useState } from "react"; | ||
| getKey: (data: TData) => string; | ||
| resource: ContravariantResource<TMethods, ResourceProps<TData>>; | ||
| resource: ContravariantResource<TMethods, [ResourceProps<TData>]>; | ||
| }; | ||
| } |
+10
-20
| import { useMemo } from "react"; | ||
| import { | ||
| useResource, | ||
| useResources, | ||
| type ResourceElement, | ||
| } from "@assistant-ui/tap"; | ||
| import { useResources, withKey, type ResourceElement } from "@assistant-ui/tap"; | ||
| import type { ClientMethods } from "./types/client"; | ||
| import { ClientResource } from "./useClientResource"; | ||
| import { wrapperResource } from "./wrapperResource"; | ||
@@ -17,14 +12,8 @@ type InferClientState<TMethods> = TMethods extends { | ||
| const ClientResourceWithKey = wrapperResource( | ||
| <TMethods extends ClientMethods>(el: ResourceElement<TMethods>) => { | ||
| if (el.key === undefined) { | ||
| throw new Error("useClientResource: Element has no key"); | ||
| } | ||
| return useResource(ClientResource(el)) as { | ||
| methods: TMethods; | ||
| state: InferClientState<TMethods>; | ||
| key: string | number; | ||
| }; | ||
| }, | ||
| ); | ||
| const getElementKey = (el: ResourceElement<unknown>) => { | ||
| if (el.key === undefined) { | ||
| throw new Error("useClientLookup: Element has no key"); | ||
| } | ||
| return el.key; | ||
| }; | ||
@@ -39,3 +28,4 @@ export function useClientLookup<TMethods extends ClientMethods>( | ||
| const resources = useResources( | ||
| () => getElements().map((el) => ClientResourceWithKey(el)), | ||
| () => | ||
| getElements().map((el) => withKey(getElementKey(el), ClientResource(el))), | ||
| // oxlint-disable-next-line react/exhaustive-deps -- caller-supplied deps array | ||
@@ -51,3 +41,3 @@ getElementsDeps, | ||
| (acc, resource, index) => { | ||
| acc[resource.key] = index; | ||
| acc[resource.key!] = index; | ||
| return acc; | ||
@@ -54,0 +44,0 @@ }, |
+29
-50
| import { useEffect, useMemo, useRef } from "react"; | ||
| import { useResource, type ResourceElement } from "@assistant-ui/tap"; | ||
| import { resource, useResource, type ResourceElement } from "@assistant-ui/tap"; | ||
| import type { ClientMethods } from "./types/client"; | ||
@@ -13,3 +13,2 @@ import { | ||
| } from "./utils/BaseProxyHandler"; | ||
| import { wrapperResource } from "./wrapperResource"; | ||
@@ -124,45 +123,2 @@ /** | ||
| /** | ||
| * Resource that wraps a plain resource element to create a stable client proxy. | ||
| * | ||
| * Takes a ResourceElement that returns methods (with optional getState()) and | ||
| * wraps it to produce a stable client proxy. This adds the client to the | ||
| * client stack, enabling event scoping. | ||
| * | ||
| * @internal | ||
| */ | ||
| export const ClientResource = wrapperResource( | ||
| <TMethods extends ClientMethods>( | ||
| element: ResourceElement<TMethods>, | ||
| ): { | ||
| methods: TMethods; | ||
| state: unknown; | ||
| key: string | number | undefined; | ||
| } => { | ||
| const valueRef = useRef(null as unknown as TMethods); | ||
| const index = useClientStack().length; | ||
| const methods = useMemo( | ||
| () => | ||
| new Proxy<TMethods>( | ||
| {} as TMethods, | ||
| new ClientProxyHandler(valueRef, index), | ||
| ), | ||
| [index], | ||
| ); | ||
| const value = useWithClientStack(methods, () => useResource(element)); | ||
| if (!valueRef.current) { | ||
| valueRef.current = value; | ||
| } | ||
| useEffect(() => { | ||
| valueRef.current = value; | ||
| }); | ||
| const state = (value as any).getState?.(); | ||
| return { methods, state, key: element.key }; | ||
| }, | ||
| ); | ||
| type InferClientState<TMethods> = TMethods extends { | ||
@@ -181,7 +137,30 @@ getState: () => infer S; | ||
| } => { | ||
| return useResource(ClientResource(element)) as { | ||
| state: InferClientState<TMethods>; | ||
| methods: TMethods; | ||
| key: string | number | undefined; | ||
| }; | ||
| const valueRef = useRef(null as unknown as TMethods); | ||
| const index = useClientStack().length; | ||
| const methods = useMemo( | ||
| () => | ||
| new Proxy<TMethods>( | ||
| {} as TMethods, | ||
| new ClientProxyHandler(valueRef, index), | ||
| ), | ||
| [index], | ||
| ); | ||
| const value = useWithClientStack(methods, function WithClientStack() { | ||
| return useResource(element); | ||
| }); | ||
| if (!valueRef.current) { | ||
| valueRef.current = value; | ||
| } | ||
| useEffect(() => { | ||
| valueRef.current = value; | ||
| }); | ||
| const state = (value as any).getState?.(); | ||
| return { methods, state, key: element.key }; | ||
| }; | ||
| export const ClientResource = resource(useClientResource); |
@@ -29,92 +29,89 @@ import { useMemo } from "react"; | ||
| export const NotificationManager = resource( | ||
| function NotificationManager(): NotificationManager { | ||
| return useMemo(() => { | ||
| const listeners = new Map<string, Set<InternalCallback>>(); | ||
| const wildcardListeners = new Set<InternalCallback>(); | ||
| const subscribers = new Set<() => void>(); | ||
| const useNotificationManager = (): NotificationManager => { | ||
| return useMemo(() => { | ||
| const listeners = new Map<string, Set<InternalCallback>>(); | ||
| const wildcardListeners = new Set<InternalCallback>(); | ||
| const subscribers = new Set<() => void>(); | ||
| return { | ||
| on(event, callback) { | ||
| const cb = callback as InternalCallback; | ||
| if (event === "*") { | ||
| wildcardListeners.add(cb); | ||
| return () => wildcardListeners.delete(cb); | ||
| } | ||
| return { | ||
| on(event, callback) { | ||
| const cb = callback as InternalCallback; | ||
| if (event === "*") { | ||
| wildcardListeners.add(cb); | ||
| return () => wildcardListeners.delete(cb); | ||
| } | ||
| let set = listeners.get(event); | ||
| if (!set) { | ||
| set = new Set(); | ||
| listeners.set(event, set); | ||
| } | ||
| set.add(cb); | ||
| let set = listeners.get(event); | ||
| if (!set) { | ||
| set = new Set(); | ||
| listeners.set(event, set); | ||
| } | ||
| set.add(cb); | ||
| return () => { | ||
| set!.delete(cb); | ||
| if (set!.size === 0) listeners.delete(event); | ||
| }; | ||
| }, | ||
| return () => { | ||
| set!.delete(cb); | ||
| if (set!.size === 0) listeners.delete(event); | ||
| }; | ||
| }, | ||
| emit(event, payload, clientStack) { | ||
| const eventListeners = listeners.get(event); | ||
| if (!eventListeners && wildcardListeners.size === 0) return; | ||
| emit(event, payload, clientStack) { | ||
| const eventListeners = listeners.get(event); | ||
| if (!eventListeners && wildcardListeners.size === 0) return; | ||
| queueMicrotask(() => { | ||
| const errors = []; | ||
| if (eventListeners) { | ||
| for (const cb of eventListeners) { | ||
| try { | ||
| cb(payload, clientStack); | ||
| } catch (e) { | ||
| errors.push(e); | ||
| } | ||
| queueMicrotask(() => { | ||
| const errors = []; | ||
| if (eventListeners) { | ||
| for (const cb of eventListeners) { | ||
| try { | ||
| cb(payload, clientStack); | ||
| } catch (e) { | ||
| errors.push(e); | ||
| } | ||
| } | ||
| if (wildcardListeners.size > 0) { | ||
| const wrapped = { event, payload }; | ||
| for (const cb of wildcardListeners) { | ||
| try { | ||
| cb(wrapped, clientStack); | ||
| } catch (e) { | ||
| errors.push(e); | ||
| } | ||
| } | ||
| if (wildcardListeners.size > 0) { | ||
| const wrapped = { event, payload }; | ||
| for (const cb of wildcardListeners) { | ||
| try { | ||
| cb(wrapped, clientStack); | ||
| } catch (e) { | ||
| errors.push(e); | ||
| } | ||
| } | ||
| } | ||
| if (errors.length > 0) { | ||
| if (errors.length === 1) { | ||
| throw errors[0]; | ||
| } else { | ||
| for (const error of errors) { | ||
| console.error(error); | ||
| } | ||
| throw new AggregateError( | ||
| errors, | ||
| "Errors occurred during event emission", | ||
| ); | ||
| if (errors.length > 0) { | ||
| if (errors.length === 1) { | ||
| throw errors[0]; | ||
| } else { | ||
| for (const error of errors) { | ||
| console.error(error); | ||
| } | ||
| throw new AggregateError( | ||
| errors, | ||
| "Errors occurred during event emission", | ||
| ); | ||
| } | ||
| }); | ||
| }, | ||
| } | ||
| }); | ||
| }, | ||
| subscribe(callback) { | ||
| subscribers.add(callback); | ||
| return () => subscribers.delete(callback); | ||
| }, | ||
| subscribe(callback) { | ||
| subscribers.add(callback); | ||
| return () => subscribers.delete(callback); | ||
| }, | ||
| notifySubscribers() { | ||
| for (const cb of subscribers) { | ||
| try { | ||
| cb(); | ||
| } catch (e) { | ||
| console.error( | ||
| "NotificationManager: subscriber callback error", | ||
| e, | ||
| ); | ||
| } | ||
| notifySubscribers() { | ||
| for (const cb of subscribers) { | ||
| try { | ||
| cb(); | ||
| } catch (e) { | ||
| console.error("NotificationManager: subscriber callback error", e); | ||
| } | ||
| }, | ||
| }; | ||
| }, []); | ||
| }, | ||
| ); | ||
| } | ||
| }, | ||
| }; | ||
| }, []); | ||
| }; | ||
| export const NotificationManager = resource(useNotificationManager); |
| import type React from "react"; | ||
| import { createContext, useContext } from "react"; | ||
| import { createContext, useContext, useEffect } from "react"; | ||
| import type { AssistantClient, AssistantClientAccessor } from "../types/client"; | ||
@@ -83,2 +83,25 @@ import { | ||
| /** | ||
| * Carries the tap host's effects callback on the client so AuiProvider can | ||
| * mount the host's commit ahead of its children's effects. | ||
| */ | ||
| export const AUI_USE_EFFECTS_SYMBOL = Symbol("assistant-ui.store.useEffects"); | ||
| const NOOP_EFFECT = () => {}; | ||
| const getTapEffects = (client: AssistantClient): (() => void) => { | ||
| return ( | ||
| (client as Record<symbol, never>)[AUI_USE_EFFECTS_SYMBOL] ?? NOOP_EFFECT | ||
| ); | ||
| }; | ||
| const UseTapEffects = () => { | ||
| "use no memo"; | ||
| const aui = useAssistantContextValue(); | ||
| // oxlint-disable-next-line react-hooks/exhaustive-deps | ||
| useEffect(getTapEffects(aui)); | ||
| return null; | ||
| }; | ||
| export const useAssistantContextValue = (): AssistantClient => { | ||
@@ -118,4 +141,7 @@ return useContext(AssistantContext); | ||
| }): React.ReactElement => { | ||
| // The <UseTapEffects /> element must be created fresh each render | ||
| "use no memo"; | ||
| return ( | ||
| <AssistantContext.Provider value={value}> | ||
| <UseTapEffects /> | ||
| {children} | ||
@@ -122,0 +148,0 @@ </AssistantContext.Provider> |
@@ -1,2 +0,2 @@ | ||
| import { Derived, type DerivedElement } from "../Derived"; | ||
| import { useDerived, type DerivedElement } from "../Derived"; | ||
| import type { | ||
@@ -10,3 +10,2 @@ AssistantClient, | ||
| import { useMemo } from "react"; | ||
| import { type ResourceElement } from "@assistant-ui/tap"; | ||
@@ -36,9 +35,7 @@ export type RootClients = Partial< | ||
| for (const clientElement of Object.values(scopes)) { | ||
| if (clientElement.type === (Derived as unknown)) continue; | ||
| if (visited.has(clientElement.type)) continue; | ||
| visited.add(clientElement.type); | ||
| if (clientElement.hook === (useDerived as unknown)) continue; | ||
| if (visited.has(clientElement.hook)) continue; | ||
| visited.add(clientElement.hook); | ||
| const transform = getTransformScopes( | ||
| clientElement.type as (props: any) => ResourceElement<any>, | ||
| ); | ||
| const transform = getTransformScopes(clientElement.hook); | ||
| if (transform) { | ||
@@ -60,3 +57,3 @@ transform(scopes, baseClient); | ||
| ][]) { | ||
| if (clientElement.type === (Derived as unknown)) { | ||
| if (clientElement.hook === (useDerived as unknown)) { | ||
| derivedClients[key] = clientElement as DerivedElement<ClientNames>; | ||
@@ -63,0 +60,0 @@ } else { |
| import { Resource, ResourceElement } from "@assistant-ui/tap"; | ||
| //#region src/wrapperResource.d.ts | ||
| declare const wrapperResource: <R, P>(fn: (props: ResourceElement<P>) => R) => Resource<R, ResourceElement<P>>; | ||
| //#endregion | ||
| export { wrapperResource }; | ||
| //# sourceMappingURL=wrapperResource.d.ts.map |
| {"version":3,"file":"wrapperResource.d.ts","names":[],"sources":["../src/wrapperResource.ts"],"mappings":";;;cAOa,eAAA,SACX,EAAA,GAAK,KAAA,EAAO,eAAA,CAAgB,CAAA,MAAO,CAAA,KAClC,QAAA,CAAS,CAAA,EAAG,eAAA,CAAgB,CAAA"} |
| import { resource, withKey } from "@assistant-ui/tap"; | ||
| //#region src/wrapperResource.ts | ||
| const wrapperResource = (fn) => { | ||
| const res = resource(fn); | ||
| return (props) => { | ||
| const el = res(props); | ||
| if (props.key === void 0) return el; | ||
| return withKey(props.key, el); | ||
| }; | ||
| }; | ||
| //#endregion | ||
| export { wrapperResource }; | ||
| //# sourceMappingURL=wrapperResource.js.map |
| {"version":3,"file":"wrapperResource.js","names":[],"sources":["../src/wrapperResource.ts"],"sourcesContent":["import {\n type ResourceElement,\n type Resource,\n resource,\n withKey,\n} from \"@assistant-ui/tap\";\n\nexport const wrapperResource = <R, P>(\n fn: (props: ResourceElement<P>) => R,\n): Resource<R, ResourceElement<P>> => {\n const res = resource(fn);\n return (props: ResourceElement<P>) => {\n const el = res(props);\n if (props.key === undefined) return el;\n return withKey(props.key, el);\n };\n};\n"],"mappings":";;AAOA,MAAa,mBACX,OACoC;CACpC,MAAM,MAAM,SAAS,EAAE;CACvB,QAAQ,UAA8B;EACpC,MAAM,KAAK,IAAI,KAAK;EACpB,IAAI,MAAM,QAAQ,KAAA,GAAW,OAAO;EACpC,OAAO,QAAQ,MAAM,KAAK,EAAE;CAC9B;AACF"} |
| import { | ||
| type ResourceElement, | ||
| type Resource, | ||
| resource, | ||
| withKey, | ||
| } from "@assistant-ui/tap"; | ||
| export const wrapperResource = <R, P>( | ||
| fn: (props: ResourceElement<P>) => R, | ||
| ): Resource<R, ResourceElement<P>> => { | ||
| const res = resource(fn); | ||
| return (props: ResourceElement<P>) => { | ||
| const el = res(props); | ||
| if (props.key === undefined) return el; | ||
| return withKey(props.key, el); | ||
| }; | ||
| }; |
217607
0.63%3795
2.35%66
3.13%103
-3.74%