@bgub/fig 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1 @@
1
+ # Changelog
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) Ben Gubler
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,329 @@
1
+ # @bgub/fig
2
+
3
+ Core primitives for Fig, a TypeScript re-implementation of React's modern
4
+ component model. Renderers live in `@bgub/fig-dom` and `@bgub/fig-server`.
5
+
6
+ ## Installation
7
+
8
+ ```bash
9
+ pnpm add @bgub/fig
10
+ ```
11
+
12
+ Fig packages are ESM-only, require Node `^20.19.0 || >=22.12.0` for Node
13
+ runtime entry points, and rely on package `exports`. TypeScript projects should
14
+ use `moduleResolution: "bundler"`, `"node16"`, or `"nodenext"`, not the legacy
15
+ `"node"` resolver, so subpaths such as `@bgub/fig/jsx-runtime` resolve with
16
+ their types.
17
+
18
+ ## Usage
19
+
20
+ ```tsx
21
+ import { createRoot, on } from "@bgub/fig-dom";
22
+ import { Suspense, readPromise, useState } from "@bgub/fig";
23
+
24
+ const message = Promise.resolve("Ready");
25
+
26
+ function Message({ value }: { value: Promise<string> }) {
27
+ return <span>{readPromise(value)}</span>;
28
+ }
29
+
30
+ function App() {
31
+ const [count, setCount] = useState(0);
32
+
33
+ return (
34
+ <main>
35
+ <button events={[on("click", () => setCount((value) => value + 1))]}>
36
+ Count {count}
37
+ </button>
38
+ <Suspense fallback={<span>Loading</span>}>
39
+ <Message value={message} />
40
+ </Suspense>
41
+ </main>
42
+ );
43
+ }
44
+
45
+ const container = document.getElementById("root");
46
+ if (container === null) throw new Error("Missing root.");
47
+
48
+ createRoot(container).render(<App />);
49
+ ```
50
+
51
+ ## Core API
52
+
53
+ - Elements: `createElement`, `isValidElement`, `Fragment`, and the JSX
54
+ runtime.
55
+ - State and memoization: `useState`, `useDeferredValue`, `useMemo`, and
56
+ `useCallback`.
57
+ - Stable identifiers: `useId()` generates IDs that match server render and
58
+ hydration output.
59
+ - Context: `createContext(defaultValue)` plus render-time
60
+ `readContext(context)`.
61
+ - Effects: `useReactive`, `useBeforePaint`, and `useBeforeLayout`. Effects
62
+ receive an `AbortSignal`; attach cleanup to `signal.abort`. An empty deps
63
+ array runs an effect once per mount.
64
+ - Strict development semantics, with no `StrictMode` component or opt-out:
65
+ development builds render components twice per pass (discarding the first
66
+ result) and run first-time effects and renderer `bind` callbacks twice with
67
+ an abort in between, so impure renders and signal-ignoring cleanup surface
68
+ early. Production builds strip these checks.
69
+ - `useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot?)` reads external
70
+ stores. Server rendering and hydration require `getServerSnapshot`.
71
+ - `useStableEvent(handler)` returns a stable function for effect-held
72
+ callbacks (subscriptions, sockets, timers). The handler always sees the
73
+ latest committed render and follows the Fig event contract: it receives a
74
+ trailing `AbortSignal`, and the previous invocation's signal aborts on
75
+ re-entry and on unmount. Calls after unmount receive an already-aborted
76
+ signal; calling it during render throws.
77
+ - `Suspense` catches pending `readPromise(promise)` reads and shows `fallback`
78
+ until the promise settles.
79
+ - Data resources live in `@bgub/fig`. Use `dataResource(...)` plus
80
+ render-time `readData(...)` for keyed async values that need Suspense,
81
+ deduping, invalidation, refresh, server hydration, and DevTools visibility.
82
+ - `lazy(load)` creates a component that suspends until `load()` resolves to a
83
+ component type.
84
+ - `<Activity mode="visible" | "hidden">` hides a subtree while preserving its
85
+ state: hiding hides the DOM (through portals) and aborts effects, binds, and
86
+ reactive events; revealing restores the DOM and re-runs them. Trees that
87
+ mount hidden defer their effects until first reveal, and updates inside
88
+ hidden trees prerender at idle priority. Server rendering streams hidden
89
+ content inside an inert template; the client keeps it dehydrated — zero
90
+ hydration cost — until reveal, then adopts the server DOM.
91
+ - `ErrorBoundary` catches render and Fig effect errors. Use `onError` for
92
+ reporting and change the boundary key to reset sticky fallback state. Data
93
+ resource load failures report failed keys on `info.dataResourceKeys`, so
94
+ recovery flows can refresh or invalidate those keys before resetting.
95
+ - `transition(callback)` and `useTransition()` mark updates that may preserve
96
+ already-revealed Suspense content while new work is pending. Updates scheduled
97
+ inside the callback run at transition priority, including updates after an
98
+ `await` while an async transition callback is still pending. If the callback
99
+ returns a promise, `useTransition()` keeps `isPending` true until it settles.
100
+ Server rendering runs transition callbacks immediately and never exposes
101
+ pending state.
102
+ - `useActionState(action, initialState)` matches React's argument order while
103
+ staying client-side in Fig today. Actions receive the previous state first,
104
+ then the runner's arguments, then an `AbortSignal` Fig appends — declare the
105
+ trailing signal parameter (it drives `Args` inference):
106
+
107
+ ```ts
108
+ const [count, add, isPending] = useActionState(
109
+ (previous: number, amount: number, signal: AbortSignal) => {
110
+ return fetchNext(previous + amount, { signal });
111
+ },
112
+ 0,
113
+ );
114
+ add(2); // Fig appends the signal; callers pass only the args.
115
+ ```
116
+
117
+ Runs are last-run-wins: a new run aborts the previous one's signal and
118
+ retires it, so a stale settlement (value or rejection) never touches state,
119
+ error, or pending. The signal also aborts on unmount and Activity hide.
120
+ Server actions can layer on top later without changing the hook shape.
121
+
122
+ - Document resources: `assets([...], children)` attaches resources to a
123
+ subtree while rendering only `children` on the client. Resource helpers include
124
+ `stylesheet`, `preload`, `font`, `preconnect`, `title`, `meta`, and `script`.
125
+ Server rendering exposes `title` and `meta` as document head output; stream-safe
126
+ assets such as stylesheets can be emitted near the segments that depend on
127
+ them.
128
+
129
+ ## Renderer APIs
130
+
131
+ Use `@bgub/fig-dom` for browser rendering:
132
+
133
+ - `createRoot(container)` renders client roots.
134
+ - `hydrateRoot(container, children, options?)` hydrates server HTML and reports
135
+ recoverable mismatches with `onRecoverableError`.
136
+ - `createPortal(children, container, key?)` renders into external DOM targets.
137
+ - DOM events use `events={[on("click", (event, signal) => ...)]}`.
138
+ - DOM node access uses `bind={(node, signal) => ...}`.
139
+ - Raw trusted HTML uses `unsafeHTML="<p>trusted html</p>"`.
140
+
141
+ Use `@bgub/fig-server` for streaming server rendering with
142
+ `renderToStream` or `renderToHtml`.
143
+
144
+ ## Data Resources
145
+
146
+ Key-addressable async data resources are part of the core package. Data
147
+ resources are render-time inputs with stable keys: Fig dedupes loads by
148
+ key, wires missing values to `Suspense`, tracks committed readers, and
149
+ lets applications invalidate or refresh specific entries and key
150
+ prefixes. The store implementation travels with `dataResource` itself, so
151
+ bundles that never define a resource never ship it.
152
+
153
+ ### Basic Usage
154
+
155
+ ```tsx
156
+ import { Suspense } from "@bgub/fig";
157
+ import { createRoot, on } from "@bgub/fig-dom";
158
+ import { dataResource, readData, refreshData } from "@bgub/fig";
159
+
160
+ const userResource = dataResource({
161
+ key: (id: string) => ["user", id],
162
+ load: async (id: string, { signal }) => {
163
+ const response = await fetch(`/api/users/${id}`, { signal });
164
+ return response.json() as Promise<{ name: string }>;
165
+ },
166
+ });
167
+
168
+ function Profile({ id }: { id: string }) {
169
+ const user = readData(userResource, id);
170
+
171
+ return (
172
+ <button events={[on("click", () => void refreshData(userResource, id))]}>
173
+ {user.name}
174
+ </button>
175
+ );
176
+ }
177
+
178
+ createRoot(document.getElementById("root")!).render(
179
+ <Suspense fallback={<span>Loading</span>}>
180
+ <Profile id="one" />
181
+ </Suspense>,
182
+ );
183
+ ```
184
+
185
+ `readData(resource, ...args)` is render-only. It returns fulfilled values,
186
+ throws pending loads to `Suspense`, and throws failed initial loads to
187
+ `ErrorBoundary`.
188
+
189
+ ### Keys
190
+
191
+ Every resource has a key function. Keys must be arrays whose first item is a
192
+ string namespace:
193
+
194
+ ```ts
195
+ key: (id: string, locale: string) => ["user", id, { locale }];
196
+ ```
197
+
198
+ Key values may contain strings, finite numbers, booleans, `null`, arrays, and
199
+ plain objects. Object keys are canonicalized in sorted order. `undefined`,
200
+ `NaN`, infinities, functions, symbols, dates, maps, sets, promises, class
201
+ instances, and cycles are invalid.
202
+
203
+ The key is the whole cache identity. Fig uses the canonical key for reads,
204
+ dedupe, invalidation, refresh, SSR serialization, hydration, diagnostics, and
205
+ DevTools.
206
+
207
+ ### Refresh And Invalidate
208
+
209
+ `invalidateData(resource, ...args)` marks an existing entry stale. If the key is
210
+ currently observed, Fig schedules the readers; the next read keeps showing the
211
+ last fulfilled value and starts a background reload. If nobody is observing the
212
+ key, invalidation is lazy and does not fetch.
213
+
214
+ `invalidateDataKey(key)` applies the same stale/reset semantics to one exact
215
+ structured key. `invalidateDataError(error)` invalidates every exact key Fig
216
+ attributed to a caught data error and returns whether any data keys were found.
217
+ Use it from an `ErrorBoundary` fallback before remounting or resetting the
218
+ boundary.
219
+
220
+ `invalidateDataPrefix(prefix)` marks every existing entry whose structured key
221
+ starts with `prefix` stale. For example, `invalidateDataPrefix(["user"])`
222
+ targets entries like `["user", id]`. Prefix matching uses the same canonical
223
+ key encoder as normal reads, so delimiter-shaped strings do not collide.
224
+
225
+ `refreshData(resource, ...args)` fetches immediately and resolves with a result
226
+ instead of rejecting:
227
+
228
+ ```ts
229
+ const result = await refreshData(userResource, "one");
230
+
231
+ if (result.status === "fulfilled") {
232
+ console.log(result.value);
233
+ }
234
+ ```
235
+
236
+ Result statuses are `fulfilled`, `rejected`, `aborted`, and `unsupported`.
237
+ Refresh failures preserve the last fulfilled value when one exists, so visible
238
+ UI does not fall back to Suspense or an error boundary just because a refresh
239
+ failed.
240
+
241
+ ### Root Store Scope
242
+
243
+ Fig installs the current root data store while rendering and while running
244
+ Fig-managed callbacks such as delegated event handlers. Outside those scopes,
245
+ bind the store explicitly:
246
+
247
+ ```ts
248
+ const root = createRoot(container);
249
+
250
+ socket.on("user:update", (id) => {
251
+ root.data.run(() => invalidateData(userResource, id));
252
+ });
253
+ ```
254
+
255
+ `root.data.run(fn)` is synchronous. If you need to invalidate after an `await`,
256
+ keep the handle and re-enter:
257
+
258
+ ```ts
259
+ const data = root.data;
260
+ await save();
261
+ data.run(() => refreshData(userResource, "one"));
262
+ ```
263
+
264
+ Client roots accept `dataPartition` and `initialData` options. The partition
265
+ separates a store's internal keyspace without changing public resource keys.
266
+ Loaders receive only their resource arguments plus `{ signal }`; app services,
267
+ request cookies, and auth state belong in the surrounding adapter or closure,
268
+ not in the data store.
269
+
270
+ ### Server Values And Hydration
271
+
272
+ Server renderers expose fulfilled data entries with `getData()`. Pass those
273
+ entries to the client root as `initialData` to hydrate by key.
274
+
275
+ For server-only data, split the browser-safe key resource from the server
276
+ loader:
277
+
278
+ ```ts
279
+ import { dataResource } from "@bgub/fig";
280
+ import { serverDataResource } from "@bgub/fig/server";
281
+
282
+ export const userKey = (id: string) => ["user", id];
283
+
284
+ export const userResource = dataResource<[string], User>({
285
+ key: userKey,
286
+ });
287
+
288
+ export const userServerResource = serverDataResource({
289
+ key: userKey,
290
+ load: async (id, { signal }) => fetchUser(id, signal),
291
+ });
292
+ ```
293
+
294
+ Client code imports the loader-less resource. If the server streamed a value
295
+ for the same key, the client can read it as a hydrate-only entry. Since the
296
+ resource has no client loader, `refreshData(userResource, id)` resolves with
297
+ `{ status: "unsupported", reason: "no-client-loader" }`; revalidation needs a
298
+ payload or framework refresh path.
299
+
300
+ Browser bundles need the `figData` transform from `@bgub/fig-vite` so imports of
301
+ `.server.ts(x)` modules become loader-free client stubs instead of bundling the
302
+ server loader. Fig Start includes this transform automatically.
303
+
304
+ Direct client refreshes of server data are a framework feature, not a core
305
+ data one: an endpoint has to exist to serve them. Fig Start provides
306
+ `remoteDataResource` (from `@bgub/fig-start/server`), whose browser import
307
+ compiles into a plain `dataResource` with a loader that calls the framework
308
+ data endpoint. Without a framework, define an isomorphic `dataResource` whose
309
+ loader fetches an endpoint you own. Server route payload navigations do not
310
+ make an extra data request either way — data read during the payload render
311
+ streams in that same payload response.
312
+
313
+ ### Activity, Errors, And DevTools
314
+
315
+ Data-resource subscriptions are committed even inside hidden `Activity` trees.
316
+ Invalidating a hidden-only key schedules offscreen prerender work and keeps the
317
+ DOM hidden.
318
+
319
+ Initial load errors are keyed. `ErrorBoundary` reports the failed keys on
320
+ `info.dataResourceKeys`; reset flows can call `invalidateDataError(error)` or
321
+ refresh/invalidate those keys before remounting or changing the boundary key.
322
+
323
+ Fig DevTools root snapshots include data-resource entries with their normalized
324
+ keys, statuses, stale state, subscriber counts, current values, and errors. The
325
+ in-page DevTools exposes them in the `Data` tab.
326
+
327
+ ## License
328
+
329
+ MIT
@@ -0,0 +1,79 @@
1
+ //#region src/data.d.ts
2
+ type DataResourceKeyInput = string | number | boolean | null | readonly DataResourceKeyInput[] | {
3
+ readonly [key: string]: DataResourceKeyInput;
4
+ };
5
+ type DataResourceKey = readonly [string, ...DataResourceKeyInput[]];
6
+ interface DataResourceLoadContext {
7
+ signal: AbortSignal;
8
+ }
9
+ interface DataResource<TArgs extends unknown[] = unknown[], TValue = unknown> {
10
+ readonly $$typeof: symbol;
11
+ readonly debugArgs?: (...args: TArgs) => DataResourceKeyInput;
12
+ readonly key: (...args: TArgs) => DataResourceKey;
13
+ readonly load?: (...argsAndContext: [...TArgs, DataResourceLoadContext]) => TValue | PromiseLike<TValue>;
14
+ }
15
+ type DataRefreshResult<T> = {
16
+ status: "fulfilled";
17
+ value: T;
18
+ } | {
19
+ status: "rejected";
20
+ error: unknown;
21
+ staleValue?: T;
22
+ } | {
23
+ status: "aborted";
24
+ reason: "superseded" | "store-disposed" | "evicted";
25
+ staleValue?: T;
26
+ } | {
27
+ status: "unsupported";
28
+ reason: "no-client-loader";
29
+ staleValue?: T;
30
+ };
31
+ interface FigDataHydrationEntry {
32
+ key: DataResourceKey;
33
+ value: unknown;
34
+ }
35
+ type FigDataEntryStatus = "pending" | "fulfilled" | "rejected" | "refreshing";
36
+ interface DataStoreEntrySnapshot {
37
+ canonicalKey: string;
38
+ error?: unknown;
39
+ hasValue: boolean;
40
+ key: DataResourceKey;
41
+ pending: boolean;
42
+ refreshError?: unknown;
43
+ stale: boolean;
44
+ status: FigDataEntryStatus;
45
+ subscriberCount: number;
46
+ value?: unknown;
47
+ }
48
+ interface FigDataStoreHandle {
49
+ hydrate(entries: readonly FigDataHydrationEntry[]): void;
50
+ invalidateData<TArgs extends unknown[], TValue>(resource: DataResource<TArgs, TValue>, ...args: TArgs): void;
51
+ invalidateDataError(error: unknown): boolean;
52
+ invalidateDataKey(key: DataResourceKey): void;
53
+ invalidateDataPrefix(prefix: DataResourceKey): void;
54
+ preloadData<TArgs extends unknown[], TValue>(resource: DataResource<TArgs, TValue>, ...args: TArgs): void;
55
+ refreshData<TArgs extends unknown[], TValue>(resource: DataResource<TArgs, TValue>, ...args: TArgs): Promise<DataRefreshResult<TValue>>;
56
+ run<T>(callback: () => T): T;
57
+ }
58
+ interface FigDataStore extends FigDataStoreHandle {
59
+ commitDataDependencies(owner: object, previousOwner: object | null): void;
60
+ deleteDataOwner(owner: object): void;
61
+ releaseDataOwner(owner: object): void;
62
+ resetDataDependencies(owner: object): void;
63
+ dispose(): void;
64
+ inspectDataEntries(): DataStoreEntrySnapshot[];
65
+ snapshot(): FigDataHydrationEntry[];
66
+ readData<TArgs extends unknown[], TValue>(resource: DataResource<TArgs, TValue>, args: TArgs, owner: object): TValue;
67
+ }
68
+ interface FigDataStoreHost {
69
+ getLane(): unknown;
70
+ partition?: DataResourceKeyInput;
71
+ schedule(owner: object, lane: unknown): void;
72
+ }
73
+ type FigDataStoreFactory = (host: FigDataStoreHost) => FigDataStore;
74
+ declare function resolveCurrentDataStore(message?: string): FigDataStore;
75
+ declare function setCurrentDataStore(store: FigDataStore | null): FigDataStore | null;
76
+ declare function markDataResourceError(error: unknown, key: DataResourceKey): void;
77
+ declare function dataResourceKeysForError(error: unknown): DataResourceKey[] | undefined;
78
+ //#endregion
79
+ export { DataResourceLoadContext as a, FigDataHydrationEntry as c, FigDataStoreHandle as d, FigDataStoreHost as f, setCurrentDataStore as g, resolveCurrentDataStore as h, DataResourceKeyInput as i, FigDataStore as l, markDataResourceError as m, DataResource as n, DataStoreEntrySnapshot as o, dataResourceKeysForError as p, DataResourceKey as r, FigDataEntryStatus as s, DataRefreshResult as t, FigDataStoreFactory as u };
@@ -0,0 +1,37 @@
1
+ //#region src/data.ts
2
+ const objectDataErrors = /* @__PURE__ */ new WeakMap();
3
+ let currentDataStore = null;
4
+ function resolveCurrentDataStore(message = "Data resource APIs require a Fig data store.") {
5
+ if (currentDataStore === null) throw new Error(message);
6
+ return currentDataStore;
7
+ }
8
+ function setCurrentDataStore(store) {
9
+ const previousStore = currentDataStore;
10
+ currentDataStore = store;
11
+ return previousStore;
12
+ }
13
+ function markDataResourceError(error, key) {
14
+ if (!isObjectKey(error)) return;
15
+ let keys = objectDataErrors.get(error);
16
+ if (keys === void 0) {
17
+ keys = [];
18
+ objectDataErrors.set(error, keys);
19
+ }
20
+ if (keys.some((existing) => sameDataResourceKey(existing, key))) return;
21
+ keys.push(key);
22
+ }
23
+ function dataResourceKeysForError(error) {
24
+ if (!isObjectKey(error)) return void 0;
25
+ const keys = objectDataErrors.get(error);
26
+ return keys === void 0 || keys.length === 0 ? void 0 : [...keys];
27
+ }
28
+ function sameDataResourceKey(a, b) {
29
+ return a.length === b.length && a.every((value, index) => Object.is(value, b[index]));
30
+ }
31
+ function isObjectKey(value) {
32
+ return (typeof value === "object" || typeof value === "function") && value !== null;
33
+ }
34
+ //#endregion
35
+ export { setCurrentDataStore as i, markDataResourceError as n, resolveCurrentDataStore as r, dataResourceKeysForError as t };
36
+
37
+ //# sourceMappingURL=data-h46EcMnE.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"data-h46EcMnE.js","names":[],"sources":["../src/data.ts"],"sourcesContent":["export type DataResourceKeyInput =\n | string\n | number\n | boolean\n | null\n | readonly DataResourceKeyInput[]\n | { readonly [key: string]: DataResourceKeyInput };\n\nexport type DataResourceKey = readonly [string, ...DataResourceKeyInput[]];\n\nexport interface DataResourceLoadContext {\n signal: AbortSignal;\n}\n\nexport interface DataResource<\n TArgs extends unknown[] = unknown[],\n TValue = unknown,\n> {\n readonly $$typeof: symbol;\n readonly debugArgs?: (...args: TArgs) => DataResourceKeyInput;\n readonly key: (...args: TArgs) => DataResourceKey;\n readonly load?: (\n ...argsAndContext: [...TArgs, DataResourceLoadContext]\n ) => TValue | PromiseLike<TValue>;\n}\n\nexport type DataRefreshResult<T> =\n | { status: \"fulfilled\"; value: T }\n | { status: \"rejected\"; error: unknown; staleValue?: T }\n | {\n status: \"aborted\";\n reason: \"superseded\" | \"store-disposed\" | \"evicted\";\n staleValue?: T;\n }\n | {\n status: \"unsupported\";\n reason: \"no-client-loader\";\n staleValue?: T;\n };\n\nexport interface FigDataHydrationEntry {\n key: DataResourceKey;\n value: unknown;\n}\n\nexport type FigDataEntryStatus =\n | \"pending\"\n | \"fulfilled\"\n | \"rejected\"\n | \"refreshing\";\n\nexport interface DataStoreEntrySnapshot {\n canonicalKey: string;\n error?: unknown;\n hasValue: boolean;\n key: DataResourceKey;\n pending: boolean;\n refreshError?: unknown;\n stale: boolean;\n status: FigDataEntryStatus;\n subscriberCount: number;\n value?: unknown;\n}\n\n// The explicit, app-facing store surface (FigRoot.data, readDataStore()).\n// The free functions in @bgub/fig resolve the ambient store slot, which\n// is only set while Fig executes synchronously — render, event handlers, the\n// synchronous prefix of actions and transitions, and effects. After an\n// `await` the slot is gone, so async flows capture this handle first and call\n// its methods instead.\nexport interface FigDataStoreHandle {\n hydrate(entries: readonly FigDataHydrationEntry[]): void;\n invalidateData<TArgs extends unknown[], TValue>(\n resource: DataResource<TArgs, TValue>,\n ...args: TArgs\n ): void;\n invalidateDataError(error: unknown): boolean;\n invalidateDataKey(key: DataResourceKey): void;\n invalidateDataPrefix(prefix: DataResourceKey): void;\n preloadData<TArgs extends unknown[], TValue>(\n resource: DataResource<TArgs, TValue>,\n ...args: TArgs\n ): void;\n refreshData<TArgs extends unknown[], TValue>(\n resource: DataResource<TArgs, TValue>,\n ...args: TArgs\n ): Promise<DataRefreshResult<TValue>>;\n run<T>(callback: () => T): T;\n}\n\nexport interface FigDataStore extends FigDataStoreHandle {\n commitDataDependencies(owner: object, previousOwner: object | null): void;\n deleteDataOwner(owner: object): void;\n releaseDataOwner(owner: object): void;\n resetDataDependencies(owner: object): void;\n dispose(): void;\n inspectDataEntries(): DataStoreEntrySnapshot[];\n snapshot(): FigDataHydrationEntry[];\n // Renderer plumbing, not handle surface: args stay an array because the\n // subscribing owner trails them.\n readData<TArgs extends unknown[], TValue>(\n resource: DataResource<TArgs, TValue>,\n args: TArgs,\n owner: object,\n ): TValue;\n}\n\n// The host callbacks a renderer hands to the data-store factory. Structurally\n// compatible with @bgub/fig's DataStoreHost so its createDataStore can\n// register directly.\nexport interface FigDataStoreHost {\n getLane(): unknown;\n partition?: DataResourceKeyInput;\n schedule(owner: object, lane: unknown): void;\n}\n\nexport type FigDataStoreFactory = (host: FigDataStoreHost) => FigDataStore;\n\nconst objectDataErrors = new WeakMap<object, DataResourceKey[]>();\n\nlet currentDataStore: FigDataStore | null = null;\n\nexport function resolveCurrentDataStore(\n message = \"Data resource APIs require a Fig data store.\",\n): FigDataStore {\n if (currentDataStore === null) throw new Error(message);\n return currentDataStore;\n}\n\nexport function setCurrentDataStore(\n store: FigDataStore | null,\n): FigDataStore | null {\n const previousStore = currentDataStore;\n currentDataStore = store;\n return previousStore;\n}\n\nexport function markDataResourceError(\n error: unknown,\n key: DataResourceKey,\n): void {\n // Only object errors are attributed: the WeakMap keys them by identity, so the\n // registry is GC-safe and cannot cross-attribute. Primitive rejection values\n // would collide by value and accumulate forever in a plain Map, so a thrown\n // primitive simply carries no resource-key metadata.\n if (!isObjectKey(error)) return;\n\n let keys = objectDataErrors.get(error);\n if (keys === undefined) {\n keys = [];\n objectDataErrors.set(error, keys);\n }\n\n if (keys.some((existing) => sameDataResourceKey(existing, key))) return;\n\n keys.push(key);\n}\n\nexport function dataResourceKeysForError(\n error: unknown,\n): DataResourceKey[] | undefined {\n if (!isObjectKey(error)) return undefined;\n\n const keys = objectDataErrors.get(error);\n return keys === undefined || keys.length === 0 ? undefined : [...keys];\n}\n\nfunction sameDataResourceKey(a: DataResourceKey, b: DataResourceKey): boolean {\n return (\n a.length === b.length &&\n a.every((value, index) => Object.is(value, b[index]))\n );\n}\n\nfunction isObjectKey(value: unknown): value is object {\n return (\n (typeof value === \"object\" || typeof value === \"function\") && value !== null\n );\n}\n"],"mappings":";AAsHA,MAAM,mCAAmB,IAAI,QAAmC;AAEhE,IAAI,mBAAwC;AAE5C,SAAgB,wBACd,UAAU,gDACI;CACd,IAAI,qBAAqB,MAAM,MAAM,IAAI,MAAM,OAAO;CACtD,OAAO;AACT;AAEA,SAAgB,oBACd,OACqB;CACrB,MAAM,gBAAgB;CACtB,mBAAmB;CACnB,OAAO;AACT;AAEA,SAAgB,sBACd,OACA,KACM;CAKN,IAAI,CAAC,YAAY,KAAK,GAAG;CAEzB,IAAI,OAAO,iBAAiB,IAAI,KAAK;CACrC,IAAI,SAAS,KAAA,GAAW;EACtB,OAAO,CAAC;EACR,iBAAiB,IAAI,OAAO,IAAI;CAClC;CAEA,IAAI,KAAK,MAAM,aAAa,oBAAoB,UAAU,GAAG,CAAC,GAAG;CAEjE,KAAK,KAAK,GAAG;AACf;AAEA,SAAgB,yBACd,OAC+B;CAC/B,IAAI,CAAC,YAAY,KAAK,GAAG,OAAO,KAAA;CAEhC,MAAM,OAAO,iBAAiB,IAAI,KAAK;CACvC,OAAO,SAAS,KAAA,KAAa,KAAK,WAAW,IAAI,KAAA,IAAY,CAAC,GAAG,IAAI;AACvE;AAEA,SAAS,oBAAoB,GAAoB,GAA6B;CAC5E,OACE,EAAE,WAAW,EAAE,UACf,EAAE,OAAO,OAAO,UAAU,OAAO,GAAG,OAAO,EAAE,MAAM,CAAC;AAExD;AAEA,SAAS,YAAY,OAAiC;CACpD,QACG,OAAO,UAAU,YAAY,OAAO,UAAU,eAAe,UAAU;AAE5E"}