@bgub/fig 0.1.0-alpha.1 → 0.1.0-alpha.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,750 @@
1
+ import { Q as isAttributableError, X as dataResourceKeysForError, Z as defineLoadContextCapabilities, et as markDataResourceError, nt as setCurrentDataStore, tt as resolveCurrentDataStore } from "./element-B7mCQIMi.js";
2
+ //#region src/context.ts
3
+ const FigContextSymbol = Symbol.for("fig.context");
4
+ function createContext(defaultValue) {
5
+ return Object.assign((props) => props.children, {
6
+ $$typeof: FigContextSymbol,
7
+ defaultValue
8
+ });
9
+ }
10
+ function isContext(value) {
11
+ return typeof value === "function" && "$$typeof" in value && value.$$typeof === FigContextSymbol;
12
+ }
13
+ //#endregion
14
+ //#region src/data-store.ts
15
+ const DataResourceSymbol = Symbol.for("fig.data-resource");
16
+ const DataStoreFactorySymbol = Symbol.for("fig.data-store-factory");
17
+ const DataStoreControllerSymbol = Symbol.for("fig.data-store-controller");
18
+ const DEFAULT_INACTIVE_RETENTION_MS = 300 * 1e3;
19
+ const DEFAULT_PRELOAD_RETENTION_MS = 30 * 1e3;
20
+ const dataStoreFactory = createRendererDataStore;
21
+ function dataResource(options) {
22
+ return {
23
+ $$typeof: DataResourceSymbol,
24
+ [DataStoreFactorySymbol]: dataStoreFactory,
25
+ debugArgs: options.debugArgs,
26
+ key: options.key,
27
+ load: options.load
28
+ };
29
+ }
30
+ function ensureData(resource, ...args) {
31
+ return resolveDataMutationStore("ensureData").ensureData(resource, ...args);
32
+ }
33
+ function invalidateData(resource, ...args) {
34
+ resolveDataMutationStore("invalidateData").invalidateData(resource, ...args);
35
+ }
36
+ function invalidateDataError(error) {
37
+ return resolveDataMutationStore("invalidateDataError").invalidateDataError(error);
38
+ }
39
+ function invalidateDataKey(key) {
40
+ resolveDataMutationStore("invalidateDataKey").invalidateDataKey(key);
41
+ }
42
+ function invalidateDataPrefix(prefix) {
43
+ resolveDataMutationStore("invalidateDataPrefix").invalidateDataPrefix(prefix);
44
+ }
45
+ function refreshData(resource, ...args) {
46
+ return resolveDataMutationStore("refreshData").refreshData(resource, ...args);
47
+ }
48
+ function readDataStore() {
49
+ return resolveCurrentDataStore("readDataStore() must be called synchronously while Fig is executing — during render, an event handler, an action, or an effect. Capture the handle there and use it after awaits.");
50
+ }
51
+ function createDataStore(options = {}) {
52
+ const state = { host: null };
53
+ const store = createRendererDataStore({
54
+ getLane: () => state.host === null ? null : state.host.getLane(),
55
+ partition: options.partition,
56
+ schedule: (owner, lane) => state.host?.schedule(owner, lane)
57
+ });
58
+ Object.defineProperty(store, DataStoreControllerSymbol, { value: state });
59
+ if (options.initialData !== void 0) store.hydrate(options.initialData);
60
+ return store;
61
+ }
62
+ function isDataStoreController(value) {
63
+ return (typeof value === "object" || typeof value === "function") && value !== null && Object.hasOwn(value, DataStoreControllerSymbol);
64
+ }
65
+ function attachDataStore(controller, host, initialData) {
66
+ if (host.partition !== void 0 || initialData !== void 0) throw new Error("Pass partition and initialData to createDataStore(), not the renderer, when adopting a data store.");
67
+ const state = controller[DataStoreControllerSymbol];
68
+ if (state === void 0) throw new Error("dataStore must be created with createDataStore().");
69
+ if (state.host !== null) throw new Error("A data store can only be adopted by one Fig renderer.");
70
+ state.host = host;
71
+ return controller;
72
+ }
73
+ function resolveDataMutationStore(name) {
74
+ return resolveCurrentDataStore(`${name}() must be called synchronously while Fig is executing — during render, an event handler, an action, or an effect. Capture readDataStore() (or root.data) synchronously and call the handle instead.`);
75
+ }
76
+ function createRendererDataStore(host) {
77
+ return new DefaultDataStore(host);
78
+ }
79
+ function normalizeDataResourceKey(key) {
80
+ return normalizeKey(key).canonical;
81
+ }
82
+ var DefaultDataStore = class {
83
+ host;
84
+ entries = /* @__PURE__ */ new Map();
85
+ inactiveRetentionMs;
86
+ ownerKeys = /* @__PURE__ */ new WeakMap();
87
+ pendingOwnerKeys = /* @__PURE__ */ new WeakMap();
88
+ partitionKey;
89
+ preloadRetentionMs;
90
+ disposed = false;
91
+ constructor(host) {
92
+ this.host = host;
93
+ this.inactiveRetentionMs = host.inactiveRetentionMs ?? DEFAULT_INACTIVE_RETENTION_MS;
94
+ this.partitionKey = host.partition === void 0 ? "" : encodeValue(host.partition, createEncodePath("partition"));
95
+ this.preloadRetentionMs = host.preloadRetentionMs ?? DEFAULT_PRELOAD_RETENTION_MS;
96
+ }
97
+ commitDataDependencies(owner, previousOwner) {
98
+ const nextKeys = this.pendingOwnerKeys.get(owner) ?? null;
99
+ const ownerKeys = this.ownerKeys.get(owner) ?? null;
100
+ const previousOwnerKeys = previousOwner === null ? null : this.ownerKeys.get(previousOwner) ?? null;
101
+ this.pendingOwnerKeys.delete(owner);
102
+ if ((nextKeys === null || nextKeys.size === 0) && ownerKeys === null && previousOwnerKeys === null) return;
103
+ let orphanCandidates = null;
104
+ orphanCandidates = this.collectSubscribedEntries(ownerKeys, orphanCandidates);
105
+ orphanCandidates = this.collectSubscribedEntries(previousOwnerKeys, orphanCandidates);
106
+ this.deleteDataOwner(owner, nextKeys);
107
+ if (previousOwner !== null) this.deleteDataOwner(previousOwner, nextKeys);
108
+ if (nextKeys !== null && nextKeys.size > 0) {
109
+ this.ownerKeys.set(owner, nextKeys);
110
+ for (const key of nextKeys) {
111
+ const entry = this.entries.get(key);
112
+ if (entry !== void 0) {
113
+ this.clearInactiveTimer(entry);
114
+ entry.subscribers.add(owner);
115
+ this.notifyEntryChange(entry);
116
+ }
117
+ }
118
+ }
119
+ if (orphanCandidates !== null) for (const entry of orphanCandidates) this.abortOrphanedLoad(entry);
120
+ }
121
+ releaseDataOwner(owner) {
122
+ const orphanCandidates = this.collectSubscribedEntries(this.ownerKeys.get(owner) ?? null, null);
123
+ this.deleteDataOwner(owner);
124
+ if (orphanCandidates !== null) for (const entry of orphanCandidates) this.abortOrphanedLoad(entry);
125
+ }
126
+ resetDataDependencies(owner) {
127
+ this.pendingOwnerKeys.delete(owner);
128
+ }
129
+ deleteDataOwner(owner, retainedKeys = null) {
130
+ this.pendingOwnerKeys.delete(owner);
131
+ const keys = this.ownerKeys.get(owner);
132
+ if (keys === void 0) return;
133
+ this.ownerKeys.delete(owner);
134
+ for (const key of keys) {
135
+ const entry = this.entries.get(key);
136
+ if (entry === void 0) continue;
137
+ entry.subscribers.delete(owner);
138
+ this.notifyEntryChange(entry);
139
+ if (retainedKeys?.has(key) !== true) this.scheduleInactiveCleanup(entry);
140
+ }
141
+ }
142
+ collectSubscribedEntries(keys, into) {
143
+ if (keys === null) return into;
144
+ for (const key of keys) {
145
+ const entry = this.entries.get(key);
146
+ if (entry !== void 0) {
147
+ into ??= /* @__PURE__ */ new Set();
148
+ into.add(entry);
149
+ }
150
+ }
151
+ return into;
152
+ }
153
+ abortOrphanedLoad(entry) {
154
+ if (this.entries.get(entry.storeKey) !== entry) return;
155
+ if (entry.subscribers.size > 0 || entry.preloadTimer !== null || entry.ensureRetainers > 0 || entry.pending === null || entryHasValue(entry)) return;
156
+ this.evictEntry(entry, "evicted");
157
+ }
158
+ dispose() {
159
+ this.disposed = true;
160
+ for (const entry of this.entries.values()) {
161
+ this.clearInactiveTimer(entry);
162
+ this.clearPreloadTimer(entry);
163
+ this.abortEntryGenerations(entry, "store-disposed");
164
+ this.notifyEntryChange(entry);
165
+ }
166
+ }
167
+ hydrate(entries) {
168
+ if (this.disposed) return;
169
+ for (const hydrated of entries) {
170
+ const normalized = normalizeKey(hydrated.key);
171
+ const storeKey = this.storeKey(normalized.canonical);
172
+ const current = this.entries.get(storeKey);
173
+ if (current !== void 0) {
174
+ this.hydrateEntry(current, normalized.key, hydrated.value);
175
+ this.publish(current);
176
+ continue;
177
+ }
178
+ const entry = this.createEntry(normalized, storeKey, null, null, "fulfilled", hydrated.value);
179
+ this.entries.set(storeKey, entry);
180
+ this.notifyEntryChange(entry);
181
+ }
182
+ }
183
+ snapshot() {
184
+ const entries = [];
185
+ for (const entry of this.entries.values()) if (entryHasValue(entry)) entries.push({
186
+ key: entry.key,
187
+ value: entry.value
188
+ });
189
+ return entries;
190
+ }
191
+ inspectDataEntries() {
192
+ return Array.from(this.entries.values(), (entry) => this.snapshotEntry(entry));
193
+ }
194
+ inspectDataDependencyCanonicalKeys(owner) {
195
+ const keys = this.ownerKeys.get(owner);
196
+ if (keys === void 0) return [];
197
+ const dependencies = [];
198
+ for (const key of keys) {
199
+ const entry = this.entries.get(key);
200
+ if (entry !== void 0) dependencies.push(entry.canonicalKey);
201
+ }
202
+ return dependencies;
203
+ }
204
+ invalidateData(resource, ...args) {
205
+ if (this.disposed) return;
206
+ const entry = this.entryFor(resource, args, false);
207
+ if (entry === null) return;
208
+ this.invalidateEntry(entry, this.host.getLane());
209
+ }
210
+ invalidateDataError(error) {
211
+ if (this.disposed) return false;
212
+ const keys = dataResourceKeysForError(error);
213
+ if (keys === void 0 || keys.length === 0) return false;
214
+ const entries = [];
215
+ for (const key of keys) {
216
+ const entry = this.entryForKey(key);
217
+ if (entry !== null) entries.push(entry);
218
+ }
219
+ this.invalidateEntries(entries, error);
220
+ return true;
221
+ }
222
+ invalidateDataKey(key) {
223
+ if (this.disposed) return;
224
+ const entry = this.entryForKey(key);
225
+ if (entry === null) return;
226
+ this.invalidateEntry(entry, this.host.getLane());
227
+ }
228
+ invalidateDataPrefix(prefix) {
229
+ if (this.disposed) return;
230
+ const prefixCanonical = normalizeKey(prefix).canonical;
231
+ const entries = [];
232
+ for (const entry of this.entries.values()) if (canonicalKeyStartsWith(entry.canonicalKey, prefixCanonical)) entries.push(entry);
233
+ this.invalidateEntries(entries);
234
+ }
235
+ async ensureData(resource, ...args) {
236
+ for (;;) {
237
+ if (this.disposed) throw new Error("ensureData() requires a live data store; this store was disposed.");
238
+ const entry = this.entryFor(resource, args, true);
239
+ this.clearInactiveTimer(entry);
240
+ this.retainPreload(entry);
241
+ if (entryHasValue(entry)) {
242
+ this.revalidateIfStale(entry, resource, args);
243
+ return entry.value;
244
+ }
245
+ if (entry.status === "rejected") throwDataResourceError(entry);
246
+ entry.ensureRetainers += 1;
247
+ try {
248
+ if (entry.pending !== null) await entry.pending.promise;
249
+ else await this.startLoad(entry, resource, args, {
250
+ lane: this.host.getLane(),
251
+ refresh: false
252
+ });
253
+ } finally {
254
+ entry.ensureRetainers -= 1;
255
+ if (this.entries.get(entry.storeKey) === entry) this.retainPreload(entry);
256
+ }
257
+ }
258
+ }
259
+ preloadData(resource, ...args) {
260
+ if (this.disposed || resource.load === void 0) return;
261
+ const entry = this.entryFor(resource, args, true);
262
+ this.clearInactiveTimer(entry);
263
+ this.retainPreload(entry);
264
+ if (entry.pending !== null) return;
265
+ if (entry.status === "fulfilled" && !entry.stale) return;
266
+ this.startLoad(entry, resource, args, {
267
+ lane: this.host.getLane(),
268
+ refresh: entry.status === "fulfilled"
269
+ });
270
+ }
271
+ readData(resource, args, owner) {
272
+ const entry = this.entryFor(resource, args, true);
273
+ this.clearInactiveTimer(entry);
274
+ this.clearPreloadTimer(entry);
275
+ this.addOwnerKey(owner, entry.storeKey);
276
+ this.revalidateIfStale(entry, resource, args);
277
+ if (entry.status === "pending" && entry.pending === null) this.startLoad(entry, resource, args, {
278
+ lane: this.host.getLane(),
279
+ refresh: false
280
+ });
281
+ return this.readCurrentValue(entry);
282
+ }
283
+ refreshData(resource, ...args) {
284
+ if (this.disposed) return Promise.resolve({
285
+ reason: "store-disposed",
286
+ staleValue: void 0,
287
+ status: "aborted"
288
+ });
289
+ if (resource.load === void 0) {
290
+ const entry = this.entryFor(resource, args, false);
291
+ if (entry === null) return Promise.resolve(unsupportedRefreshResult());
292
+ this.clearInactiveTimer(entry);
293
+ return Promise.resolve(unsupportedRefreshResult(entry));
294
+ }
295
+ const entry = this.entryFor(resource, args, true);
296
+ this.clearInactiveTimer(entry);
297
+ if (entry.pending !== null && entry.status !== "refreshing") return entry.pending.promise;
298
+ return this.startLoad(entry, resource, args, {
299
+ lane: this.host.getLane(),
300
+ refresh: entry.status === "fulfilled" || entry.status === "refreshing"
301
+ });
302
+ }
303
+ run(callback) {
304
+ const previousStore = setCurrentDataStore(this);
305
+ try {
306
+ return callback();
307
+ } finally {
308
+ setCurrentDataStore(previousStore);
309
+ }
310
+ }
311
+ addOwnerKey(owner, key) {
312
+ let keys = this.pendingOwnerKeys.get(owner);
313
+ if (keys === void 0) {
314
+ keys = /* @__PURE__ */ new Set();
315
+ this.pendingOwnerKeys.set(owner, keys);
316
+ }
317
+ keys.add(key);
318
+ }
319
+ entryFor(resource, args, create) {
320
+ const normalized = normalizeKey(resource.key(...args));
321
+ const fingerprint = fingerprintFor(resource, args);
322
+ const key = this.storeKey(normalized.canonical);
323
+ const current = this.entries.get(key);
324
+ if (current !== void 0) {
325
+ diagnoseEntryDrift(current, resource, normalized.canonical, fingerprint);
326
+ return current;
327
+ }
328
+ if (!create) return null;
329
+ const entry = this.createEntry(normalized, key, resource, fingerprint, "pending", void 0);
330
+ if (this.disposed) return entry;
331
+ this.entries.set(key, entry);
332
+ this.notifyEntryChange(entry);
333
+ return entry;
334
+ }
335
+ revalidateIfStale(entry, resource, args) {
336
+ if (entry.status !== "fulfilled" || !entry.stale || entry.pending !== null || entry.refreshError !== void 0 || resource.load === void 0) return;
337
+ this.startLoad(entry, resource, args, {
338
+ lane: this.host.getLane(),
339
+ refresh: true
340
+ });
341
+ }
342
+ entryForKey(key) {
343
+ const normalized = normalizeKey(key);
344
+ return this.entries.get(this.storeKey(normalized.canonical)) ?? null;
345
+ }
346
+ createEntry(normalized, storeKey, resource, fingerprint, status, value) {
347
+ return {
348
+ canonicalKey: normalized.canonical,
349
+ controller: null,
350
+ ensureRetainers: 0,
351
+ error: void 0,
352
+ fingerprint,
353
+ generation: 0,
354
+ invalidationVersion: 0,
355
+ inactiveTimer: null,
356
+ key: normalized.key,
357
+ lane: null,
358
+ pending: null,
359
+ preloadTimer: null,
360
+ refreshError: void 0,
361
+ resource,
362
+ stale: false,
363
+ status,
364
+ storeKey,
365
+ subscribers: /* @__PURE__ */ new Set(),
366
+ value,
367
+ valueController: null,
368
+ valueErrors: /* @__PURE__ */ new WeakSet()
369
+ };
370
+ }
371
+ hydrateEntry(entry, key, value) {
372
+ this.clearInactiveTimer(entry);
373
+ this.abortEntryGenerations(entry, "superseded");
374
+ entry.error = void 0;
375
+ entry.generation += 1;
376
+ entry.valueErrors = /* @__PURE__ */ new WeakSet();
377
+ entry.invalidationVersion = 0;
378
+ entry.key = key;
379
+ entry.lane = null;
380
+ entry.refreshError = void 0;
381
+ entry.stale = false;
382
+ entry.status = "fulfilled";
383
+ entry.value = value;
384
+ }
385
+ storeKey(canonicalKey) {
386
+ return this.partitionKey === "" ? canonicalKey : `${this.partitionKey}:${canonicalKey}`;
387
+ }
388
+ startLoad(entry, resource, args, options) {
389
+ const hadValue = entryHasValue(entry);
390
+ const load = resource.load;
391
+ if (this.disposed) return Promise.resolve(abortedRefreshResult(entry, "store-disposed"));
392
+ if (load === void 0) {
393
+ const error = /* @__PURE__ */ new Error(`Data resource "${entry.canonicalKey}" has no loader and no hydrated value.`);
394
+ entry.error = error;
395
+ entry.status = "rejected";
396
+ markDataResourceError(error, entry.key);
397
+ this.notifyEntryChange(entry);
398
+ return Promise.resolve(unsupportedRefreshResult(entry));
399
+ }
400
+ this.abortPendingLoad(entry, "superseded");
401
+ const controller = new AbortController();
402
+ const valueErrors = /* @__PURE__ */ new WeakSet();
403
+ const generation = entry.generation + 1;
404
+ const invalidationVersion = entry.invalidationVersion;
405
+ const pending = createPendingResult();
406
+ entry.controller = controller;
407
+ entry.generation = generation;
408
+ entry.lane = options.lane;
409
+ entry.pending = pending;
410
+ entry.status = options.refresh && hadValue ? "refreshing" : "pending";
411
+ this.notifyEntryChange(entry);
412
+ let loaded;
413
+ try {
414
+ loaded = load(...args, this.createLoadContext(entry, controller, valueErrors));
415
+ } catch (error) {
416
+ loaded = Promise.reject(error);
417
+ }
418
+ const settleAborted = () => {
419
+ const result = abortedRefreshResult(entry, "superseded");
420
+ pending.resolve(result);
421
+ return result;
422
+ };
423
+ const fulfill = (value) => {
424
+ if (entry.generation !== generation || controller.signal.aborted) return settleAborted();
425
+ const superseded = entry.valueController;
426
+ entry.controller = null;
427
+ entry.valueController = controller;
428
+ entry.valueErrors = valueErrors;
429
+ entry.error = void 0;
430
+ entry.pending = null;
431
+ entry.refreshError = void 0;
432
+ entry.stale = entry.invalidationVersion !== invalidationVersion;
433
+ entry.status = "fulfilled";
434
+ entry.value = value;
435
+ this.publish(entry);
436
+ superseded?.abort();
437
+ const result = {
438
+ status: "fulfilled",
439
+ value
440
+ };
441
+ pending.resolve(result);
442
+ return result;
443
+ };
444
+ const reject = (error) => {
445
+ if (entry.generation !== generation || controller.signal.aborted) return settleAborted();
446
+ entry.controller = null;
447
+ controller.abort();
448
+ entry.pending = null;
449
+ if (hadValue && entryHasValue(entry)) {
450
+ entry.refreshError = error;
451
+ entry.stale = true;
452
+ entry.status = "fulfilled";
453
+ this.publish(entry);
454
+ const result = {
455
+ error,
456
+ staleValue: entry.value,
457
+ status: "rejected"
458
+ };
459
+ pending.resolve(result);
460
+ return result;
461
+ }
462
+ entry.error = error;
463
+ entry.status = "rejected";
464
+ markDataResourceError(error, entry.key);
465
+ this.publish(entry);
466
+ const result = {
467
+ error,
468
+ status: "rejected"
469
+ };
470
+ pending.resolve(result);
471
+ return result;
472
+ };
473
+ if (!isThenable(loaded)) {
474
+ fulfill(loaded);
475
+ return pending.promise;
476
+ }
477
+ Promise.resolve(loaded).then(fulfill, reject);
478
+ return pending.promise;
479
+ }
480
+ createLoadContext(entry, controller, valueErrors) {
481
+ const context = { signal: controller.signal };
482
+ defineLoadContextCapabilities(context, {
483
+ key: entry.key,
484
+ attributeError: (error) => {
485
+ if (this.disposed || controller.signal.aborted) return;
486
+ markDataResourceError(error, entry.key);
487
+ if (isAttributableError(error)) valueErrors.add(error);
488
+ },
489
+ hydrate: (entries) => {
490
+ if (this.disposed || controller.signal.aborted) return;
491
+ const foreign = entries.filter((hydrated) => this.storeKey(normalizeKey(hydrated.key).canonical) !== entry.storeKey);
492
+ if (foreign.length !== entries.length) warn(`Data rows targeting the loading key ${entry.canonicalKey} were skipped: a loader cannot hydrate its own entry.`);
493
+ if (foreign.length > 0) this.hydrate(foreign);
494
+ }
495
+ });
496
+ return context;
497
+ }
498
+ publish(entry) {
499
+ this.notifyEntryChange(entry);
500
+ this.scheduleInactiveCleanup(entry);
501
+ this.scheduleSubscribers(entry, entry.lane ?? this.host.getLane());
502
+ }
503
+ scheduleSubscribers(entry, lane) {
504
+ for (const subscriber of entry.subscribers) this.host.schedule(subscriber, lane);
505
+ }
506
+ abortPendingLoad(entry, reason) {
507
+ entry.controller?.abort();
508
+ entry.controller = null;
509
+ const pending = entry.pending;
510
+ if (pending === null) return;
511
+ entry.pending = null;
512
+ pending.resolve(abortedRefreshResult(entry, reason));
513
+ }
514
+ abortEntryGenerations(entry, reason) {
515
+ this.abortPendingLoad(entry, reason);
516
+ entry.valueController?.abort();
517
+ entry.valueController = null;
518
+ }
519
+ retainPreload(entry) {
520
+ this.clearPreloadTimer(entry);
521
+ if (this.disposed || !Number.isFinite(this.preloadRetentionMs)) return;
522
+ entry.preloadTimer = scheduleStoreTimer(() => {
523
+ entry.preloadTimer = null;
524
+ if (entry.subscribers.size === 0 && entry.ensureRetainers === 0 && entry.pending !== null && !entryHasValue(entry)) {
525
+ this.evictEntry(entry, "evicted");
526
+ return;
527
+ }
528
+ this.scheduleInactiveCleanup(entry);
529
+ }, Math.max(0, this.preloadRetentionMs));
530
+ }
531
+ scheduleInactiveCleanup(entry) {
532
+ if (this.disposed || entry.subscribers.size > 0 || entry.pending !== null || entry.preloadTimer !== null || entry.ensureRetainers > 0 || !Number.isFinite(this.inactiveRetentionMs)) return;
533
+ this.clearInactiveTimer(entry);
534
+ entry.inactiveTimer = scheduleStoreTimer(() => {
535
+ entry.inactiveTimer = null;
536
+ if (entry.subscribers.size > 0 || entry.pending !== null || entry.preloadTimer !== null || entry.ensureRetainers > 0) return;
537
+ this.evictEntry(entry, "evicted");
538
+ }, Math.max(0, this.inactiveRetentionMs));
539
+ }
540
+ evictEntry(entry, reason) {
541
+ if (this.entries.get(entry.storeKey) !== entry) return;
542
+ this.clearInactiveTimer(entry);
543
+ this.clearPreloadTimer(entry);
544
+ this.abortEntryGenerations(entry, reason);
545
+ this.entries.delete(entry.storeKey);
546
+ this.host.onEntryEvict?.(this.snapshotEntry(entry));
547
+ }
548
+ clearInactiveTimer(entry) {
549
+ if (entry.inactiveTimer === null) return;
550
+ clearTimeout(entry.inactiveTimer);
551
+ entry.inactiveTimer = null;
552
+ }
553
+ clearPreloadTimer(entry) {
554
+ if (entry.preloadTimer === null) return;
555
+ clearTimeout(entry.preloadTimer);
556
+ entry.preloadTimer = null;
557
+ }
558
+ notifyEntryChange(entry) {
559
+ this.host.onEntryChange?.(this.snapshotEntry(entry));
560
+ }
561
+ snapshotEntry(entry) {
562
+ const hasValue = entryHasValue(entry);
563
+ return {
564
+ canonicalKey: entry.canonicalKey,
565
+ error: entry.status === "rejected" ? entry.error : void 0,
566
+ hasValue,
567
+ key: entry.key,
568
+ pending: entry.pending !== null,
569
+ refreshError: entry.refreshError,
570
+ stale: entry.stale,
571
+ status: entry.status,
572
+ subscriberCount: entry.subscribers.size,
573
+ value: hasValue ? entry.value : void 0
574
+ };
575
+ }
576
+ readCurrentValue(entry) {
577
+ if (entryHasValue(entry)) return entry.value;
578
+ if (entry.status === "rejected") throwDataResourceError(entry);
579
+ if (entry.pending === null) throw new Error(`Data resource "${entry.canonicalKey}" has no pending load.`);
580
+ throw entry.pending.promise;
581
+ }
582
+ invalidateEntry(entry, lane, attributedError) {
583
+ entry.invalidationVersion += 1;
584
+ entry.stale = true;
585
+ entry.refreshError = void 0;
586
+ if (isAttributableError(attributedError) && entry.valueErrors.has(attributedError)) {
587
+ entry.valueController?.abort();
588
+ entry.valueController = null;
589
+ entry.valueErrors = /* @__PURE__ */ new WeakSet();
590
+ entry.value = void 0;
591
+ entry.error = void 0;
592
+ entry.status = "pending";
593
+ } else if (entry.status === "rejected") {
594
+ entry.error = void 0;
595
+ entry.status = "pending";
596
+ }
597
+ this.notifyEntryChange(entry);
598
+ if (entry.subscribers.size === 0) return;
599
+ this.scheduleSubscribers(entry, lane);
600
+ }
601
+ invalidateEntries(entries, attributedError) {
602
+ if (entries.length === 0) return;
603
+ const lane = this.host.getLane();
604
+ for (const entry of entries) this.invalidateEntry(entry, lane, attributedError);
605
+ }
606
+ };
607
+ function normalizeKey(key) {
608
+ if (!Array.isArray(key) || typeof key[0] !== "string") throw new Error("Data resource keys must be arrays starting with a string.");
609
+ return {
610
+ canonical: encodeArray(key, createEncodePath("key")),
611
+ key
612
+ };
613
+ }
614
+ function canonicalKeyStartsWith(key, prefix) {
615
+ if (key === prefix) return true;
616
+ const arrayPrefix = prefix.slice(0, -1);
617
+ return key.startsWith(arrayPrefix) && key[arrayPrefix.length] === ",";
618
+ }
619
+ function entryHasValue(entry) {
620
+ return entry.status === "fulfilled" || entry.status === "refreshing";
621
+ }
622
+ function throwDataResourceError(entry) {
623
+ markDataResourceError(entry.error, entry.key);
624
+ throw entry.error;
625
+ }
626
+ function abortedRefreshResult(entry, reason) {
627
+ return {
628
+ reason,
629
+ staleValue: entryHasValue(entry) ? entry.value : void 0,
630
+ status: "aborted"
631
+ };
632
+ }
633
+ function unsupportedRefreshResult(entry) {
634
+ const result = {
635
+ reason: "no-client-loader",
636
+ status: "unsupported"
637
+ };
638
+ if (entry !== void 0) result.staleValue = entryHasValue(entry) ? entry.value : void 0;
639
+ return result;
640
+ }
641
+ function fingerprintFor(resource, args) {
642
+ if (resource.debugArgs !== void 0) return encodeValue(resource.debugArgs(...args), createEncodePath("debugArgs"));
643
+ try {
644
+ return encodeArray(args, createEncodePath("args"));
645
+ } catch {
646
+ return null;
647
+ }
648
+ }
649
+ function diagnoseEntryDrift(entry, resource, key, fingerprint) {
650
+ if (entry.resource === null) entry.resource = resource;
651
+ else if (entry.resource !== resource) warn(`Data resource key ${key} was read by multiple resource definitions.`);
652
+ if (entry.fingerprint !== null && fingerprint !== null && entry.fingerprint !== fingerprint) warn(`Data resource key ${key} was read with different argument fingerprints.`);
653
+ }
654
+ function warn(message) {
655
+ if (typeof console === "undefined") return;
656
+ console.warn(message);
657
+ }
658
+ function createEncodePath(root) {
659
+ return {
660
+ root,
661
+ segments: []
662
+ };
663
+ }
664
+ function formatEncodePath(path) {
665
+ let text = path.root;
666
+ for (const segment of path.segments) text += typeof segment === "number" ? `[${segment}]` : `.${segment}`;
667
+ return text;
668
+ }
669
+ function encodeArray(values, path) {
670
+ const parts = [];
671
+ for (let index = 0; index < values.length; index += 1) {
672
+ path.segments.push(index);
673
+ parts.push(encodeValue(values[index], path));
674
+ path.segments.pop();
675
+ }
676
+ return `[${parts.join(",")}]`;
677
+ }
678
+ function encodeObject(value, path) {
679
+ const prototype = Object.getPrototypeOf(value);
680
+ if (prototype !== Object.prototype && prototype !== null) throw new Error(`Invalid data resource key value at ${formatEncodePath(path)}.`);
681
+ const record = value;
682
+ const keys = Object.keys(record).sort();
683
+ const parts = [];
684
+ for (const key of keys) {
685
+ const child = record[key];
686
+ if (child === void 0) throw new Error(`Invalid undefined data resource key value at ${formatEncodePath(path)}.`);
687
+ path.segments.push(key);
688
+ parts.push(`${JSON.stringify(key)}:${encodeValue(child, path)}`);
689
+ path.segments.pop();
690
+ }
691
+ return `{${parts.join(",")}}`;
692
+ }
693
+ function encodeValue(value, path) {
694
+ if (value === null) return "null";
695
+ switch (typeof value) {
696
+ case "string": return JSON.stringify(value);
697
+ case "boolean": return value ? "true" : "false";
698
+ case "number":
699
+ if (!Number.isFinite(value)) throw new Error(`Invalid number in data resource key at ${formatEncodePath(path)}.`);
700
+ return Object.is(value, -0) ? "0" : JSON.stringify(value);
701
+ case "object":
702
+ if (Array.isArray(value)) return encodeArray(value, path);
703
+ return encodeObject(value, path);
704
+ default: throw new Error(`Invalid data resource key value at ${formatEncodePath(path)}.`);
705
+ }
706
+ }
707
+ function isThenable(value) {
708
+ return (typeof value === "object" || typeof value === "function") && value !== null && "then" in value && typeof value.then === "function";
709
+ }
710
+ function createPendingResult() {
711
+ let resolve = () => void 0;
712
+ return {
713
+ promise: new Promise((settle) => {
714
+ resolve = settle;
715
+ }),
716
+ resolve
717
+ };
718
+ }
719
+ function scheduleStoreTimer(callback, delay) {
720
+ const timer = setTimeout(callback, delay);
721
+ const unref = timer.unref;
722
+ if (unref !== void 0) unref.call(timer);
723
+ return timer;
724
+ }
725
+ function runWithDataStore(store, callback) {
726
+ return store.run(callback);
727
+ }
728
+ function currentDataStore() {
729
+ return resolveCurrentDataStore();
730
+ }
731
+ //#endregion
732
+ //#region src/transition.ts
733
+ let transitionHandler = (callback) => callback();
734
+ /**
735
+ * Runs state updates scheduled by `callback` at transition priority. If
736
+ * `callback` returns a thenable, updates after an `await` remain in the
737
+ * transition priority scope until it settles.
738
+ */
739
+ function transition(callback, options) {
740
+ return transitionHandler(callback, options);
741
+ }
742
+ function setTransitionHandler(handler) {
743
+ const previous = transitionHandler;
744
+ transitionHandler = handler;
745
+ return previous;
746
+ }
747
+ //#endregion
748
+ export { runWithDataStore as _, createRendererDataStore as a, isContext as b, ensureData as c, invalidateDataKey as d, invalidateDataPrefix as f, refreshData as g, readDataStore as h, createDataStore as i, invalidateData as l, normalizeDataResourceKey as m, transition as n, currentDataStore as o, isDataStoreController as p, attachDataStore as r, dataResource as s, setTransitionHandler as t, invalidateDataError as u, FigContextSymbol as v, createContext as y };
749
+
750
+ //# sourceMappingURL=transition-B4XiOWAj.js.map