@kontsedal/olas-core 0.0.1-rc.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +64 -0
  3. package/dist/index.cjs +363 -0
  4. package/dist/index.cjs.map +1 -0
  5. package/dist/index.d.cts +178 -0
  6. package/dist/index.d.cts.map +1 -0
  7. package/dist/index.d.mts +178 -0
  8. package/dist/index.d.mts.map +1 -0
  9. package/dist/index.mjs +339 -0
  10. package/dist/index.mjs.map +1 -0
  11. package/dist/root-BImHnGj1.mjs +3270 -0
  12. package/dist/root-BImHnGj1.mjs.map +1 -0
  13. package/dist/root-Bazp5_Ik.cjs +3347 -0
  14. package/dist/root-Bazp5_Ik.cjs.map +1 -0
  15. package/dist/testing.cjs +81 -0
  16. package/dist/testing.cjs.map +1 -0
  17. package/dist/testing.d.cts +56 -0
  18. package/dist/testing.d.cts.map +1 -0
  19. package/dist/testing.d.mts +56 -0
  20. package/dist/testing.d.mts.map +1 -0
  21. package/dist/testing.mjs +78 -0
  22. package/dist/testing.mjs.map +1 -0
  23. package/dist/types-CAMgqCMz.d.mts +816 -0
  24. package/dist/types-CAMgqCMz.d.mts.map +1 -0
  25. package/dist/types-emq_lZd7.d.cts +816 -0
  26. package/dist/types-emq_lZd7.d.cts.map +1 -0
  27. package/package.json +47 -0
  28. package/src/__dev__.d.ts +8 -0
  29. package/src/controller/define.ts +50 -0
  30. package/src/controller/index.ts +12 -0
  31. package/src/controller/instance.ts +499 -0
  32. package/src/controller/root.ts +160 -0
  33. package/src/controller/types.ts +195 -0
  34. package/src/devtools.ts +0 -0
  35. package/src/emitter.ts +79 -0
  36. package/src/errors.ts +49 -0
  37. package/src/forms/field.ts +303 -0
  38. package/src/forms/form-types.ts +130 -0
  39. package/src/forms/form.ts +640 -0
  40. package/src/forms/index.ts +2 -0
  41. package/src/forms/types.ts +1 -0
  42. package/src/forms/validators.ts +70 -0
  43. package/src/index.ts +89 -0
  44. package/src/query/client.ts +934 -0
  45. package/src/query/define.ts +154 -0
  46. package/src/query/entry.ts +322 -0
  47. package/src/query/focus-online.ts +73 -0
  48. package/src/query/index.ts +3 -0
  49. package/src/query/infinite.ts +462 -0
  50. package/src/query/keys.ts +33 -0
  51. package/src/query/local.ts +113 -0
  52. package/src/query/mutation.ts +384 -0
  53. package/src/query/plugin.ts +135 -0
  54. package/src/query/types.ts +168 -0
  55. package/src/query/use.ts +321 -0
  56. package/src/scope.ts +42 -0
  57. package/src/selection.ts +146 -0
  58. package/src/signals/index.ts +3 -0
  59. package/src/signals/readonly.ts +22 -0
  60. package/src/signals/runtime.ts +115 -0
  61. package/src/signals/types.ts +31 -0
  62. package/src/testing.ts +142 -0
  63. package/src/timing/debounced.ts +32 -0
  64. package/src/timing/index.ts +2 -0
  65. package/src/timing/throttled.ts +46 -0
  66. package/src/utils.ts +13 -0
@@ -0,0 +1,3347 @@
1
+ let _preact_signals_core = require("@preact/signals-core");
2
+ //#region src/controller/define.ts
3
+ /**
4
+ * Create a controller definition. The factory is stored on the returned object
5
+ * and invoked during `createRoot` / `ctx.child` to build instances.
6
+ *
7
+ * `Props` defaults to `void` so a factory written as `(ctx) => ...` is typed
8
+ * as `ControllerDef<void, Api>` — the form `createRoot` requires.
9
+ */
10
+ function defineController(factory, options) {
11
+ return {
12
+ __olas: "controller",
13
+ __factory: factory,
14
+ ...options?.name !== void 0 ? { __name: options.name } : {}
15
+ };
16
+ }
17
+ /** Internal — extracts the factory from a ControllerDef. */
18
+ function getFactory(def) {
19
+ return def.__factory;
20
+ }
21
+ /** Internal — extracts the explicit `name` option from a ControllerDef, if any. */
22
+ function getName(def) {
23
+ return def.__name;
24
+ }
25
+ //#endregion
26
+ //#region src/devtools.ts
27
+ function pathKey(path) {
28
+ return path.join("\0");
29
+ }
30
+ /**
31
+ * Per-root devtools event multiplexer. Emit is a no-op when no one is
32
+ * subscribed (one Set size check), so leaving the bus in production has
33
+ * effectively zero cost until a consumer attaches.
34
+ *
35
+ * Internal — exposed to consumers via `root.__debug`.
36
+ *
37
+ * Tracks a snapshot of every live controller (constructed but not yet
38
+ * disposed) and replays construction events to new subscribers, so the
39
+ * devtools tree populates on mount instead of staying blank until the
40
+ * next event.
41
+ */
42
+ var DevtoolsEmitter = class {
43
+ handlers = /* @__PURE__ */ new Set();
44
+ /** Path → entry for every live (constructed, not disposed) controller. */
45
+ liveControllers = /* @__PURE__ */ new Map();
46
+ subscribe(handler) {
47
+ for (const entry of this.liveControllers.values()) try {
48
+ handler({
49
+ type: "controller:constructed",
50
+ path: entry.path,
51
+ props: entry.props
52
+ });
53
+ if (entry.state === "suspended") handler({
54
+ type: "controller:suspended",
55
+ path: entry.path
56
+ });
57
+ } catch {}
58
+ this.handlers.add(handler);
59
+ return () => {
60
+ this.handlers.delete(handler);
61
+ };
62
+ }
63
+ emit(event) {
64
+ this.recordLifecycle(event);
65
+ if (this.handlers.size === 0) return;
66
+ const snapshot = Array.from(this.handlers);
67
+ for (const handler of snapshot) try {
68
+ handler(event);
69
+ } catch {}
70
+ }
71
+ get hasSubscribers() {
72
+ return this.handlers.size > 0;
73
+ }
74
+ recordLifecycle(event) {
75
+ if (event.type === "controller:constructed") {
76
+ this.liveControllers.set(pathKey(event.path), {
77
+ path: event.path,
78
+ props: event.props,
79
+ state: "active"
80
+ });
81
+ return;
82
+ }
83
+ if (event.type === "controller:suspended") {
84
+ const key = pathKey(event.path);
85
+ const cur = this.liveControllers.get(key);
86
+ if (cur !== void 0) cur.state = "suspended";
87
+ return;
88
+ }
89
+ if (event.type === "controller:resumed") {
90
+ const key = pathKey(event.path);
91
+ const cur = this.liveControllers.get(key);
92
+ if (cur !== void 0) cur.state = "active";
93
+ return;
94
+ }
95
+ if (event.type === "controller:disposed") {
96
+ this.liveControllers.delete(pathKey(event.path));
97
+ return;
98
+ }
99
+ }
100
+ };
101
+ //#endregion
102
+ //#region src/errors.ts
103
+ const defaultHandler = (err, context) => {
104
+ console.error("[olas]", context, err);
105
+ };
106
+ /**
107
+ * Dispatch an error to a user-provided handler, falling back to console.error.
108
+ * The handler itself is wrapped — if it throws, the throw is swallowed and
109
+ * logged so an `onError` bug never tears down the tree.
110
+ *
111
+ * Internal — used by the controller container and query client.
112
+ */
113
+ function dispatchError(handler, err, context) {
114
+ const fn = handler ?? defaultHandler;
115
+ try {
116
+ fn(err, context);
117
+ } catch (handlerErr) {
118
+ try {
119
+ console.error("[olas] onError handler threw:", handlerErr);
120
+ console.error("[olas] original error:", err, context);
121
+ } catch {}
122
+ }
123
+ }
124
+ //#endregion
125
+ //#region src/signals/runtime.ts
126
+ var SignalImpl = class {
127
+ inner;
128
+ constructor(initial) {
129
+ this.inner = (0, _preact_signals_core.signal)(initial);
130
+ }
131
+ get value() {
132
+ return this.inner.value;
133
+ }
134
+ set value(next) {
135
+ this.inner.value = next;
136
+ }
137
+ peek() {
138
+ return this.inner.peek();
139
+ }
140
+ subscribe(handler) {
141
+ return this.inner.subscribe(handler);
142
+ }
143
+ set(value) {
144
+ this.inner.value = value;
145
+ }
146
+ update(fn) {
147
+ this.inner.value = fn(this.inner.peek());
148
+ }
149
+ };
150
+ var ComputedImpl = class {
151
+ inner;
152
+ constructor(fn) {
153
+ this.inner = (0, _preact_signals_core.computed)(fn);
154
+ }
155
+ get value() {
156
+ return this.inner.value;
157
+ }
158
+ peek() {
159
+ return this.inner.peek();
160
+ }
161
+ subscribe(handler) {
162
+ return this.inner.subscribe(handler);
163
+ }
164
+ };
165
+ /**
166
+ * Create a writable `Signal<T>`. Reads track the current auto-tracking scope
167
+ * (effect / computed); writes notify all subscribers (deduped via `Object.is`).
168
+ *
169
+ * Spec §20.1. For a single-pass non-tracked read use `signal.peek()`.
170
+ */
171
+ function signal(initial) {
172
+ return new SignalImpl(initial);
173
+ }
174
+ /**
175
+ * Create a `Computed<T>` — a read-only derived signal. The provided `fn` is
176
+ * re-evaluated whenever a signal it read during its last run changes; the
177
+ * resulting value is cached until then.
178
+ *
179
+ * Spec §20.1. The graph is glitch-free: a `computed` re-runs at most once per
180
+ * batched-write cycle.
181
+ */
182
+ function computed(fn) {
183
+ return new ComputedImpl(fn);
184
+ }
185
+ /**
186
+ * Run `fn` immediately and again whenever any signal it reads changes. If
187
+ * `fn` returns a function, that function is called as a cleanup before the
188
+ * next re-run and on dispose.
189
+ *
190
+ * Returns a `dispose` function. Inside a controller use `ctx.effect(...)`
191
+ * instead — that variant is auto-disposed with the controller.
192
+ */
193
+ function effect(fn) {
194
+ return (0, _preact_signals_core.effect)(fn);
195
+ }
196
+ /**
197
+ * Batch synchronous signal writes so subscribers see one notification at the
198
+ * end of the batch rather than one per write. Returns whatever `fn` returns.
199
+ */
200
+ function batch(fn) {
201
+ return (0, _preact_signals_core.batch)(fn);
202
+ }
203
+ /**
204
+ * Run `fn` with auto-tracking suppressed — signals read inside don't become
205
+ * dependencies of the surrounding `computed` / `effect`. Useful for "read
206
+ * these signals once to log them" or for snapshotting state inside an effect
207
+ * without subscribing to it. For a single-signal peek, prefer `signal.peek()`.
208
+ */
209
+ function untracked(fn) {
210
+ return (0, _preact_signals_core.untracked)(fn);
211
+ }
212
+ //#endregion
213
+ //#region src/utils.ts
214
+ /**
215
+ * True iff `err` is an AbortError. Used to filter superseded latest-wins
216
+ * mutations and aborted fetches from genuine failures.
217
+ *
218
+ * Spec: §20.12 — checks `err instanceof DOMException && err.name === 'AbortError'`.
219
+ * Node 17+ exposes a global DOMException, so this works server-side too.
220
+ */
221
+ function isAbortError(err) {
222
+ if (typeof DOMException !== "undefined" && err instanceof DOMException) return err.name === "AbortError";
223
+ return false;
224
+ }
225
+ //#endregion
226
+ //#region src/query/entry.ts
227
+ /**
228
+ * One cache entry's state machine. Owns the AsyncState signals, race
229
+ * protection, retry loop, optimistic-update snapshot stack.
230
+ *
231
+ * Internal — not exported from the public surface.
232
+ */
233
+ var Entry = class {
234
+ data;
235
+ error = signal(void 0);
236
+ status;
237
+ isLoading = signal(false);
238
+ isFetching = signal(false);
239
+ lastUpdatedAt;
240
+ hasPendingMutations = signal(false);
241
+ isStale = signal(true);
242
+ fetcherProvider;
243
+ staleTime;
244
+ retry;
245
+ retryDelay;
246
+ currentFetchId = 0;
247
+ currentAbort = null;
248
+ staleTimer = null;
249
+ snapshots = [];
250
+ nextSnapshotId = 0;
251
+ disposed = false;
252
+ events;
253
+ fetchStartTime = 0;
254
+ constructor(options) {
255
+ this.fetcherProvider = options.fetcher;
256
+ this.staleTime = options.staleTime ?? 0;
257
+ this.retry = options.retry ?? 0;
258
+ this.retryDelay = options.retryDelay ?? 1e3;
259
+ this.events = options.events ?? {};
260
+ this.data = signal(options.initialData);
261
+ if (options.initialData !== void 0) {
262
+ this.status = signal("success");
263
+ this.scheduleStaleness();
264
+ this.isStale.set(this.staleTime === 0);
265
+ } else this.status = signal("idle");
266
+ this.lastUpdatedAt = signal(options.initialUpdatedAt);
267
+ }
268
+ startFetch() {
269
+ if (this.disposed) return Promise.reject(/* @__PURE__ */ new Error("Entry disposed"));
270
+ const myId = ++this.currentFetchId;
271
+ this.currentAbort?.abort();
272
+ const abort = new AbortController();
273
+ this.currentAbort = abort;
274
+ const previouslyHadData = this.data.peek() !== void 0;
275
+ batch(() => {
276
+ this.status.set("pending");
277
+ this.isFetching.set(true);
278
+ this.isLoading.set(!previouslyHadData);
279
+ });
280
+ this.fetchStartTime = Date.now();
281
+ try {
282
+ this.events.onFetchStart?.();
283
+ } catch {}
284
+ return this.runWithRetry(myId, abort);
285
+ }
286
+ async runWithRetry(myId, abort) {
287
+ let attempt = 0;
288
+ while (true) {
289
+ if (myId !== this.currentFetchId || this.disposed) throw new DOMException("Superseded", "AbortError");
290
+ try {
291
+ const result = await this.fetcherProvider()(abort.signal);
292
+ if (myId !== this.currentFetchId || this.disposed) throw new DOMException("Superseded", "AbortError");
293
+ return this.applySuccess(result);
294
+ } catch (err) {
295
+ if (myId !== this.currentFetchId || this.disposed || isAbortError(err)) throw err;
296
+ if (!this.shouldRetry(attempt, err)) return this.applyFailure(err);
297
+ await abortableSleep$2(this.computeDelay(attempt), abort.signal);
298
+ attempt += 1;
299
+ }
300
+ }
301
+ }
302
+ shouldRetry(attempt, err) {
303
+ const retry = this.retry;
304
+ if (retry === 0) return false;
305
+ if (typeof retry === "number") return attempt < retry;
306
+ return retry(attempt, err);
307
+ }
308
+ computeDelay(attempt) {
309
+ const d = this.retryDelay;
310
+ return typeof d === "function" ? d(attempt) : d;
311
+ }
312
+ applySuccess(result) {
313
+ batch(() => {
314
+ this.data.set(result);
315
+ this.error.set(void 0);
316
+ this.status.set("success");
317
+ this.isLoading.set(false);
318
+ this.isFetching.set(false);
319
+ this.lastUpdatedAt.set(Date.now());
320
+ this.isStale.set(this.staleTime === 0);
321
+ });
322
+ if (this.staleTime > 0) this.scheduleStaleness();
323
+ try {
324
+ this.events.onFetchSuccess?.(Date.now() - this.fetchStartTime);
325
+ } catch {}
326
+ return result;
327
+ }
328
+ applyFailure(err) {
329
+ batch(() => {
330
+ this.error.set(err);
331
+ this.status.set("error");
332
+ this.isLoading.set(false);
333
+ this.isFetching.set(false);
334
+ });
335
+ try {
336
+ this.events.onFetchError?.(Date.now() - this.fetchStartTime, err);
337
+ } catch {}
338
+ throw err;
339
+ }
340
+ scheduleStaleness() {
341
+ if (this.staleTimer != null) clearTimeout(this.staleTimer);
342
+ if (this.staleTime > 0) this.staleTimer = setTimeout(() => {
343
+ this.staleTimer = null;
344
+ if (!this.disposed) this.isStale.set(true);
345
+ }, this.staleTime);
346
+ }
347
+ refetch() {
348
+ return this.startFetch();
349
+ }
350
+ invalidate() {
351
+ if (this.staleTimer != null) {
352
+ clearTimeout(this.staleTimer);
353
+ this.staleTimer = null;
354
+ }
355
+ this.isStale.set(true);
356
+ return this.startFetch();
357
+ }
358
+ reset() {
359
+ if (this.disposed) return;
360
+ batch(() => {
361
+ this.error.set(void 0);
362
+ this.status.set(this.data.peek() !== void 0 ? "success" : "idle");
363
+ });
364
+ }
365
+ setData(updater) {
366
+ if (this.disposed) return {
367
+ rollback: () => {},
368
+ finalize: () => {}
369
+ };
370
+ const prev = this.data.peek();
371
+ const next = updater(prev);
372
+ const id = this.nextSnapshotId++;
373
+ const record = {
374
+ id,
375
+ prev,
376
+ live: true
377
+ };
378
+ this.snapshots.push(record);
379
+ batch(() => {
380
+ this.data.set(next);
381
+ if (this.status.peek() === "idle" || this.status.peek() === "pending") this.status.set("success");
382
+ this.lastUpdatedAt.set(Date.now());
383
+ this.hasPendingMutations.set(true);
384
+ });
385
+ return {
386
+ rollback: () => {
387
+ if (!record.live || this.disposed) return;
388
+ record.live = false;
389
+ batch(() => {
390
+ this.data.set(record.prev);
391
+ this.snapshots = this.snapshots.filter((s) => s.id !== id);
392
+ const anyLive = this.snapshots.some((s) => s.live);
393
+ this.hasPendingMutations.set(anyLive);
394
+ });
395
+ },
396
+ finalize: () => {
397
+ if (!record.live || this.disposed) return;
398
+ record.live = false;
399
+ this.snapshots = this.snapshots.filter((s) => s.id !== id);
400
+ if (!this.snapshots.some((s) => s.live)) this.hasPendingMutations.set(false);
401
+ }
402
+ };
403
+ }
404
+ finalizeSnapshot(snapshot) {
405
+ const id = snapshotIds.get(snapshot);
406
+ if (id === void 0) return;
407
+ const record = this.snapshots.find((s) => s.live && s.id === id);
408
+ if (!record) return;
409
+ record.live = false;
410
+ this.snapshots = this.snapshots.filter((s) => s !== record);
411
+ if (!this.snapshots.some((s) => s.live)) this.hasPendingMutations.set(false);
412
+ }
413
+ firstValue() {
414
+ if (this.status.peek() === "success") return Promise.resolve(this.data.peek());
415
+ if (this.status.peek() === "error") return Promise.reject(this.error.peek());
416
+ return new Promise((resolve, reject) => {
417
+ const unsub = this.status.subscribe((s) => {
418
+ if (s === "success") {
419
+ unsub();
420
+ resolve(this.data.peek());
421
+ } else if (s === "error") {
422
+ unsub();
423
+ reject(this.error.peek());
424
+ }
425
+ });
426
+ });
427
+ }
428
+ /**
429
+ * True iff data is older than `staleTime` (or no data has been fetched yet).
430
+ * Used by the query client to decide whether to refetch on subscribe.
431
+ */
432
+ isStaleNow() {
433
+ const last = this.lastUpdatedAt.peek();
434
+ if (last === void 0) return true;
435
+ return Date.now() - last >= this.staleTime;
436
+ }
437
+ dispose() {
438
+ if (this.disposed) return;
439
+ this.disposed = true;
440
+ if (this.staleTimer != null) {
441
+ clearTimeout(this.staleTimer);
442
+ this.staleTimer = null;
443
+ }
444
+ this.currentAbort?.abort();
445
+ this.currentAbort = null;
446
+ }
447
+ };
448
+ const snapshotIds = /* @__PURE__ */ new WeakMap();
449
+ function abortableSleep$2(ms, signal) {
450
+ return new Promise((resolve, reject) => {
451
+ if (signal.aborted) {
452
+ reject(new DOMException("Aborted", "AbortError"));
453
+ return;
454
+ }
455
+ const timer = setTimeout(() => {
456
+ signal.removeEventListener("abort", onAbort);
457
+ resolve();
458
+ }, ms);
459
+ const onAbort = () => {
460
+ clearTimeout(timer);
461
+ signal.removeEventListener("abort", onAbort);
462
+ reject(new DOMException("Aborted", "AbortError"));
463
+ };
464
+ signal.addEventListener("abort", onAbort, { once: true });
465
+ });
466
+ }
467
+ //#endregion
468
+ //#region src/query/focus-online.ts
469
+ const focusSubs = /* @__PURE__ */ new Set();
470
+ const onlineSubs = /* @__PURE__ */ new Set();
471
+ let focusInstalled = false;
472
+ let onlineInstalled = false;
473
+ function fireFocus() {
474
+ for (const fn of focusSubs) try {
475
+ fn();
476
+ } catch {}
477
+ }
478
+ function fireOnline() {
479
+ for (const fn of onlineSubs) try {
480
+ fn();
481
+ } catch {}
482
+ }
483
+ function ensureFocusInstalled() {
484
+ if (focusInstalled) return;
485
+ if (typeof window === "undefined") return;
486
+ window.addEventListener("focus", fireFocus);
487
+ if (typeof document !== "undefined") document.addEventListener("visibilitychange", () => {
488
+ if (document.visibilityState === "visible") fireFocus();
489
+ });
490
+ focusInstalled = true;
491
+ }
492
+ function ensureOnlineInstalled() {
493
+ if (onlineInstalled) return;
494
+ if (typeof window === "undefined") return;
495
+ window.addEventListener("online", fireOnline);
496
+ onlineInstalled = true;
497
+ }
498
+ function subscribeWindowFocus(fn) {
499
+ ensureFocusInstalled();
500
+ focusSubs.add(fn);
501
+ return () => {
502
+ focusSubs.delete(fn);
503
+ };
504
+ }
505
+ function subscribeReconnect(fn) {
506
+ ensureOnlineInstalled();
507
+ onlineSubs.add(fn);
508
+ return () => {
509
+ onlineSubs.delete(fn);
510
+ };
511
+ }
512
+ //#endregion
513
+ //#region src/query/infinite.ts
514
+ /**
515
+ * Holds an array of pages plus their pageParams. Supports fetchNextPage /
516
+ * fetchPreviousPage / invalidate (drops all pages). Race-protected.
517
+ *
518
+ * Internal.
519
+ */
520
+ var InfiniteEntry = class {
521
+ pages = signal([]);
522
+ pageParams;
523
+ data;
524
+ error = signal(void 0);
525
+ status = signal("idle");
526
+ isLoading = signal(false);
527
+ isFetching = signal(false);
528
+ isStale = signal(true);
529
+ lastUpdatedAt = signal(void 0);
530
+ hasPendingMutations = signal(false);
531
+ isFetchingNextPage = signal(false);
532
+ isFetchingPreviousPage = signal(false);
533
+ hasNextPage;
534
+ hasPreviousPage;
535
+ flat;
536
+ currentFetchId = 0;
537
+ currentAbort = null;
538
+ staleTimer = null;
539
+ snapshots = [];
540
+ nextSnapshotId = 0;
541
+ disposed = false;
542
+ fetcher;
543
+ initialPageParam;
544
+ getNextPageParam;
545
+ getPreviousPageParam;
546
+ staleTime;
547
+ retry;
548
+ retryDelay;
549
+ itemsOf;
550
+ constructor(opts) {
551
+ this.fetcher = opts.fetcher;
552
+ this.initialPageParam = opts.initialPageParam;
553
+ this.getNextPageParam = opts.getNextPageParam;
554
+ this.getPreviousPageParam = opts.getPreviousPageParam;
555
+ this.itemsOf = opts.itemsOf;
556
+ this.staleTime = opts.staleTime ?? 0;
557
+ this.retry = opts.retry ?? 0;
558
+ this.retryDelay = opts.retryDelay ?? 1e3;
559
+ this.pageParams = signal([]);
560
+ this.data = computed(() => {
561
+ const ps = this.pages.value;
562
+ return ps.length === 0 ? void 0 : ps;
563
+ });
564
+ this.flat = computed(() => {
565
+ const ps = this.pages.value;
566
+ if (!this.itemsOf) return ps;
567
+ const out = [];
568
+ for (const p of ps) for (const item of this.itemsOf(p)) out.push(item);
569
+ return out;
570
+ });
571
+ this.hasNextPage = computed(() => {
572
+ const ps = this.pages.value;
573
+ if (ps.length === 0) return false;
574
+ return this.getNextPageParam(ps[ps.length - 1], ps) !== null;
575
+ });
576
+ this.hasPreviousPage = computed(() => {
577
+ const ps = this.pages.value;
578
+ if (ps.length === 0) return false;
579
+ const fn = this.getPreviousPageParam;
580
+ if (!fn) return false;
581
+ return fn(ps[0], ps) !== null;
582
+ });
583
+ }
584
+ /** Initial / refetch — drops all pages and fetches starting from initialPageParam. */
585
+ startFetch() {
586
+ if (this.disposed) return Promise.reject(/* @__PURE__ */ new Error("Entry disposed"));
587
+ const myId = ++this.currentFetchId;
588
+ this.currentAbort?.abort();
589
+ const abort = new AbortController();
590
+ this.currentAbort = abort;
591
+ const previouslyHadPages = this.pages.peek().length > 0;
592
+ batch(() => {
593
+ this.status.set("pending");
594
+ this.isFetching.set(true);
595
+ this.isLoading.set(!previouslyHadPages);
596
+ });
597
+ return this.runFetch(myId, abort.signal, this.initialPageParam, (page, param) => {
598
+ if (myId !== this.currentFetchId || this.disposed) return;
599
+ batch(() => {
600
+ this.pages.set([page]);
601
+ this.pageParams.set([param]);
602
+ this.error.set(void 0);
603
+ this.status.set("success");
604
+ this.isLoading.set(false);
605
+ this.isFetching.set(false);
606
+ this.lastUpdatedAt.set(Date.now());
607
+ this.isStale.set(this.staleTime === 0);
608
+ });
609
+ if (this.staleTime > 0) this.scheduleStaleness();
610
+ }, "initial");
611
+ }
612
+ fetchNextPage() {
613
+ if (this.disposed) return Promise.reject(/* @__PURE__ */ new Error("Entry disposed"));
614
+ if (this.isFetchingNextPage.peek()) return Promise.resolve();
615
+ const ps = this.pages.peek();
616
+ if (ps.length === 0) return this.startFetch().then(() => {});
617
+ const nextParam = this.getNextPageParam(ps[ps.length - 1], ps);
618
+ if (nextParam === null) return Promise.resolve();
619
+ const myId = ++this.currentFetchId;
620
+ const abort = new AbortController();
621
+ this.currentAbort?.abort();
622
+ this.currentAbort = abort;
623
+ batch(() => {
624
+ this.isFetchingNextPage.set(true);
625
+ this.isFetching.set(true);
626
+ });
627
+ return this.runFetch(myId, abort.signal, nextParam, (page, param) => {
628
+ if (myId !== this.currentFetchId || this.disposed) return;
629
+ batch(() => {
630
+ this.pages.set([...this.pages.peek(), page]);
631
+ this.pageParams.set([...this.pageParams.peek(), param]);
632
+ this.isFetchingNextPage.set(false);
633
+ this.isFetching.set(false);
634
+ this.lastUpdatedAt.set(Date.now());
635
+ });
636
+ }, "next").then(() => {});
637
+ }
638
+ fetchPreviousPage() {
639
+ if (this.disposed) return Promise.reject(/* @__PURE__ */ new Error("Entry disposed"));
640
+ if (this.isFetchingPreviousPage.peek()) return Promise.resolve();
641
+ if (!this.getPreviousPageParam) return Promise.resolve();
642
+ const ps = this.pages.peek();
643
+ if (ps.length === 0) return this.startFetch().then(() => {});
644
+ const prevParam = this.getPreviousPageParam(ps[0], ps);
645
+ if (prevParam === null) return Promise.resolve();
646
+ const myId = ++this.currentFetchId;
647
+ const abort = new AbortController();
648
+ this.currentAbort?.abort();
649
+ this.currentAbort = abort;
650
+ batch(() => {
651
+ this.isFetchingPreviousPage.set(true);
652
+ this.isFetching.set(true);
653
+ });
654
+ return this.runFetch(myId, abort.signal, prevParam, (page, param) => {
655
+ if (myId !== this.currentFetchId || this.disposed) return;
656
+ batch(() => {
657
+ this.pages.set([page, ...this.pages.peek()]);
658
+ this.pageParams.set([param, ...this.pageParams.peek()]);
659
+ this.isFetchingPreviousPage.set(false);
660
+ this.isFetching.set(false);
661
+ this.lastUpdatedAt.set(Date.now());
662
+ });
663
+ }, "prev").then(() => {});
664
+ }
665
+ async runFetch(myId, signal, pageParam, onSuccess, direction) {
666
+ let attempt = 0;
667
+ while (true) {
668
+ if (myId !== this.currentFetchId || this.disposed) throw new DOMException("Superseded", "AbortError");
669
+ try {
670
+ const page = await this.fetcher({
671
+ pageParam,
672
+ signal
673
+ });
674
+ if (myId !== this.currentFetchId || this.disposed) throw new DOMException("Superseded", "AbortError");
675
+ onSuccess(page, pageParam);
676
+ return page;
677
+ } catch (err) {
678
+ if (myId !== this.currentFetchId || this.disposed || isAbortError(err)) throw err;
679
+ if (!(typeof this.retry === "number" ? attempt < this.retry : this.retry(attempt, err))) {
680
+ batch(() => {
681
+ this.error.set(err);
682
+ this.status.set("error");
683
+ this.isLoading.set(false);
684
+ this.isFetching.set(false);
685
+ if (direction === "next") this.isFetchingNextPage.set(false);
686
+ if (direction === "prev") this.isFetchingPreviousPage.set(false);
687
+ });
688
+ throw err;
689
+ }
690
+ await abortableSleep$1(typeof this.retryDelay === "function" ? this.retryDelay(attempt) : this.retryDelay, signal);
691
+ attempt += 1;
692
+ }
693
+ }
694
+ }
695
+ refetch() {
696
+ return this.startFetch();
697
+ }
698
+ invalidate() {
699
+ if (this.staleTimer != null) {
700
+ clearTimeout(this.staleTimer);
701
+ this.staleTimer = null;
702
+ }
703
+ this.isStale.set(true);
704
+ return this.startFetch();
705
+ }
706
+ reset() {
707
+ if (this.disposed) return;
708
+ batch(() => {
709
+ this.error.set(void 0);
710
+ this.status.set(this.pages.peek().length > 0 ? "success" : "idle");
711
+ });
712
+ }
713
+ setData(updater) {
714
+ if (this.disposed) return {
715
+ rollback: () => {},
716
+ finalize: () => {}
717
+ };
718
+ const prev = this.pages.peek();
719
+ const next = updater(prev.length === 0 ? void 0 : prev);
720
+ const id = this.nextSnapshotId++;
721
+ const record = {
722
+ id,
723
+ prev,
724
+ live: true
725
+ };
726
+ this.snapshots.push(record);
727
+ batch(() => {
728
+ this.pages.set(next);
729
+ if (this.status.peek() === "idle" || this.status.peek() === "pending") this.status.set("success");
730
+ this.lastUpdatedAt.set(Date.now());
731
+ this.hasPendingMutations.set(true);
732
+ });
733
+ return {
734
+ rollback: () => {
735
+ if (!record.live || this.disposed) return;
736
+ record.live = false;
737
+ batch(() => {
738
+ this.pages.set(record.prev);
739
+ this.snapshots = this.snapshots.filter((s) => s.id !== id);
740
+ this.hasPendingMutations.set(this.snapshots.some((s) => s.live));
741
+ });
742
+ },
743
+ finalize: () => {
744
+ if (!record.live || this.disposed) return;
745
+ record.live = false;
746
+ this.snapshots = this.snapshots.filter((s) => s.id !== id);
747
+ if (!this.snapshots.some((s) => s.live)) this.hasPendingMutations.set(false);
748
+ }
749
+ };
750
+ }
751
+ firstValue() {
752
+ if (this.status.peek() === "success") return Promise.resolve(this.pages.peek());
753
+ if (this.status.peek() === "error") return Promise.reject(this.error.peek());
754
+ return new Promise((resolve, reject) => {
755
+ const unsub = this.status.subscribe((s) => {
756
+ if (s === "success") {
757
+ unsub();
758
+ resolve(this.pages.peek());
759
+ } else if (s === "error") {
760
+ unsub();
761
+ reject(this.error.peek());
762
+ }
763
+ });
764
+ });
765
+ }
766
+ isStaleNow() {
767
+ const last = this.lastUpdatedAt.peek();
768
+ if (last === void 0) return true;
769
+ return Date.now() - last >= this.staleTime;
770
+ }
771
+ scheduleStaleness() {
772
+ if (this.staleTimer != null) clearTimeout(this.staleTimer);
773
+ if (this.staleTime > 0) this.staleTimer = setTimeout(() => {
774
+ this.staleTimer = null;
775
+ if (!this.disposed) this.isStale.set(true);
776
+ }, this.staleTime);
777
+ }
778
+ dispose() {
779
+ if (this.disposed) return;
780
+ this.disposed = true;
781
+ if (this.staleTimer != null) {
782
+ clearTimeout(this.staleTimer);
783
+ this.staleTimer = null;
784
+ }
785
+ this.currentAbort?.abort();
786
+ this.currentAbort = null;
787
+ }
788
+ };
789
+ function abortableSleep$1(ms, signal) {
790
+ return new Promise((resolve, reject) => {
791
+ if (signal.aborted) {
792
+ reject(new DOMException("Aborted", "AbortError"));
793
+ return;
794
+ }
795
+ const timer = setTimeout(() => {
796
+ signal.removeEventListener("abort", onAbort);
797
+ resolve();
798
+ }, ms);
799
+ const onAbort = () => {
800
+ clearTimeout(timer);
801
+ signal.removeEventListener("abort", onAbort);
802
+ reject(new DOMException("Aborted", "AbortError"));
803
+ };
804
+ signal.addEventListener("abort", onAbort, { once: true });
805
+ });
806
+ }
807
+ //#endregion
808
+ //#region src/query/keys.ts
809
+ /**
810
+ * Stable string hash of a key tuple. Two equal-by-content args produce the
811
+ * same string regardless of property iteration order. Handles primitives,
812
+ * arrays, plain objects, Date.
813
+ *
814
+ * Functions and symbols throw — keys must be serializable so distinct
815
+ * subscribers can share entries.
816
+ */
817
+ function stableHash(args) {
818
+ return JSON.stringify(args, replacer);
819
+ }
820
+ const replacer = (_key, value) => {
821
+ if (typeof value === "function") throw new Error("[olas] query keys cannot contain functions");
822
+ if (typeof value === "symbol") throw new Error("[olas] query keys cannot contain symbols");
823
+ if (value === void 0) return "__undefined__";
824
+ if (value instanceof Date) return { __date: value.toISOString() };
825
+ if (value instanceof Map || value instanceof Set) throw new Error("[olas] query keys cannot contain Map/Set — use arrays/objects");
826
+ if (value && typeof value === "object" && !Array.isArray(value)) {
827
+ const sorted = {};
828
+ for (const k of Object.keys(value).sort()) sorted[k] = value[k];
829
+ return sorted;
830
+ }
831
+ return value;
832
+ };
833
+ //#endregion
834
+ //#region src/query/plugin.ts
835
+ const queryRegistry = /* @__PURE__ */ new Map();
836
+ /**
837
+ * Register a query by its `queryId`. Internal — called from `defineQuery` /
838
+ * `defineInfiniteQuery`. Replaces any previous registration with the same
839
+ * id (matches Olas's "full root rebuild" HMR story; a mid-flight remote
840
+ * message routed against the old `Query` simply misses).
841
+ */
842
+ function registerQueryById(queryId, query) {
843
+ queryRegistry.set(queryId, query);
844
+ }
845
+ /**
846
+ * Look up a query by its declared `queryId`. Returns `undefined` when no
847
+ * query with that id has been defined yet (e.g. the module isn't imported
848
+ * in the receiving tab).
849
+ */
850
+ function lookupRegisteredQuery(queryId) {
851
+ return queryRegistry.get(queryId);
852
+ }
853
+ //#endregion
854
+ //#region src/query/client.ts
855
+ const DEFAULT_GC_TIME = 5 * 6e4;
856
+ var ClientEntry = class {
857
+ entry;
858
+ /** The result of `spec.key(...args)` — used for hashing/identity. */
859
+ keyArgs;
860
+ /** The original args the consumer passed — what the fetcher receives. */
861
+ callArgs;
862
+ client;
863
+ query;
864
+ subscriberCount = 0;
865
+ gcTimer = null;
866
+ intervalTimer = null;
867
+ unsubFocus = null;
868
+ unsubOnline = null;
869
+ gcTime;
870
+ refetchInterval;
871
+ refetchOnWindowFocus;
872
+ refetchOnReconnect;
873
+ constructor(client, query, callArgs, keyArgs, spec, hydrated) {
874
+ this.client = client;
875
+ this.query = query;
876
+ this.callArgs = callArgs;
877
+ this.keyArgs = keyArgs;
878
+ this.gcTime = spec.gcTime ?? DEFAULT_GC_TIME;
879
+ this.refetchInterval = spec.refetchInterval;
880
+ this.refetchOnWindowFocus = spec.refetchOnWindowFocus ?? client.refetchOnWindowFocus;
881
+ this.refetchOnReconnect = spec.refetchOnReconnect ?? client.refetchOnReconnect;
882
+ const fetcherFn = spec.fetcher;
883
+ const deps = client.deps;
884
+ client.devtools;
885
+ this.keyArgs;
886
+ this.entry = new Entry({
887
+ fetcher: () => (signal) => fetcherFn({
888
+ signal,
889
+ deps
890
+ }, ...callArgs),
891
+ staleTime: spec.staleTime,
892
+ retry: spec.retry,
893
+ retryDelay: spec.retryDelay,
894
+ initialData: hydrated?.data,
895
+ initialUpdatedAt: hydrated?.lastUpdatedAt,
896
+ events: void 0
897
+ });
898
+ }
899
+ acquire() {
900
+ this.subscriberCount += 1;
901
+ if (this.gcTimer != null) {
902
+ clearTimeout(this.gcTimer);
903
+ this.gcTimer = null;
904
+ }
905
+ if (this.subscriberCount === 1) {
906
+ if (this.refetchInterval != null) this.startIntervalTimer();
907
+ if (this.refetchOnWindowFocus) this.unsubFocus = subscribeWindowFocus(() => this.triggerEventRefetch());
908
+ if (this.refetchOnReconnect) this.unsubOnline = subscribeReconnect(() => this.triggerEventRefetch());
909
+ }
910
+ }
911
+ release() {
912
+ this.subscriberCount -= 1;
913
+ if (this.subscriberCount <= 0) {
914
+ this.stopIntervalTimer();
915
+ this.stopEventSubscriptions();
916
+ if (this.gcTime === 0) this.client.dropEntry(this);
917
+ else this.gcTimer = setTimeout(() => {
918
+ this.gcTimer = null;
919
+ this.client.dropEntry(this);
920
+ }, this.gcTime);
921
+ }
922
+ }
923
+ hasSubscribers() {
924
+ return this.subscriberCount > 0;
925
+ }
926
+ startIntervalTimer() {
927
+ if (this.refetchInterval == null) return;
928
+ if (this.intervalTimer != null) return;
929
+ this.intervalTimer = setInterval(() => {
930
+ this.entry.startFetch().catch(() => {});
931
+ }, this.refetchInterval);
932
+ }
933
+ stopIntervalTimer() {
934
+ if (this.intervalTimer != null) {
935
+ clearInterval(this.intervalTimer);
936
+ this.intervalTimer = null;
937
+ }
938
+ }
939
+ stopEventSubscriptions() {
940
+ if (this.unsubFocus != null) {
941
+ this.unsubFocus();
942
+ this.unsubFocus = null;
943
+ }
944
+ if (this.unsubOnline != null) {
945
+ this.unsubOnline();
946
+ this.unsubOnline = null;
947
+ }
948
+ }
949
+ /**
950
+ * Schedule a gc timer for an entry that was just created via a non-subscribing
951
+ * path (`prefetch`, `setData`, `invalidate`). Without this, those entries
952
+ * never trigger `release()` and would live until root dispose. Called by
953
+ * `QueryClient.bindEntry` right after creating a fresh entry; `acquire()`
954
+ * (e.g., from a subscriber that arrives shortly after a prefetch) clears it.
955
+ * No-op if the entry already has subscribers or a gc timer pending.
956
+ */
957
+ scheduleGcIfOrphan() {
958
+ if (this.subscriberCount > 0 || this.gcTimer != null) return;
959
+ if (this.gcTime === 0) {
960
+ queueMicrotask(() => {
961
+ if (this.subscriberCount === 0 && this.gcTimer == null) this.client.dropEntry(this);
962
+ });
963
+ return;
964
+ }
965
+ this.gcTimer = setTimeout(() => {
966
+ this.gcTimer = null;
967
+ this.client.dropEntry(this);
968
+ }, this.gcTime);
969
+ }
970
+ /** Refetch on focus / reconnect, but only if the data is actually stale. */
971
+ triggerEventRefetch() {
972
+ if (!this.entry.isStaleNow()) return;
973
+ this.entry.startFetch().catch(() => {});
974
+ }
975
+ dispose() {
976
+ if (this.gcTimer != null) {
977
+ clearTimeout(this.gcTimer);
978
+ this.gcTimer = null;
979
+ }
980
+ this.stopIntervalTimer();
981
+ this.stopEventSubscriptions();
982
+ this.entry.dispose();
983
+ }
984
+ };
985
+ var InfiniteClientEntry = class {
986
+ entry;
987
+ keyArgs;
988
+ callArgs;
989
+ client;
990
+ query;
991
+ subscriberCount = 0;
992
+ gcTimer = null;
993
+ intervalTimer = null;
994
+ gcTime;
995
+ refetchInterval;
996
+ constructor(client, query, callArgs, keyArgs, spec) {
997
+ this.client = client;
998
+ this.query = query;
999
+ this.callArgs = callArgs;
1000
+ this.keyArgs = keyArgs;
1001
+ this.gcTime = spec.gcTime ?? DEFAULT_GC_TIME;
1002
+ this.refetchInterval = spec.refetchInterval;
1003
+ const fetcherFn = spec.fetcher;
1004
+ const deps = client.deps;
1005
+ this.entry = new InfiniteEntry({
1006
+ fetcher: ({ pageParam, signal }) => fetcherFn({
1007
+ pageParam,
1008
+ signal,
1009
+ deps
1010
+ }, ...callArgs),
1011
+ initialPageParam: spec.initialPageParam,
1012
+ getNextPageParam: spec.getNextPageParam,
1013
+ getPreviousPageParam: spec.getPreviousPageParam,
1014
+ itemsOf: spec.itemsOf,
1015
+ staleTime: spec.staleTime,
1016
+ retry: spec.retry,
1017
+ retryDelay: spec.retryDelay
1018
+ });
1019
+ }
1020
+ acquire() {
1021
+ this.subscriberCount += 1;
1022
+ if (this.gcTimer != null) {
1023
+ clearTimeout(this.gcTimer);
1024
+ this.gcTimer = null;
1025
+ }
1026
+ if (this.subscriberCount === 1 && this.refetchInterval != null) this.startIntervalTimer();
1027
+ }
1028
+ release() {
1029
+ this.subscriberCount -= 1;
1030
+ if (this.subscriberCount <= 0) {
1031
+ this.stopIntervalTimer();
1032
+ if (this.gcTime === 0) this.client.dropInfiniteEntry(this);
1033
+ else this.gcTimer = setTimeout(() => {
1034
+ this.gcTimer = null;
1035
+ this.client.dropInfiniteEntry(this);
1036
+ }, this.gcTime);
1037
+ }
1038
+ }
1039
+ startIntervalTimer() {
1040
+ if (this.refetchInterval == null || this.intervalTimer != null) return;
1041
+ this.intervalTimer = setInterval(() => {
1042
+ this.entry.startFetch().catch(() => {});
1043
+ }, this.refetchInterval);
1044
+ }
1045
+ stopIntervalTimer() {
1046
+ if (this.intervalTimer != null) {
1047
+ clearInterval(this.intervalTimer);
1048
+ this.intervalTimer = null;
1049
+ }
1050
+ }
1051
+ /** See `ClientEntry.scheduleGcIfOrphan`. */
1052
+ scheduleGcIfOrphan() {
1053
+ if (this.subscriberCount > 0 || this.gcTimer != null) return;
1054
+ if (this.gcTime === 0) {
1055
+ queueMicrotask(() => {
1056
+ if (this.subscriberCount === 0 && this.gcTimer == null) this.client.dropInfiniteEntry(this);
1057
+ });
1058
+ return;
1059
+ }
1060
+ this.gcTimer = setTimeout(() => {
1061
+ this.gcTimer = null;
1062
+ this.client.dropInfiniteEntry(this);
1063
+ }, this.gcTime);
1064
+ }
1065
+ dispose() {
1066
+ if (this.gcTimer != null) {
1067
+ clearTimeout(this.gcTimer);
1068
+ this.gcTimer = null;
1069
+ }
1070
+ this.stopIntervalTimer();
1071
+ this.entry.dispose();
1072
+ }
1073
+ };
1074
+ /**
1075
+ * Per-root entry registry. Owns the keyed `Map<hash, ClientEntry>` per query,
1076
+ * GC timers, refetch-interval timers. Subscribers are routed in/out via
1077
+ * `acquire` / `release`.
1078
+ */
1079
+ var QueryClient = class {
1080
+ maps = /* @__PURE__ */ new Map();
1081
+ infiniteMaps = /* @__PURE__ */ new Map();
1082
+ touchedQueries = /* @__PURE__ */ new Set();
1083
+ touchedInfiniteQueries = /* @__PURE__ */ new Set();
1084
+ hydratedData = /* @__PURE__ */ new Map();
1085
+ /** Mutations inflight across the whole root — used by `waitForIdle`. */
1086
+ mutationsInflight$ = signal(0);
1087
+ onError;
1088
+ disposed = false;
1089
+ /** Devtools bus, if any — passed by `createRoot`. Used to emit cache events. */
1090
+ devtools;
1091
+ /** Root-level deps; passed to every `QuerySpec.fetcher` via `FetchCtx`. */
1092
+ deps;
1093
+ /** Root-wide defaults for refetch triggers; per-query spec overrides win. Spec §5.9. */
1094
+ refetchOnWindowFocus;
1095
+ refetchOnReconnect;
1096
+ /**
1097
+ * Installed plugins. Fired on every `setData` / `invalidate` / `gc` so
1098
+ * cross-tab / persistence-like layers can observe and react. SPEC §13.2.
1099
+ */
1100
+ plugins;
1101
+ /**
1102
+ * Flipped to `true` while a remote-originated write (via
1103
+ * `applyRemoteSetData` / `applyRemoteInvalidate`) is being applied. The
1104
+ * resulting plugin events carry `isRemote: true` so plugins know to skip
1105
+ * rebroadcast.
1106
+ */
1107
+ applyingRemote = false;
1108
+ constructor(opts) {
1109
+ this.onError = opts?.onError;
1110
+ this.devtools = opts?.devtools;
1111
+ this.deps = opts?.deps ?? {};
1112
+ this.refetchOnWindowFocus = opts?.refetchOnWindowFocus ?? false;
1113
+ this.refetchOnReconnect = opts?.refetchOnReconnect ?? false;
1114
+ this.plugins = opts?.plugins ?? [];
1115
+ if (opts?.hydrate) this.hydrate(opts.hydrate);
1116
+ const api = this.makePluginApi();
1117
+ for (const plugin of this.plugins) this.callPlugin(() => plugin.init?.(api));
1118
+ }
1119
+ /**
1120
+ * Build the `QueryClientPluginApi` view that plugins receive at `init`
1121
+ * time. Closes over `this`; safe to hand out — plugins call back through
1122
+ * these methods to push remote-originated writes into the local cache.
1123
+ */
1124
+ makePluginApi() {
1125
+ const self = this;
1126
+ return {
1127
+ applyRemoteSetData(queryId, keyArgs, data) {
1128
+ self.applyRemoteSetData(queryId, keyArgs, data);
1129
+ },
1130
+ applyRemoteInvalidate(queryId, keyArgs) {
1131
+ self.applyRemoteInvalidate(queryId, keyArgs);
1132
+ },
1133
+ subscribedKeys(queryId) {
1134
+ return self.subscribedKeysFor(queryId);
1135
+ }
1136
+ };
1137
+ }
1138
+ /** Invoke a plugin callback; route exceptions through `onError`. */
1139
+ callPlugin(fn) {
1140
+ try {
1141
+ fn();
1142
+ } catch (err) {
1143
+ dispatchError(this.onError, err, {
1144
+ kind: "plugin",
1145
+ controllerPath: []
1146
+ });
1147
+ }
1148
+ }
1149
+ emitSetData(query, keyArgs, data, kind) {
1150
+ if (this.plugins.length === 0) return;
1151
+ const queryId = query.__spec.queryId;
1152
+ if (queryId == null) return;
1153
+ const event = {
1154
+ queryId,
1155
+ keyArgs,
1156
+ data,
1157
+ kind,
1158
+ isRemote: this.applyingRemote
1159
+ };
1160
+ for (const plugin of this.plugins) if (plugin.onSetData) {
1161
+ const cb = plugin.onSetData;
1162
+ this.callPlugin(() => cb.call(plugin, event));
1163
+ }
1164
+ }
1165
+ emitInvalidate(query, keyArgs, kind) {
1166
+ if (this.plugins.length === 0) return;
1167
+ const queryId = query.__spec.queryId;
1168
+ if (queryId == null) return;
1169
+ const event = {
1170
+ queryId,
1171
+ keyArgs,
1172
+ kind,
1173
+ isRemote: this.applyingRemote
1174
+ };
1175
+ for (const plugin of this.plugins) if (plugin.onInvalidate) {
1176
+ const cb = plugin.onInvalidate;
1177
+ this.callPlugin(() => cb.call(plugin, event));
1178
+ }
1179
+ }
1180
+ emitGc(query, keyArgs, kind) {
1181
+ if (this.plugins.length === 0) return;
1182
+ const queryId = query.__spec.queryId;
1183
+ if (queryId == null) return;
1184
+ const event = {
1185
+ queryId,
1186
+ keyArgs,
1187
+ kind
1188
+ };
1189
+ for (const plugin of this.plugins) if (plugin.onGc) {
1190
+ const cb = plugin.onGc;
1191
+ this.callPlugin(() => cb.call(plugin, event));
1192
+ }
1193
+ }
1194
+ /** Resolve `queryId → live entry-map keys`. Empty array when unknown. */
1195
+ subscribedKeysFor(queryId) {
1196
+ const query = lookupRegisteredQuery(queryId);
1197
+ if (!query) return [];
1198
+ const out = [];
1199
+ if (query.__olas === "query") {
1200
+ const map = this.maps.get(query);
1201
+ if (map) for (const ce of map.values()) out.push(ce.keyArgs);
1202
+ } else {
1203
+ const map = this.infiniteMaps.get(query);
1204
+ if (map) for (const ce of map.values()) out.push(ce.keyArgs);
1205
+ }
1206
+ return out;
1207
+ }
1208
+ /**
1209
+ * Apply a remote-originated `setData` for the query identified by
1210
+ * `queryId`, scoped to the entry already keyed by `keyArgs` in this
1211
+ * client. Goes through the underlying `Entry.setData` so subscribers see
1212
+ * the write; plugin `onSetData` fires with `isRemote: true`.
1213
+ *
1214
+ * Drops silently when:
1215
+ * - No query with that id is registered (the receiving tab hasn't
1216
+ * imported the module that defined it).
1217
+ * - The registered query is an infinite query (cross-tab infinite sync
1218
+ * is deferred — see `plugin.ts` `SetDataEvent.kind`).
1219
+ * - No local entry exists for that key (the receiving tab isn't
1220
+ * subscribed; nothing useful to write to without callArgs for a
1221
+ * future refetch).
1222
+ */
1223
+ applyRemoteSetData(queryId, keyArgs, data) {
1224
+ const query = lookupRegisteredQuery(queryId);
1225
+ if (!query) return;
1226
+ if (query.__olas !== "query") return;
1227
+ const internal = query;
1228
+ const map = this.maps.get(internal);
1229
+ if (!map) return;
1230
+ const hash = stableHash(keyArgs);
1231
+ const entry = map.get(hash);
1232
+ if (!entry) return;
1233
+ this.applyingRemote = true;
1234
+ try {
1235
+ entry.entry.setData(() => data);
1236
+ this.emitSetData(internal, entry.keyArgs, data, "data");
1237
+ } finally {
1238
+ this.applyingRemote = false;
1239
+ }
1240
+ }
1241
+ applyRemoteInvalidate(queryId, keyArgs) {
1242
+ const query = lookupRegisteredQuery(queryId);
1243
+ if (!query) return;
1244
+ if (query.__olas !== "query") return;
1245
+ const internal = query;
1246
+ const map = this.maps.get(internal);
1247
+ if (!map) return;
1248
+ const hash = stableHash(keyArgs);
1249
+ const entry = map.get(hash);
1250
+ if (!entry) return;
1251
+ this.applyingRemote = true;
1252
+ try {
1253
+ entry.entry.invalidate().catch((err) => {
1254
+ dispatchError(this.onError, err, {
1255
+ kind: "cache",
1256
+ controllerPath: [],
1257
+ queryKey: entry.keyArgs
1258
+ });
1259
+ });
1260
+ this.emitInvalidate(internal, entry.keyArgs, "data");
1261
+ } finally {
1262
+ this.applyingRemote = false;
1263
+ }
1264
+ }
1265
+ hydrate(state) {
1266
+ if (state.version !== 1) return;
1267
+ for (const entry of state.entries) {
1268
+ const hash = stableHash(entry.key);
1269
+ this.hydratedData.set(hash, {
1270
+ data: entry.data,
1271
+ lastUpdatedAt: entry.lastUpdatedAt
1272
+ });
1273
+ }
1274
+ }
1275
+ /**
1276
+ * Snapshot every live cache entry (regular + infinite) as a flat list of
1277
+ * `DebugCacheEntry`. Exposed via `root.__debug.queryEntries()` for the
1278
+ * devtools cache inspector — shows current data and state, not past
1279
+ * fetch events. Spec §20.9.
1280
+ */
1281
+ queryEntriesSnapshot() {
1282
+ const out = [];
1283
+ for (const map of this.maps.values()) for (const ce of map.values()) out.push({
1284
+ key: ce.keyArgs,
1285
+ status: ce.entry.status.peek(),
1286
+ data: ce.entry.data.peek(),
1287
+ error: ce.entry.error.peek(),
1288
+ lastUpdatedAt: ce.entry.lastUpdatedAt.peek(),
1289
+ isStale: ce.entry.isStale.peek(),
1290
+ isFetching: ce.entry.isFetching.peek(),
1291
+ hasPendingMutations: ce.entry.hasPendingMutations.peek()
1292
+ });
1293
+ for (const map of this.infiniteMaps.values()) for (const ce of map.values()) out.push({
1294
+ key: ce.keyArgs,
1295
+ status: ce.entry.status.peek(),
1296
+ data: ce.entry.pages.peek(),
1297
+ error: ce.entry.error.peek(),
1298
+ lastUpdatedAt: ce.entry.lastUpdatedAt.peek(),
1299
+ isStale: ce.entry.isStale.peek(),
1300
+ isFetching: ce.entry.isFetching.peek(),
1301
+ hasPendingMutations: ce.entry.hasPendingMutations.peek()
1302
+ });
1303
+ return out;
1304
+ }
1305
+ dehydrate() {
1306
+ const entries = [];
1307
+ for (const map of this.maps.values()) for (const ce of map.values()) if (ce.entry.status.peek() === "success") entries.push({
1308
+ key: ce.keyArgs,
1309
+ data: ce.entry.data.peek(),
1310
+ lastUpdatedAt: ce.entry.lastUpdatedAt.peek() ?? Date.now()
1311
+ });
1312
+ return {
1313
+ version: 1,
1314
+ entries
1315
+ };
1316
+ }
1317
+ async waitForIdle() {
1318
+ for (let safety = 0; safety < 100; safety++) {
1319
+ const tasks = [];
1320
+ for (const map of this.maps.values()) for (const ce of map.values()) if (ce.entry.isFetching.peek()) tasks.push(waitUntilFalse(ce.entry.isFetching));
1321
+ for (const map of this.infiniteMaps.values()) for (const ce of map.values()) if (ce.entry.isFetching.peek()) tasks.push(waitUntilFalse(ce.entry.isFetching));
1322
+ if (this.mutationsInflight$.peek() > 0) tasks.push(new Promise((resolve) => {
1323
+ const unsub = this.mutationsInflight$.subscribe((v) => {
1324
+ if (v === 0) {
1325
+ unsub();
1326
+ resolve();
1327
+ }
1328
+ });
1329
+ }));
1330
+ if (tasks.length === 0) return;
1331
+ await Promise.all(tasks);
1332
+ }
1333
+ }
1334
+ bindEntry(query, args) {
1335
+ const internal = query;
1336
+ let map = this.maps.get(internal);
1337
+ if (!map) {
1338
+ map = /* @__PURE__ */ new Map();
1339
+ this.maps.set(internal, map);
1340
+ this.touchedQueries.add(internal);
1341
+ internal.__clients.add(this);
1342
+ }
1343
+ const keyArgs = internal.__spec.key(...args);
1344
+ const hash = stableHash(keyArgs);
1345
+ let entry = map.get(hash);
1346
+ if (!entry) {
1347
+ const hydrated = this.hydratedData.get(hash);
1348
+ if (hydrated) this.hydratedData.delete(hash);
1349
+ entry = new ClientEntry(this, internal, args, keyArgs, internal.__spec, hydrated);
1350
+ map.set(hash, entry);
1351
+ entry.scheduleGcIfOrphan();
1352
+ }
1353
+ return entry;
1354
+ }
1355
+ dropEntry(entry) {
1356
+ const map = this.maps.get(entry.query);
1357
+ if (!map) return;
1358
+ const hash = stableHash(entry.keyArgs);
1359
+ if (map.get(hash) !== entry) return;
1360
+ map.delete(hash);
1361
+ entry.dispose();
1362
+ if (map.size === 0) this.maps.delete(entry.query);
1363
+ this.emitGc(entry.query, entry.keyArgs, "data");
1364
+ }
1365
+ invalidate(query, args) {
1366
+ const internal = query;
1367
+ const map = this.maps.get(internal);
1368
+ if (!map) return;
1369
+ const keyArgs = internal.__spec.key(...args);
1370
+ const hash = stableHash(keyArgs);
1371
+ const entry = map.get(hash);
1372
+ if (!entry) return;
1373
+ entry.entry.invalidate().catch((err) => {
1374
+ dispatchError(this.onError, err, {
1375
+ kind: "cache",
1376
+ controllerPath: [],
1377
+ queryKey: keyArgs
1378
+ });
1379
+ });
1380
+ this.emitInvalidate(internal, keyArgs, "data");
1381
+ }
1382
+ invalidateAll(query) {
1383
+ const internal = query;
1384
+ const map = this.maps.get(internal);
1385
+ if (!map) return;
1386
+ for (const [hash, entry] of map) {
1387
+ entry.entry.invalidate().catch((err) => {
1388
+ dispatchError(this.onError, err, {
1389
+ kind: "cache",
1390
+ controllerPath: [],
1391
+ queryKey: entry.keyArgs
1392
+ });
1393
+ });
1394
+ this.emitInvalidate(internal, entry.keyArgs, "data");
1395
+ }
1396
+ }
1397
+ setData(query, args, updater) {
1398
+ const entry = this.bindEntry(query, args);
1399
+ const snapshot = entry.entry.setData(updater);
1400
+ this.emitSetData(entry.query, entry.keyArgs, entry.entry.data.peek(), "data");
1401
+ return snapshot;
1402
+ }
1403
+ bindInfiniteEntry(query, args) {
1404
+ const internal = query;
1405
+ let map = this.infiniteMaps.get(internal);
1406
+ if (!map) {
1407
+ map = /* @__PURE__ */ new Map();
1408
+ this.infiniteMaps.set(internal, map);
1409
+ this.touchedInfiniteQueries.add(internal);
1410
+ internal.__clients.add(this);
1411
+ }
1412
+ const keyArgs = internal.__spec.key(...args);
1413
+ const hash = stableHash(keyArgs);
1414
+ let entry = map.get(hash);
1415
+ if (!entry) {
1416
+ entry = new InfiniteClientEntry(this, internal, args, keyArgs, internal.__spec);
1417
+ map.set(hash, entry);
1418
+ entry.scheduleGcIfOrphan();
1419
+ }
1420
+ return entry;
1421
+ }
1422
+ dropInfiniteEntry(entry) {
1423
+ const map = this.infiniteMaps.get(entry.query);
1424
+ if (!map) return;
1425
+ const hash = stableHash(entry.keyArgs);
1426
+ if (map.get(hash) !== entry) return;
1427
+ map.delete(hash);
1428
+ entry.dispose();
1429
+ if (map.size === 0) this.infiniteMaps.delete(entry.query);
1430
+ this.emitGc(entry.query, entry.keyArgs, "infinite");
1431
+ }
1432
+ invalidateInfinite(query, args) {
1433
+ const internal = query;
1434
+ const map = this.infiniteMaps.get(internal);
1435
+ if (!map) return;
1436
+ const keyArgs = internal.__spec.key(...args);
1437
+ const hash = stableHash(keyArgs);
1438
+ const entry = map.get(hash);
1439
+ if (!entry) return;
1440
+ entry.entry.invalidate().catch((err) => {
1441
+ dispatchError(this.onError, err, {
1442
+ kind: "cache",
1443
+ controllerPath: [],
1444
+ queryKey: entry.keyArgs
1445
+ });
1446
+ });
1447
+ this.emitInvalidate(internal, keyArgs, "infinite");
1448
+ }
1449
+ invalidateAllInfinite(query) {
1450
+ const internal = query;
1451
+ const map = this.infiniteMaps.get(internal);
1452
+ if (!map) return;
1453
+ for (const entry of map.values()) {
1454
+ entry.entry.invalidate().catch((err) => {
1455
+ dispatchError(this.onError, err, {
1456
+ kind: "cache",
1457
+ controllerPath: [],
1458
+ queryKey: entry.keyArgs
1459
+ });
1460
+ });
1461
+ this.emitInvalidate(internal, entry.keyArgs, "infinite");
1462
+ }
1463
+ }
1464
+ setInfiniteData(query, args, updater) {
1465
+ const entry = this.bindInfiniteEntry(query, args);
1466
+ const snapshot = entry.entry.setData(updater);
1467
+ this.emitSetData(entry.query, entry.keyArgs, entry.entry.pages.peek(), "infinite");
1468
+ return snapshot;
1469
+ }
1470
+ prefetchInfinite(query, args) {
1471
+ const entry = this.bindInfiniteEntry(query, args);
1472
+ entry.acquire();
1473
+ return (async () => {
1474
+ if (entry.entry.status.peek() === "success" && !entry.entry.isStaleNow()) return entry.entry.pages.peek()[0];
1475
+ return entry.entry.startFetch();
1476
+ })().finally(() => entry.release());
1477
+ }
1478
+ prefetch(query, args) {
1479
+ const entry = this.bindEntry(query, args);
1480
+ entry.acquire();
1481
+ return (async () => {
1482
+ if (entry.entry.status.peek() === "success" && !entry.entry.isStaleNow()) return entry.entry.data.peek();
1483
+ if (entry.entry.isFetching.peek()) return entry.entry.firstValue();
1484
+ return entry.entry.startFetch();
1485
+ })().finally(() => entry.release());
1486
+ }
1487
+ inflightCount() {
1488
+ let count = 0;
1489
+ for (const [, map] of this.maps) for (const [, entry] of map) if (entry.entry.isFetching.peek()) count++;
1490
+ return count;
1491
+ }
1492
+ dispose() {
1493
+ if (this.disposed) return;
1494
+ this.disposed = true;
1495
+ for (const map of this.maps.values()) for (const entry of map.values()) entry.dispose();
1496
+ this.maps.clear();
1497
+ for (const map of this.infiniteMaps.values()) for (const entry of map.values()) entry.dispose();
1498
+ this.infiniteMaps.clear();
1499
+ for (const q of this.touchedQueries) q.__clients.delete(this);
1500
+ this.touchedQueries.clear();
1501
+ for (const q of this.touchedInfiniteQueries) q.__clients.delete(this);
1502
+ this.touchedInfiniteQueries.clear();
1503
+ this.hydratedData.clear();
1504
+ for (const plugin of this.plugins) if (plugin.dispose) {
1505
+ const cb = plugin.dispose;
1506
+ this.callPlugin(() => cb.call(plugin));
1507
+ }
1508
+ }
1509
+ };
1510
+ function waitUntilFalse(sig) {
1511
+ if (!sig.peek()) return Promise.resolve();
1512
+ return new Promise((resolve) => {
1513
+ const unsub = sig.subscribe((v) => {
1514
+ if (!v) {
1515
+ unsub();
1516
+ resolve();
1517
+ }
1518
+ });
1519
+ });
1520
+ }
1521
+ //#endregion
1522
+ //#region src/emitter.ts
1523
+ var EmitterImpl = class {
1524
+ handlers = /* @__PURE__ */ new Set();
1525
+ disposed = false;
1526
+ emit(value) {
1527
+ if (this.disposed) return;
1528
+ const snapshot = Array.from(this.handlers);
1529
+ for (const handler of snapshot) handler(value);
1530
+ }
1531
+ on(handler) {
1532
+ if (this.disposed) return () => {};
1533
+ const wrapped = handler;
1534
+ this.handlers.add(wrapped);
1535
+ return () => {
1536
+ this.handlers.delete(wrapped);
1537
+ };
1538
+ }
1539
+ once(handler) {
1540
+ if (this.disposed) return () => {};
1541
+ const wrapped = (value) => {
1542
+ this.handlers.delete(wrapped);
1543
+ handler(value);
1544
+ };
1545
+ this.handlers.add(wrapped);
1546
+ return () => {
1547
+ this.handlers.delete(wrapped);
1548
+ };
1549
+ }
1550
+ dispose() {
1551
+ if (this.disposed) return;
1552
+ this.disposed = true;
1553
+ this.handlers.clear();
1554
+ }
1555
+ };
1556
+ /**
1557
+ * Create a standalone emitter. Handlers persist until explicitly unsubscribed
1558
+ * (or the emitter is disposed). Use this for emitters that live outside any
1559
+ * single controller — typically in deps. Use `ctx.emitter()` for emitters that
1560
+ * should auto-clean with a controller.
1561
+ */
1562
+ function createEmitter() {
1563
+ const impl = new EmitterImpl();
1564
+ return {
1565
+ emit: ((value) => impl.emit(value)),
1566
+ on: (handler) => impl.on(handler),
1567
+ once: (handler) => impl.once(handler),
1568
+ dispose: () => impl.dispose()
1569
+ };
1570
+ }
1571
+ //#endregion
1572
+ //#region src/forms/field.ts
1573
+ var FieldImpl = class {
1574
+ value$;
1575
+ errors$;
1576
+ touched$;
1577
+ dirty$;
1578
+ validating$;
1579
+ isValid$;
1580
+ revalidateTrigger$;
1581
+ validators;
1582
+ /** The value `reset()` returns to. Mutated by `setAsInitial()` so a form
1583
+ * initialized from server data resets to *that* data, not the empty seed. */
1584
+ initial;
1585
+ validatorDispose = null;
1586
+ currentAbort = null;
1587
+ runId = 0;
1588
+ disposed = false;
1589
+ devtoolsOwner = null;
1590
+ constructor(initial, validators = []) {
1591
+ this.initial = initial;
1592
+ this.validators = validators;
1593
+ this.value$ = signal(initial);
1594
+ this.errors$ = signal([]);
1595
+ this.touched$ = signal(false);
1596
+ this.dirty$ = signal(false);
1597
+ this.validating$ = signal(false);
1598
+ this.revalidateTrigger$ = signal(0);
1599
+ this.isValid$ = computed(() => this.errors$.value.length === 0 && !this.validating$.value);
1600
+ if (validators.length > 0) this.validatorDispose = effect(() => {
1601
+ this.runValidators();
1602
+ });
1603
+ }
1604
+ get value() {
1605
+ return this.value$.value;
1606
+ }
1607
+ peek() {
1608
+ return this.value$.peek();
1609
+ }
1610
+ subscribe(handler) {
1611
+ return this.value$.subscribe(handler);
1612
+ }
1613
+ get errors() {
1614
+ return this.errors$;
1615
+ }
1616
+ get isValid() {
1617
+ return this.isValid$;
1618
+ }
1619
+ get isDirty() {
1620
+ return this.dirty$;
1621
+ }
1622
+ get touched() {
1623
+ return this.touched$;
1624
+ }
1625
+ get isValidating() {
1626
+ return this.validating$;
1627
+ }
1628
+ set(value) {
1629
+ if (this.disposed) return;
1630
+ this.value$.set(value);
1631
+ this.dirty$.set(true);
1632
+ }
1633
+ /**
1634
+ * Reseat the field as if this value had been its constructor `initial`.
1635
+ * Sets the value, re-anchors `reset()`'s target, and does NOT mark dirty.
1636
+ * Used by `Form` when applying its own `initial` (in the constructor and
1637
+ * on `reset()`), so server-loaded forms don't start dirty. Internal-ish —
1638
+ * exposed for `Form`'s use, not for user code that just wants to write.
1639
+ */
1640
+ setAsInitial(value) {
1641
+ if (this.disposed) return;
1642
+ this.initial = value;
1643
+ batch(() => {
1644
+ this.value$.set(value);
1645
+ this.dirty$.set(false);
1646
+ });
1647
+ }
1648
+ reset() {
1649
+ if (this.disposed) return;
1650
+ this.currentAbort?.abort();
1651
+ this.currentAbort = null;
1652
+ batch(() => {
1653
+ this.value$.set(this.initial);
1654
+ this.dirty$.set(false);
1655
+ this.touched$.set(false);
1656
+ this.errors$.set([]);
1657
+ this.validating$.set(false);
1658
+ });
1659
+ }
1660
+ markTouched() {
1661
+ if (this.disposed) return;
1662
+ this.touched$.set(true);
1663
+ }
1664
+ async revalidate() {
1665
+ if (this.disposed) return this.isValid$.peek();
1666
+ this.revalidateTrigger$.update((n) => n + 1);
1667
+ await this.waitUntilSettled();
1668
+ return this.isValid$.peek();
1669
+ }
1670
+ dispose() {
1671
+ if (this.disposed) return;
1672
+ this.disposed = true;
1673
+ this.validatorDispose?.();
1674
+ this.validatorDispose = null;
1675
+ this.currentAbort?.abort();
1676
+ this.currentAbort = null;
1677
+ this.devtoolsOwner = null;
1678
+ }
1679
+ /**
1680
+ * Bind this field to a devtools owner. Each subsequent validation pass
1681
+ * publishes a `field:validated` event with the supplied path + name.
1682
+ * Idempotent — calling again replaces the owner. Internal: called by
1683
+ * `createForm` / `createFieldArray` so the form's keys reach the panel.
1684
+ */
1685
+ bindDevtoolsOwner(owner) {
1686
+ this.devtoolsOwner = owner;
1687
+ }
1688
+ emitValidated(valid, errors) {}
1689
+ async waitUntilSettled() {
1690
+ if (!this.validating$.peek()) return;
1691
+ await new Promise((resolve) => {
1692
+ const unsub = this.validating$.subscribe((v) => {
1693
+ if (!v) {
1694
+ unsub();
1695
+ resolve();
1696
+ }
1697
+ });
1698
+ });
1699
+ }
1700
+ runValidators() {
1701
+ if (this.disposed) return;
1702
+ const value = this.value$.value;
1703
+ this.revalidateTrigger$.value;
1704
+ this.currentAbort?.abort();
1705
+ const abort = new AbortController();
1706
+ this.currentAbort = abort;
1707
+ const myId = ++this.runId;
1708
+ const syncErrors = [];
1709
+ const asyncPromises = [];
1710
+ for (const validator of this.validators) {
1711
+ const result = validator(value, abort.signal);
1712
+ if (result instanceof Promise) asyncPromises.push(result);
1713
+ else if (result != null) syncErrors.push(result);
1714
+ }
1715
+ if (syncErrors.length > 0) {
1716
+ batch(() => {
1717
+ this.errors$.set(syncErrors);
1718
+ this.validating$.set(false);
1719
+ });
1720
+ this.emitValidated(false, syncErrors);
1721
+ return;
1722
+ }
1723
+ if (asyncPromises.length === 0) {
1724
+ batch(() => {
1725
+ this.errors$.set([]);
1726
+ this.validating$.set(false);
1727
+ });
1728
+ this.emitValidated(true, []);
1729
+ return;
1730
+ }
1731
+ batch(() => {
1732
+ this.errors$.set([]);
1733
+ this.validating$.set(true);
1734
+ });
1735
+ Promise.allSettled(asyncPromises).then((results) => {
1736
+ if (myId !== this.runId || this.disposed) return;
1737
+ const asyncErrors = [];
1738
+ for (const r of results) if (r.status === "fulfilled") {
1739
+ if (r.value != null) asyncErrors.push(r.value);
1740
+ } else if (!isAbortError(r.reason)) {
1741
+ const msg = r.reason instanceof Error ? r.reason.message : String(r.reason);
1742
+ asyncErrors.push(msg);
1743
+ }
1744
+ batch(() => {
1745
+ this.errors$.set(asyncErrors);
1746
+ this.validating$.set(false);
1747
+ });
1748
+ this.emitValidated(asyncErrors.length === 0, asyncErrors);
1749
+ });
1750
+ }
1751
+ };
1752
+ /**
1753
+ * Internal — type guard / accessor for the binding hook. Avoids exposing
1754
+ * `bindDevtoolsOwner` on the public `Field<T>` type while letting `createForm`
1755
+ * call it via a structural check.
1756
+ */
1757
+ function bindFieldDevtoolsOwner(field, owner) {
1758
+ const impl = field;
1759
+ if (typeof impl.bindDevtoolsOwner === "function") impl.bindDevtoolsOwner(owner);
1760
+ }
1761
+ function createField(initial, validators) {
1762
+ return new FieldImpl(initial, validators);
1763
+ }
1764
+ /**
1765
+ * Wrap an async validator with a debounce. The debounce timer resets on every
1766
+ * value change. While debouncing or the request is in flight, the field's
1767
+ * `isValidating` is true and `isValid` is false (treat-as-invalid-until-proven-valid).
1768
+ */
1769
+ function debouncedValidator(fn, ms) {
1770
+ return (value, signal) => new Promise((resolve, reject) => {
1771
+ if (signal.aborted) {
1772
+ reject(new DOMException("Aborted", "AbortError"));
1773
+ return;
1774
+ }
1775
+ const timer = setTimeout(() => {
1776
+ signal.removeEventListener("abort", onAbort);
1777
+ fn(value, signal).then(resolve, reject);
1778
+ }, ms);
1779
+ const onAbort = () => {
1780
+ clearTimeout(timer);
1781
+ signal.removeEventListener("abort", onAbort);
1782
+ reject(new DOMException("Aborted", "AbortError"));
1783
+ };
1784
+ signal.addEventListener("abort", onAbort, { once: true });
1785
+ });
1786
+ }
1787
+ //#endregion
1788
+ //#region src/forms/form.ts
1789
+ const FORM_BRAND = Symbol.for("olas.form");
1790
+ const FIELD_ARRAY_BRAND = Symbol.for("olas.fieldArray");
1791
+ const isForm = (x) => typeof x === "object" && x !== null && x[FORM_BRAND] === true;
1792
+ const isFieldArray = (x) => typeof x === "object" && x !== null && x[FIELD_ARRAY_BRAND] === true;
1793
+ var FormImpl = class {
1794
+ [FORM_BRAND] = true;
1795
+ fields;
1796
+ value;
1797
+ errors;
1798
+ isValid;
1799
+ isDirty;
1800
+ touched;
1801
+ isValidating;
1802
+ flatErrors;
1803
+ topLevelErrors$ = signal([]);
1804
+ topLevelErrors = this.topLevelErrors$;
1805
+ topLevelValidating$ = signal(false);
1806
+ validators;
1807
+ options;
1808
+ validatorDispose = null;
1809
+ currentValidatorRun = 0;
1810
+ currentValidatorAbort = null;
1811
+ disposed = false;
1812
+ constructor(schema, options) {
1813
+ this.fields = schema;
1814
+ this.options = options;
1815
+ this.validators = options?.validators ?? [];
1816
+ if (options?.initial !== void 0) {
1817
+ const ini = typeof options.initial === "function" ? options.initial() : options.initial;
1818
+ if (ini !== void 0) this.applyPartial(ini, true);
1819
+ }
1820
+ this.value = computed(() => this.computeValue());
1821
+ this.errors = computed(() => this.computeErrors());
1822
+ this.isDirty = computed(() => this.computeBool("isDirty"));
1823
+ this.touched = computed(() => this.computeBool("touched"));
1824
+ this.isValidating = computed(() => {
1825
+ if (this.topLevelValidating$.value) return true;
1826
+ for (const child of Object.values(this.fields)) if (child.isValidating.value) return true;
1827
+ return false;
1828
+ });
1829
+ this.isValid = computed(() => {
1830
+ if (this.topLevelErrors$.value.length > 0) return false;
1831
+ if (this.isValidating.value) return false;
1832
+ for (const child of Object.values(this.fields)) if (!child.isValid.value) return false;
1833
+ return true;
1834
+ });
1835
+ this.flatErrors = computed(() => this.computeFlatErrors());
1836
+ if (this.validators.length > 0) this.validatorDispose = effect(() => this.runTopLevelValidators());
1837
+ }
1838
+ computeValue() {
1839
+ const out = {};
1840
+ for (const [k, child] of Object.entries(this.fields)) if (isForm(child) || isFieldArray(child)) out[k] = child.value.value;
1841
+ else out[k] = child.value;
1842
+ return out;
1843
+ }
1844
+ computeErrors() {
1845
+ const out = {};
1846
+ for (const [k, child] of Object.entries(this.fields)) if (isForm(child)) out[k] = child.errors.value;
1847
+ else if (isFieldArray(child)) out[k] = child.errors.value;
1848
+ else {
1849
+ const errs = child.errors.value;
1850
+ out[k] = errs.length > 0 ? errs : void 0;
1851
+ }
1852
+ return out;
1853
+ }
1854
+ computeBool(key) {
1855
+ for (const child of Object.values(this.fields)) if (child[key]?.value) return true;
1856
+ return false;
1857
+ }
1858
+ computeFlatErrors() {
1859
+ const out = [];
1860
+ const tle = this.topLevelErrors$.value;
1861
+ if (tle.length > 0) out.push({
1862
+ path: "",
1863
+ errors: tle
1864
+ });
1865
+ walkErrors(this.fields, "", out);
1866
+ return out;
1867
+ }
1868
+ set(partial) {
1869
+ if (this.disposed) return;
1870
+ batch(() => this.applyPartial(partial, false));
1871
+ }
1872
+ applyPartial(partial, asInitial) {
1873
+ for (const [k, val] of Object.entries(partial)) {
1874
+ const child = this.fields[k];
1875
+ if (!child) continue;
1876
+ if (isForm(child)) if (asInitial) child.resetWithInitial(val);
1877
+ else child.set(val);
1878
+ else if (isFieldArray(child)) {
1879
+ const arr = child;
1880
+ arr.clear();
1881
+ for (const itemVal of val) arr.add(itemVal);
1882
+ } else {
1883
+ const f = child;
1884
+ if (asInitial) f.setAsInitial(val);
1885
+ else f.set(val);
1886
+ }
1887
+ }
1888
+ }
1889
+ /** Internal: re-seat this form's leaves from `partial` as their new initial. */
1890
+ resetWithInitial(partial) {
1891
+ if (this.disposed) return;
1892
+ batch(() => this.applyPartial(partial, true));
1893
+ }
1894
+ reset() {
1895
+ if (this.disposed) return;
1896
+ batch(() => {
1897
+ for (const child of Object.values(this.fields)) if (isForm(child) || isFieldArray(child)) child.reset();
1898
+ else child.reset();
1899
+ this.topLevelErrors$.set([]);
1900
+ });
1901
+ if (this.options?.initial !== void 0) {
1902
+ const ini = typeof this.options.initial === "function" ? this.options.initial() : this.options.initial;
1903
+ if (ini !== void 0) this.applyPartial(ini, true);
1904
+ }
1905
+ }
1906
+ markAllTouched() {
1907
+ if (this.disposed) return;
1908
+ for (const child of Object.values(this.fields)) if (isForm(child)) child.markAllTouched();
1909
+ else if (isFieldArray(child)) child.markAllTouched();
1910
+ else child.markTouched();
1911
+ }
1912
+ async validate() {
1913
+ if (this.disposed) return this.isValid.peek();
1914
+ const tasks = [];
1915
+ for (const child of Object.values(this.fields)) if (isForm(child) || isFieldArray(child)) tasks.push(child.validate());
1916
+ else tasks.push(child.revalidate());
1917
+ await Promise.all(tasks);
1918
+ if (this.topLevelValidating$.peek()) await new Promise((resolve) => {
1919
+ const unsub = this.topLevelValidating$.subscribe((v) => {
1920
+ if (!v) {
1921
+ unsub();
1922
+ resolve();
1923
+ }
1924
+ });
1925
+ });
1926
+ return this.isValid.peek();
1927
+ }
1928
+ dispose() {
1929
+ if (this.disposed) return;
1930
+ this.disposed = true;
1931
+ this.validatorDispose?.();
1932
+ this.currentValidatorAbort?.abort();
1933
+ for (const child of Object.values(this.fields)) child.dispose?.();
1934
+ }
1935
+ runTopLevelValidators() {
1936
+ if (this.disposed) return;
1937
+ const value = this.value.value;
1938
+ this.currentValidatorAbort?.abort();
1939
+ const abort = new AbortController();
1940
+ this.currentValidatorAbort = abort;
1941
+ const myId = ++this.currentValidatorRun;
1942
+ const syncErrors = [];
1943
+ const asyncPromises = [];
1944
+ for (const v of this.validators) {
1945
+ const r = v(value, abort.signal);
1946
+ if (r instanceof Promise) asyncPromises.push(r);
1947
+ else if (r != null) syncErrors.push(r);
1948
+ }
1949
+ if (syncErrors.length > 0) {
1950
+ batch(() => {
1951
+ this.topLevelErrors$.set(syncErrors);
1952
+ this.topLevelValidating$.set(false);
1953
+ });
1954
+ return;
1955
+ }
1956
+ if (asyncPromises.length === 0) {
1957
+ batch(() => {
1958
+ this.topLevelErrors$.set([]);
1959
+ this.topLevelValidating$.set(false);
1960
+ });
1961
+ return;
1962
+ }
1963
+ batch(() => {
1964
+ this.topLevelErrors$.set([]);
1965
+ this.topLevelValidating$.set(true);
1966
+ });
1967
+ Promise.allSettled(asyncPromises).then((results) => {
1968
+ if (myId !== this.currentValidatorRun || this.disposed) return;
1969
+ const errs = [];
1970
+ for (const r of results) if (r.status === "fulfilled" && r.value != null) errs.push(r.value);
1971
+ batch(() => {
1972
+ this.topLevelErrors$.set(errs);
1973
+ this.topLevelValidating$.set(false);
1974
+ });
1975
+ });
1976
+ }
1977
+ };
1978
+ function walkErrors(fields, prefix, out) {
1979
+ for (const [k, child] of Object.entries(fields)) {
1980
+ const path = prefix ? `${prefix}.${k}` : k;
1981
+ if (isForm(child)) {
1982
+ const tle = child.topLevelErrors.value;
1983
+ if (tle.length > 0) out.push({
1984
+ path,
1985
+ errors: tle
1986
+ });
1987
+ walkErrors(child.fields, path, out);
1988
+ } else if (isFieldArray(child)) {
1989
+ const tle = child.topLevelErrors.value;
1990
+ if (tle.length > 0) out.push({
1991
+ path,
1992
+ errors: tle
1993
+ });
1994
+ child.items.value.forEach((item, idx) => {
1995
+ const itemPath = `${path}[${idx}]`;
1996
+ if (isForm(item)) {
1997
+ const itle = item.topLevelErrors.value;
1998
+ if (itle.length > 0) out.push({
1999
+ path: itemPath,
2000
+ errors: itle
2001
+ });
2002
+ walkErrors(item.fields, itemPath, out);
2003
+ } else {
2004
+ const errs = item.errors.value;
2005
+ if (errs.length > 0) out.push({
2006
+ path: itemPath,
2007
+ errors: errs
2008
+ });
2009
+ }
2010
+ });
2011
+ } else {
2012
+ const errs = child.errors.value;
2013
+ if (errs.length > 0) out.push({
2014
+ path,
2015
+ errors: errs
2016
+ });
2017
+ }
2018
+ }
2019
+ }
2020
+ var FieldArrayImpl = class {
2021
+ [FIELD_ARRAY_BRAND] = true;
2022
+ items;
2023
+ value;
2024
+ errors;
2025
+ size;
2026
+ isValid;
2027
+ isDirty;
2028
+ touched;
2029
+ isValidating;
2030
+ items$;
2031
+ topLevelErrors$ = signal([]);
2032
+ topLevelErrors = this.topLevelErrors$;
2033
+ topLevelValidating$ = signal(false);
2034
+ itemFactory;
2035
+ initialItems = [];
2036
+ validators;
2037
+ currentValidatorRun = 0;
2038
+ currentValidatorAbort = null;
2039
+ validatorDispose = null;
2040
+ disposed = false;
2041
+ constructor(itemFactory, options) {
2042
+ this.itemFactory = itemFactory;
2043
+ this.validators = options?.validators ?? [];
2044
+ this.items$ = signal([]);
2045
+ if (options?.initial) {
2046
+ this.initialItems = options.initial;
2047
+ for (const ini of options.initial) this.items$.peek().push(itemFactory(ini));
2048
+ this.items$.set([...this.items$.peek()]);
2049
+ }
2050
+ this.items = this.items$;
2051
+ this.size = computed(() => this.items$.value.length);
2052
+ this.value = computed(() => this.items$.value.map((item) => {
2053
+ if (isForm(item)) return item.value.value;
2054
+ return item.value;
2055
+ }));
2056
+ this.errors = computed(() => this.items$.value.map((item) => {
2057
+ if (isForm(item)) return item.errors.value;
2058
+ const errs = item.errors.value;
2059
+ return errs.length > 0 ? errs : void 0;
2060
+ }));
2061
+ this.isDirty = computed(() => {
2062
+ for (const item of this.items$.value) if (item.isDirty.value) return true;
2063
+ return false;
2064
+ });
2065
+ this.touched = computed(() => {
2066
+ for (const item of this.items$.value) if (item.touched.value) return true;
2067
+ return false;
2068
+ });
2069
+ this.isValidating = computed(() => {
2070
+ if (this.topLevelValidating$.value) return true;
2071
+ for (const item of this.items$.value) if (item.isValidating.value) return true;
2072
+ return false;
2073
+ });
2074
+ this.isValid = computed(() => {
2075
+ if (this.topLevelErrors$.value.length > 0) return false;
2076
+ if (this.isValidating.value) return false;
2077
+ for (const item of this.items$.value) if (!item.isValid.value) return false;
2078
+ return true;
2079
+ });
2080
+ if (this.validators.length > 0) this.validatorDispose = effect(() => this.runTopLevelValidators());
2081
+ }
2082
+ at(index) {
2083
+ return this.items$.peek()[index];
2084
+ }
2085
+ add(initial) {
2086
+ if (this.disposed) return;
2087
+ const item = this.itemFactory(initial);
2088
+ this.items$.set([...this.items$.peek(), item]);
2089
+ }
2090
+ insert(index, initial) {
2091
+ if (this.disposed) return;
2092
+ const item = this.itemFactory(initial);
2093
+ const next = [...this.items$.peek()];
2094
+ next.splice(index, 0, item);
2095
+ this.items$.set(next);
2096
+ }
2097
+ remove(index) {
2098
+ if (this.disposed) return;
2099
+ const next = [...this.items$.peek()];
2100
+ const [removed] = next.splice(index, 1);
2101
+ if (removed) removed.dispose?.();
2102
+ this.items$.set(next);
2103
+ }
2104
+ move(from, to) {
2105
+ if (this.disposed) return;
2106
+ const next = [...this.items$.peek()];
2107
+ const [item] = next.splice(from, 1);
2108
+ if (item) next.splice(to, 0, item);
2109
+ this.items$.set(next);
2110
+ }
2111
+ clear() {
2112
+ if (this.disposed) return;
2113
+ for (const item of this.items$.peek()) item.dispose?.();
2114
+ this.items$.set([]);
2115
+ }
2116
+ reset() {
2117
+ if (this.disposed) return;
2118
+ batch(() => {
2119
+ this.clear();
2120
+ for (const ini of this.initialItems) this.add(ini);
2121
+ this.topLevelErrors$.set([]);
2122
+ });
2123
+ }
2124
+ markAllTouched() {
2125
+ for (const item of this.items$.peek()) if (isForm(item)) item.markAllTouched();
2126
+ else item.markTouched();
2127
+ }
2128
+ async validate() {
2129
+ if (this.disposed) return this.isValid.peek();
2130
+ const tasks = [];
2131
+ for (const item of this.items$.peek()) if (isForm(item)) tasks.push(item.validate());
2132
+ else tasks.push(item.revalidate());
2133
+ await Promise.all(tasks);
2134
+ if (this.topLevelValidating$.peek()) await new Promise((resolve) => {
2135
+ const unsub = this.topLevelValidating$.subscribe((v) => {
2136
+ if (!v) {
2137
+ unsub();
2138
+ resolve();
2139
+ }
2140
+ });
2141
+ });
2142
+ return this.isValid.peek();
2143
+ }
2144
+ dispose() {
2145
+ if (this.disposed) return;
2146
+ this.disposed = true;
2147
+ this.validatorDispose?.();
2148
+ this.currentValidatorAbort?.abort();
2149
+ for (const item of this.items$.peek()) item.dispose?.();
2150
+ }
2151
+ runTopLevelValidators() {
2152
+ if (this.disposed) return;
2153
+ const value = this.value.value;
2154
+ this.currentValidatorAbort?.abort();
2155
+ const abort = new AbortController();
2156
+ this.currentValidatorAbort = abort;
2157
+ const myId = ++this.currentValidatorRun;
2158
+ const syncErrors = [];
2159
+ const asyncPromises = [];
2160
+ for (const v of this.validators) {
2161
+ const r = v(value, abort.signal);
2162
+ if (r instanceof Promise) asyncPromises.push(r);
2163
+ else if (r != null) syncErrors.push(r);
2164
+ }
2165
+ if (syncErrors.length > 0) {
2166
+ batch(() => {
2167
+ this.topLevelErrors$.set(syncErrors);
2168
+ this.topLevelValidating$.set(false);
2169
+ });
2170
+ return;
2171
+ }
2172
+ if (asyncPromises.length === 0) {
2173
+ batch(() => {
2174
+ this.topLevelErrors$.set([]);
2175
+ this.topLevelValidating$.set(false);
2176
+ });
2177
+ return;
2178
+ }
2179
+ batch(() => {
2180
+ this.topLevelErrors$.set([]);
2181
+ this.topLevelValidating$.set(true);
2182
+ });
2183
+ Promise.allSettled(asyncPromises).then((results) => {
2184
+ if (myId !== this.currentValidatorRun || this.disposed) return;
2185
+ const errs = [];
2186
+ for (const r of results) if (r.status === "fulfilled" && r.value != null) errs.push(r.value);
2187
+ batch(() => {
2188
+ this.topLevelErrors$.set(errs);
2189
+ this.topLevelValidating$.set(false);
2190
+ });
2191
+ });
2192
+ }
2193
+ };
2194
+ function createForm(schema, options) {
2195
+ return new FormImpl(schema, options);
2196
+ }
2197
+ function createFieldArray(itemFactory, options) {
2198
+ return new FieldArrayImpl(itemFactory, options);
2199
+ }
2200
+ /**
2201
+ * Recursively wire every leaf `Field` in a form / field-array tree to a
2202
+ * devtools emitter. Returns a single disposer that tears down every standalone
2203
+ * `effect()` registered along the way (used for FieldArray watching), so the
2204
+ * caller — `ctx.form` / `ctx.fieldArray` in the controller — can register one
2205
+ * cleanup entry and have the whole subtree's reactive work die with the
2206
+ * controller. Spec §20.9.
2207
+ */
2208
+ function bindTreeToDevtools(node, prefix, controllerPath, emitter) {
2209
+ const disposers = [];
2210
+ bindTreeToDevtoolsInto(node, prefix, controllerPath, emitter, disposers);
2211
+ return () => {
2212
+ for (const d of disposers) try {
2213
+ d();
2214
+ } catch {}
2215
+ disposers.length = 0;
2216
+ };
2217
+ }
2218
+ function bindTreeToDevtoolsInto(node, prefix, controllerPath, emitter, disposers) {
2219
+ if (isForm(node)) {
2220
+ for (const [key, child] of Object.entries(node.fields)) bindTreeToDevtoolsInto(child, prefix === "" ? key : `${prefix}.${key}`, controllerPath, emitter, disposers);
2221
+ return;
2222
+ }
2223
+ if (isFieldArray(node)) {
2224
+ const arr = node;
2225
+ const stop = effect(() => {
2226
+ arr.items.value.forEach((item, idx) => {
2227
+ bindTreeToDevtoolsInto(item, `${prefix}[${idx}]`, controllerPath, emitter, disposers);
2228
+ });
2229
+ });
2230
+ disposers.push(stop);
2231
+ return;
2232
+ }
2233
+ bindFieldDevtoolsOwner(node, {
2234
+ controllerPath,
2235
+ fieldName: prefix,
2236
+ emitter
2237
+ });
2238
+ }
2239
+ //#endregion
2240
+ //#region src/query/local.ts
2241
+ var LocalCacheImpl = class {
2242
+ entry;
2243
+ keyEffectDispose = null;
2244
+ disposed = false;
2245
+ keepPreviousData;
2246
+ lastSucceededFor = null;
2247
+ constructor(fetcher, options) {
2248
+ this.keepPreviousData = options.keepPreviousData ?? false;
2249
+ this.entry = new Entry({
2250
+ fetcher: () => fetcher,
2251
+ staleTime: options.staleTime ?? 0,
2252
+ initialData: options.initialData
2253
+ });
2254
+ if (options.key) {
2255
+ const keyFn = options.key;
2256
+ this.keyEffectDispose = effect(() => {
2257
+ const keyArgs = keyFn();
2258
+ untracked(() => {
2259
+ if (!this.keepPreviousData) {
2260
+ if (this.lastSucceededFor != null && !arraysEqual(this.lastSucceededFor, keyArgs)) this.entry.data.set(void 0);
2261
+ }
2262
+ this.entry.startFetch().then(() => {
2263
+ this.lastSucceededFor = [...keyArgs];
2264
+ }, () => {});
2265
+ });
2266
+ });
2267
+ } else this.entry.startFetch().catch(() => {});
2268
+ }
2269
+ get data() {
2270
+ return this.entry.data;
2271
+ }
2272
+ get error() {
2273
+ return this.entry.error;
2274
+ }
2275
+ get status() {
2276
+ return this.entry.status;
2277
+ }
2278
+ get isLoading() {
2279
+ return this.entry.isLoading;
2280
+ }
2281
+ get isFetching() {
2282
+ return this.entry.isFetching;
2283
+ }
2284
+ get isStale() {
2285
+ return this.entry.isStale;
2286
+ }
2287
+ get lastUpdatedAt() {
2288
+ return this.entry.lastUpdatedAt;
2289
+ }
2290
+ get hasPendingMutations() {
2291
+ return this.entry.hasPendingMutations;
2292
+ }
2293
+ refetch = () => this.entry.refetch();
2294
+ reset = () => this.entry.reset();
2295
+ firstValue = () => this.entry.firstValue();
2296
+ invalidate = () => {
2297
+ this.entry.invalidate().catch(() => {});
2298
+ };
2299
+ setData = (updater) => this.entry.setData(updater);
2300
+ dispose() {
2301
+ if (this.disposed) return;
2302
+ this.disposed = true;
2303
+ this.keyEffectDispose?.();
2304
+ this.keyEffectDispose = null;
2305
+ this.entry.dispose();
2306
+ }
2307
+ };
2308
+ function createLocalCache(fetcher, options) {
2309
+ return new LocalCacheImpl(fetcher, options ?? {});
2310
+ }
2311
+ function arraysEqual(a, b) {
2312
+ if (a.length !== b.length) return false;
2313
+ for (let i = 0; i < a.length; i++) if (!Object.is(a[i], b[i])) return false;
2314
+ return true;
2315
+ }
2316
+ //#endregion
2317
+ //#region src/query/mutation.ts
2318
+ var MutationImpl = class {
2319
+ spec;
2320
+ onError;
2321
+ controllerPath;
2322
+ inflightCounter;
2323
+ devtools;
2324
+ data = signal(void 0);
2325
+ error = signal(void 0);
2326
+ isPending = signal(false);
2327
+ lastVariables = signal(void 0);
2328
+ inflight = /* @__PURE__ */ new Set();
2329
+ serialQueue = [];
2330
+ serialActive = false;
2331
+ disposed = false;
2332
+ constructor(spec, onError, controllerPath, inflightCounter, devtools) {
2333
+ this.spec = spec;
2334
+ this.onError = onError;
2335
+ this.controllerPath = controllerPath;
2336
+ this.inflightCounter = inflightCounter;
2337
+ this.devtools = devtools;
2338
+ }
2339
+ emit(event) {}
2340
+ run = ((vars = void 0) => {
2341
+ if (this.disposed) return Promise.reject(/* @__PURE__ */ new Error("Mutation disposed"));
2342
+ switch (this.spec.concurrency ?? "parallel") {
2343
+ case "parallel": return this.executeRun(vars);
2344
+ case "latest-wins":
2345
+ for (const handle of this.inflight) {
2346
+ handle.abort.abort();
2347
+ handle.snapshot?.rollback();
2348
+ handle.snapshot = void 0;
2349
+ }
2350
+ return this.executeRun(vars);
2351
+ case "serial": return this.enqueueSerial(vars);
2352
+ }
2353
+ });
2354
+ enqueueSerial(vars) {
2355
+ if (this.serialActive) return new Promise((resolve, reject) => {
2356
+ this.serialQueue.push({
2357
+ vars,
2358
+ resolve,
2359
+ reject
2360
+ });
2361
+ });
2362
+ this.serialActive = true;
2363
+ return this.executeRun(vars).finally(() => this.advanceSerialQueue());
2364
+ }
2365
+ advanceSerialQueue() {
2366
+ const next = this.serialQueue.shift();
2367
+ if (!next) {
2368
+ this.serialActive = false;
2369
+ return;
2370
+ }
2371
+ this.executeRun(next.vars).then((result) => {
2372
+ next.resolve(result);
2373
+ this.advanceSerialQueue();
2374
+ }, (err) => {
2375
+ next.reject(err);
2376
+ this.advanceSerialQueue();
2377
+ });
2378
+ }
2379
+ async executeRun(vars) {
2380
+ const abort = new AbortController();
2381
+ let snapshot;
2382
+ try {
2383
+ const raw = this.spec.onMutate?.(vars) ?? void 0;
2384
+ snapshot = raw === void 0 ? void 0 : this.wrapSnapshot(raw);
2385
+ } catch (err) {
2386
+ dispatchError(this.onError, err, {
2387
+ kind: "mutation",
2388
+ controllerPath: this.controllerPath
2389
+ });
2390
+ }
2391
+ const handle = {
2392
+ abort,
2393
+ snapshot
2394
+ };
2395
+ this.inflight.add(handle);
2396
+ this.inflightCounter?.update((n) => n + 1);
2397
+ batch(() => {
2398
+ this.isPending.set(true);
2399
+ this.lastVariables.set(vars);
2400
+ });
2401
+ try {
2402
+ const result = await raceAbort(this.runWithRetry(vars, abort.signal), abort.signal);
2403
+ if (abort.signal.aborted || this.disposed) {
2404
+ snapshot?.rollback();
2405
+ throw new DOMException("Superseded", "AbortError");
2406
+ }
2407
+ batch(() => {
2408
+ this.data.set(result);
2409
+ this.error.set(void 0);
2410
+ });
2411
+ this.safeCall(() => this.spec.onSuccess?.(result, vars), "mutation");
2412
+ snapshot?.finalize();
2413
+ this.safeCall(() => this.spec.onSettled?.(result, void 0, vars), "mutation");
2414
+ return result;
2415
+ } catch (err) {
2416
+ if (isAbortError(err) || abort.signal.aborted) {
2417
+ snapshot?.rollback();
2418
+ throw err;
2419
+ }
2420
+ this.error.set(err);
2421
+ this.safeCall(() => this.spec.onError?.(err, vars, snapshot), "mutation");
2422
+ snapshot?.rollback();
2423
+ this.safeCall(() => this.spec.onSettled?.(void 0, err, vars), "mutation");
2424
+ throw err;
2425
+ } finally {
2426
+ this.inflight.delete(handle);
2427
+ this.inflightCounter?.update((n) => Math.max(0, n - 1));
2428
+ if (this.inflight.size === 0) this.isPending.set(false);
2429
+ }
2430
+ }
2431
+ wrapSnapshot(raw) {
2432
+ let consumed = false;
2433
+ return {
2434
+ rollback: () => {
2435
+ if (consumed) return;
2436
+ consumed = true;
2437
+ raw.rollback();
2438
+ },
2439
+ finalize: () => {
2440
+ if (consumed) return;
2441
+ consumed = true;
2442
+ raw.finalize();
2443
+ }
2444
+ };
2445
+ }
2446
+ async runWithRetry(vars, signal) {
2447
+ const retry = this.spec.retry ?? 0;
2448
+ const retryDelay = this.spec.retryDelay ?? 1e3;
2449
+ let attempt = 0;
2450
+ while (true) try {
2451
+ return await this.spec.mutate(vars, signal);
2452
+ } catch (err) {
2453
+ if (signal.aborted || isAbortError(err)) throw err;
2454
+ if (!(typeof retry === "number" ? attempt < retry : retry(attempt, err))) throw err;
2455
+ await abortableSleep(typeof retryDelay === "function" ? retryDelay(attempt) : retryDelay, signal);
2456
+ attempt += 1;
2457
+ }
2458
+ }
2459
+ safeCall(fn, kind) {
2460
+ try {
2461
+ fn();
2462
+ } catch (err) {
2463
+ dispatchError(this.onError, err, {
2464
+ kind,
2465
+ controllerPath: this.controllerPath
2466
+ });
2467
+ }
2468
+ }
2469
+ reset() {
2470
+ if (this.disposed) return;
2471
+ for (const handle of this.inflight) handle.abort.abort();
2472
+ this.serialQueue.length = 0;
2473
+ batch(() => {
2474
+ this.data.set(void 0);
2475
+ this.error.set(void 0);
2476
+ this.lastVariables.set(void 0);
2477
+ this.isPending.set(false);
2478
+ });
2479
+ }
2480
+ dispose() {
2481
+ if (this.disposed) return;
2482
+ this.disposed = true;
2483
+ for (const handle of this.inflight) handle.abort.abort();
2484
+ for (const queued of this.serialQueue) queued.reject(new DOMException("Disposed", "AbortError"));
2485
+ this.serialQueue.length = 0;
2486
+ }
2487
+ };
2488
+ function createMutation(spec, onError, controllerPath, inflightCounter, devtools) {
2489
+ return new MutationImpl(spec, onError, controllerPath, inflightCounter, devtools);
2490
+ }
2491
+ /**
2492
+ * Race a promise against an AbortSignal. If the signal fires before the
2493
+ * promise settles, the returned promise rejects with AbortError — regardless
2494
+ * of whether the underlying promise ever resolves. Protects against
2495
+ * misbehaving mutate fns that ignore their signal.
2496
+ */
2497
+ function raceAbort(promise, signal) {
2498
+ if (signal.aborted) return Promise.reject(new DOMException("Aborted", "AbortError"));
2499
+ return new Promise((resolve, reject) => {
2500
+ let settled = false;
2501
+ const onAbort = () => {
2502
+ if (settled) return;
2503
+ settled = true;
2504
+ reject(new DOMException("Aborted", "AbortError"));
2505
+ };
2506
+ signal.addEventListener("abort", onAbort, { once: true });
2507
+ promise.then((v) => {
2508
+ if (settled) return;
2509
+ settled = true;
2510
+ signal.removeEventListener("abort", onAbort);
2511
+ resolve(v);
2512
+ }, (e) => {
2513
+ if (settled) return;
2514
+ settled = true;
2515
+ signal.removeEventListener("abort", onAbort);
2516
+ reject(e);
2517
+ });
2518
+ });
2519
+ }
2520
+ function abortableSleep(ms, signal) {
2521
+ return new Promise((resolve, reject) => {
2522
+ if (signal.aborted) {
2523
+ reject(new DOMException("Aborted", "AbortError"));
2524
+ return;
2525
+ }
2526
+ const timer = setTimeout(() => {
2527
+ signal.removeEventListener("abort", onAbort);
2528
+ resolve();
2529
+ }, ms);
2530
+ const onAbort = () => {
2531
+ clearTimeout(timer);
2532
+ signal.removeEventListener("abort", onAbort);
2533
+ reject(new DOMException("Aborted", "AbortError"));
2534
+ };
2535
+ signal.addEventListener("abort", onAbort, { once: true });
2536
+ });
2537
+ }
2538
+ //#endregion
2539
+ //#region src/query/use.ts
2540
+ var SubscriptionImpl = class {
2541
+ keepPreviousData;
2542
+ current$ = signal(null);
2543
+ previousData$ = signal(void 0);
2544
+ data;
2545
+ error;
2546
+ status;
2547
+ isLoading;
2548
+ isFetching;
2549
+ isStale;
2550
+ lastUpdatedAt;
2551
+ hasPendingMutations;
2552
+ constructor(keepPreviousData) {
2553
+ this.keepPreviousData = keepPreviousData;
2554
+ this.data = computed(() => {
2555
+ const curData = this.current$.value?.entry.data.value;
2556
+ if (curData !== void 0) return curData;
2557
+ if (keepPreviousData) return this.previousData$.value;
2558
+ });
2559
+ this.error = computed(() => this.current$.value?.entry.error.value);
2560
+ this.status = computed(() => this.current$.value?.entry.status.value ?? "idle");
2561
+ this.isLoading = computed(() => {
2562
+ const cur = this.current$.value;
2563
+ if (!cur) return false;
2564
+ if (keepPreviousData && this.previousData$.value !== void 0) return false;
2565
+ return cur.entry.isLoading.value;
2566
+ });
2567
+ this.isFetching = computed(() => this.current$.value?.entry.isFetching.value ?? false);
2568
+ this.isStale = computed(() => this.current$.value?.entry.isStale.value ?? true);
2569
+ this.lastUpdatedAt = computed(() => this.current$.value?.entry.lastUpdatedAt.value);
2570
+ this.hasPendingMutations = computed(() => this.current$.value?.entry.hasPendingMutations.value ?? false);
2571
+ }
2572
+ attach(entry) {
2573
+ const prev = this.current$.peek();
2574
+ if (prev === entry) return;
2575
+ if (prev && this.keepPreviousData) {
2576
+ const prevData = prev.entry.data.peek();
2577
+ if (prevData !== void 0) this.previousData$.set(prevData);
2578
+ }
2579
+ this.current$.set(entry);
2580
+ }
2581
+ detach() {
2582
+ this.current$.set(null);
2583
+ }
2584
+ refetch = () => {
2585
+ const cur = this.current$.peek();
2586
+ if (!cur) return Promise.reject(/* @__PURE__ */ new Error("[olas] no active subscription"));
2587
+ return cur.entry.refetch();
2588
+ };
2589
+ reset = () => {
2590
+ this.current$.peek()?.entry.reset();
2591
+ };
2592
+ firstValue = () => {
2593
+ const cur = this.current$.peek();
2594
+ if (!cur) return Promise.reject(/* @__PURE__ */ new Error("[olas] no active subscription"));
2595
+ return cur.entry.firstValue();
2596
+ };
2597
+ };
2598
+ /**
2599
+ * Build a subscription + the effect that keeps it bound to the right entry.
2600
+ * The controller container wires the disposer into the lifecycle.
2601
+ */
2602
+ function createUse(client, query, keyOrOptions) {
2603
+ const keepPreviousData = query.__spec.keepPreviousData ?? false;
2604
+ const keyFn = typeof keyOrOptions === "function" ? keyOrOptions : keyOrOptions?.key;
2605
+ const enabledFn = typeof keyOrOptions === "object" && keyOrOptions !== null ? keyOrOptions.enabled : void 0;
2606
+ const sub = new SubscriptionImpl(keepPreviousData);
2607
+ let currentEntry = null;
2608
+ const effectDispose = effect(() => {
2609
+ if (!(enabledFn ? enabledFn() : true)) {
2610
+ untracked(() => {
2611
+ if (currentEntry) {
2612
+ currentEntry.release();
2613
+ currentEntry = null;
2614
+ }
2615
+ sub.detach();
2616
+ });
2617
+ return;
2618
+ }
2619
+ const args = keyFn ? keyFn() : [];
2620
+ untracked(() => {
2621
+ const entry = client.bindEntry(query, args);
2622
+ if (currentEntry === entry) return;
2623
+ if (currentEntry) currentEntry.release();
2624
+ entry.acquire();
2625
+ currentEntry = entry;
2626
+ sub.attach(entry);
2627
+ const status = entry.entry.status.peek();
2628
+ if (!entry.entry.isFetching.peek() && (status === "idle" || entry.entry.isStaleNow() || status === "error")) entry.entry.startFetch().catch(() => {});
2629
+ });
2630
+ });
2631
+ const dispose = () => {
2632
+ effectDispose();
2633
+ if (currentEntry) {
2634
+ currentEntry.release();
2635
+ currentEntry = null;
2636
+ }
2637
+ sub.detach();
2638
+ };
2639
+ return {
2640
+ subscription: sub,
2641
+ dispose
2642
+ };
2643
+ }
2644
+ var InfiniteSubscriptionImpl = class {
2645
+ keepPreviousData;
2646
+ current$ = signal(null);
2647
+ previousPages$ = signal(void 0);
2648
+ data;
2649
+ pages;
2650
+ flat;
2651
+ error;
2652
+ status;
2653
+ isLoading;
2654
+ isFetching;
2655
+ isStale;
2656
+ lastUpdatedAt;
2657
+ hasPendingMutations;
2658
+ hasNextPage;
2659
+ hasPreviousPage;
2660
+ isFetchingNextPage;
2661
+ isFetchingPreviousPage;
2662
+ constructor(keepPreviousData) {
2663
+ this.keepPreviousData = keepPreviousData;
2664
+ this.pages = computed(() => {
2665
+ const ps = this.current$.value?.entry.pages.value;
2666
+ if (ps && ps.length > 0) return ps;
2667
+ if (keepPreviousData) return this.previousPages$.value ?? [];
2668
+ return ps ?? [];
2669
+ });
2670
+ this.data = computed(() => {
2671
+ const ps = this.current$.value?.entry.pages.value;
2672
+ if (ps && ps.length > 0) return ps;
2673
+ if (keepPreviousData) {
2674
+ const prev = this.previousPages$.value;
2675
+ if (prev && prev.length > 0) return prev;
2676
+ }
2677
+ });
2678
+ this.flat = computed(() => this.current$.value?.entry.flat.value ?? []);
2679
+ this.error = computed(() => this.current$.value?.entry.error.value);
2680
+ this.status = computed(() => this.current$.value?.entry.status.value ?? "idle");
2681
+ this.isLoading = computed(() => {
2682
+ const cur = this.current$.value;
2683
+ if (!cur) return false;
2684
+ if (keepPreviousData) {
2685
+ const prev = this.previousPages$.value;
2686
+ if (prev && prev.length > 0) return false;
2687
+ }
2688
+ return cur.entry.isLoading.value;
2689
+ });
2690
+ this.isFetching = computed(() => this.current$.value?.entry.isFetching.value ?? false);
2691
+ this.isStale = computed(() => this.current$.value?.entry.isStale.value ?? true);
2692
+ this.lastUpdatedAt = computed(() => this.current$.value?.entry.lastUpdatedAt.value);
2693
+ this.hasPendingMutations = computed(() => this.current$.value?.entry.hasPendingMutations.value ?? false);
2694
+ this.hasNextPage = computed(() => this.current$.value?.entry.hasNextPage.value ?? false);
2695
+ this.hasPreviousPage = computed(() => this.current$.value?.entry.hasPreviousPage.value ?? false);
2696
+ this.isFetchingNextPage = computed(() => this.current$.value?.entry.isFetchingNextPage.value ?? false);
2697
+ this.isFetchingPreviousPage = computed(() => this.current$.value?.entry.isFetchingPreviousPage.value ?? false);
2698
+ }
2699
+ attach(entry) {
2700
+ const prev = this.current$.peek();
2701
+ if (prev === entry) return;
2702
+ if (prev && this.keepPreviousData) {
2703
+ const prevPages = prev.entry.pages.peek();
2704
+ if (prevPages.length > 0) this.previousPages$.set(prevPages);
2705
+ }
2706
+ this.current$.set(entry);
2707
+ }
2708
+ detach() {
2709
+ this.current$.set(null);
2710
+ }
2711
+ refetch = () => {
2712
+ const cur = this.current$.peek();
2713
+ if (!cur) return Promise.reject(/* @__PURE__ */ new Error("[olas] no active subscription"));
2714
+ return cur.entry.refetch().then(() => cur.entry.pages.peek());
2715
+ };
2716
+ reset = () => {
2717
+ this.current$.peek()?.entry.reset();
2718
+ };
2719
+ firstValue = () => {
2720
+ const cur = this.current$.peek();
2721
+ if (!cur) return Promise.reject(/* @__PURE__ */ new Error("[olas] no active subscription"));
2722
+ return cur.entry.firstValue();
2723
+ };
2724
+ fetchNextPage = () => {
2725
+ const cur = this.current$.peek();
2726
+ if (!cur) return Promise.resolve();
2727
+ return cur.entry.fetchNextPage();
2728
+ };
2729
+ fetchPreviousPage = () => {
2730
+ const cur = this.current$.peek();
2731
+ if (!cur) return Promise.resolve();
2732
+ return cur.entry.fetchPreviousPage();
2733
+ };
2734
+ };
2735
+ function createInfiniteUse(client, query, keyOrOptions) {
2736
+ const keepPreviousData = query.__spec.keepPreviousData ?? false;
2737
+ const keyFn = typeof keyOrOptions === "function" ? keyOrOptions : keyOrOptions?.key;
2738
+ const enabledFn = typeof keyOrOptions === "object" && keyOrOptions !== null ? keyOrOptions.enabled : void 0;
2739
+ const sub = new InfiniteSubscriptionImpl(keepPreviousData);
2740
+ let currentEntry = null;
2741
+ const effectDispose = effect(() => {
2742
+ if (!(enabledFn ? enabledFn() : true)) {
2743
+ untracked(() => {
2744
+ if (currentEntry) {
2745
+ currentEntry.release();
2746
+ currentEntry = null;
2747
+ }
2748
+ sub.detach();
2749
+ });
2750
+ return;
2751
+ }
2752
+ const args = keyFn ? keyFn() : [];
2753
+ untracked(() => {
2754
+ const entry = client.bindInfiniteEntry(query, args);
2755
+ if (currentEntry === entry) return;
2756
+ if (currentEntry) currentEntry.release();
2757
+ entry.acquire();
2758
+ currentEntry = entry;
2759
+ sub.attach(entry);
2760
+ const status = entry.entry.status.peek();
2761
+ if (!entry.entry.isFetching.peek() && (status === "idle" || entry.entry.isStaleNow() || status === "error")) entry.entry.startFetch().catch(() => {});
2762
+ });
2763
+ });
2764
+ const dispose = () => {
2765
+ effectDispose();
2766
+ if (currentEntry) {
2767
+ currentEntry.release();
2768
+ currentEntry = null;
2769
+ }
2770
+ sub.detach();
2771
+ };
2772
+ return {
2773
+ subscription: sub,
2774
+ dispose
2775
+ };
2776
+ }
2777
+ //#endregion
2778
+ //#region src/controller/instance.ts
2779
+ var ControllerInstance = class ControllerInstance {
2780
+ path;
2781
+ deps;
2782
+ state = "constructing";
2783
+ entries = [];
2784
+ rootShared;
2785
+ parent;
2786
+ childCounter = 0;
2787
+ /** Scope values provided on this instance, keyed by `Scope.__id`. */
2788
+ scopes = null;
2789
+ constructor(parent, rootShared, pathSegment, deps) {
2790
+ this.parent = parent;
2791
+ this.rootShared = rootShared;
2792
+ this.path = parent ? [...parent.path, pathSegment] : [pathSegment];
2793
+ this.deps = deps;
2794
+ }
2795
+ /**
2796
+ * Run the factory and produce an api. On throw, the partially-constructed
2797
+ * state is rolled back (entries disposed in reverse) and the error is rethrown.
2798
+ */
2799
+ construct(factory, props) {
2800
+ const ctx = this.buildCtx();
2801
+ let api;
2802
+ try {
2803
+ api = factory(ctx, props);
2804
+ } catch (err) {
2805
+ this.rollbackPartialConstruction();
2806
+ throw err;
2807
+ }
2808
+ this.state = "active";
2809
+ return api;
2810
+ }
2811
+ rollbackPartialConstruction() {
2812
+ for (let i = this.entries.length - 1; i >= 0; i--) {
2813
+ const entry = this.entries[i];
2814
+ if (!entry) continue;
2815
+ try {
2816
+ this.disposeEntry(entry);
2817
+ } catch {}
2818
+ }
2819
+ this.entries.length = 0;
2820
+ this.state = "disposed";
2821
+ }
2822
+ dispose() {
2823
+ if (this.state === "disposed") return;
2824
+ this.state;
2825
+ this.state = "disposed";
2826
+ for (let i = this.entries.length - 1; i >= 0; i--) {
2827
+ const entry = this.entries[i];
2828
+ if (!entry) continue;
2829
+ try {
2830
+ this.disposeEntry(entry);
2831
+ } catch (err) {
2832
+ dispatchError(this.rootShared.onError, err, {
2833
+ kind: "effect",
2834
+ controllerPath: this.path
2835
+ });
2836
+ }
2837
+ }
2838
+ this.entries.length = 0;
2839
+ this.scopes = null;
2840
+ }
2841
+ disposeEntry(entry) {
2842
+ switch (entry.kind) {
2843
+ case "effect":
2844
+ entry.dispose?.();
2845
+ entry.dispose = null;
2846
+ break;
2847
+ case "cleanup":
2848
+ entry.dispose();
2849
+ break;
2850
+ case "child":
2851
+ entry.instance.dispose();
2852
+ break;
2853
+ case "subscription":
2854
+ entry.unsubscribe();
2855
+ break;
2856
+ case "onDispose":
2857
+ entry.fn();
2858
+ break;
2859
+ case "onSuspend":
2860
+ case "onResume": break;
2861
+ }
2862
+ }
2863
+ suspend() {
2864
+ if (this.state !== "active") return;
2865
+ this.state = "suspended";
2866
+ for (let i = this.entries.length - 1; i >= 0; i--) {
2867
+ const entry = this.entries[i];
2868
+ if (!entry) continue;
2869
+ try {
2870
+ switch (entry.kind) {
2871
+ case "effect":
2872
+ entry.dispose?.();
2873
+ entry.dispose = null;
2874
+ break;
2875
+ case "child":
2876
+ entry.instance.suspend();
2877
+ break;
2878
+ case "onSuspend":
2879
+ entry.fn();
2880
+ break;
2881
+ default: break;
2882
+ }
2883
+ } catch (err) {
2884
+ dispatchError(this.rootShared.onError, err, {
2885
+ kind: "effect",
2886
+ controllerPath: this.path
2887
+ });
2888
+ }
2889
+ }
2890
+ }
2891
+ resume() {
2892
+ if (this.state !== "suspended") return;
2893
+ this.state = "active";
2894
+ for (const entry of this.entries) try {
2895
+ switch (entry.kind) {
2896
+ case "effect":
2897
+ entry.dispose = effect(entry.factory);
2898
+ break;
2899
+ case "child":
2900
+ entry.instance.resume();
2901
+ break;
2902
+ case "onResume":
2903
+ entry.fn();
2904
+ break;
2905
+ default: break;
2906
+ }
2907
+ } catch (err) {
2908
+ dispatchError(this.rootShared.onError, err, {
2909
+ kind: "effect",
2910
+ controllerPath: this.path
2911
+ });
2912
+ }
2913
+ }
2914
+ buildCtx() {
2915
+ const self = this;
2916
+ return {
2917
+ get deps() {
2918
+ return self.deps;
2919
+ },
2920
+ effect(fn) {
2921
+ if (self.isTerminal()) return;
2922
+ const entry = {
2923
+ kind: "effect",
2924
+ factory: () => fn(),
2925
+ dispose: null
2926
+ };
2927
+ const wrapped = () => {
2928
+ try {
2929
+ return fn();
2930
+ } catch (err) {
2931
+ dispatchError(self.rootShared.onError, err, {
2932
+ kind: "effect",
2933
+ controllerPath: self.path
2934
+ });
2935
+ return;
2936
+ }
2937
+ };
2938
+ entry.factory = wrapped;
2939
+ entry.dispose = effect(wrapped);
2940
+ self.entries.push(entry);
2941
+ },
2942
+ cache(fetcher, options) {
2943
+ const cache = createLocalCache(fetcher, options);
2944
+ self.entries.push({
2945
+ kind: "cleanup",
2946
+ dispose: () => cache.dispose()
2947
+ });
2948
+ return cache;
2949
+ },
2950
+ use(query, keyOrOptions) {
2951
+ if (query.__olas === "infiniteQuery") {
2952
+ const { subscription, dispose: d } = createInfiniteUse(self.rootShared.queryClient, query, keyOrOptions);
2953
+ self.entries.push({
2954
+ kind: "cleanup",
2955
+ dispose: d
2956
+ });
2957
+ return subscription;
2958
+ }
2959
+ const { subscription, dispose: d } = createUse(self.rootShared.queryClient, query, keyOrOptions);
2960
+ self.entries.push({
2961
+ kind: "cleanup",
2962
+ dispose: d
2963
+ });
2964
+ return subscription;
2965
+ },
2966
+ mutation(spec) {
2967
+ const m = createMutation(spec, self.rootShared.onError, self.path, self.rootShared.queryClient.mutationsInflight$, self.rootShared.devtools);
2968
+ self.entries.push({
2969
+ kind: "cleanup",
2970
+ dispose: () => m.dispose()
2971
+ });
2972
+ return m;
2973
+ },
2974
+ emitter() {
2975
+ const e = createEmitter();
2976
+ self.entries.push({
2977
+ kind: "cleanup",
2978
+ dispose: () => e.dispose()
2979
+ });
2980
+ return e;
2981
+ },
2982
+ field(initial, validators) {
2983
+ const f = createField(initial, validators);
2984
+ self.entries.push({
2985
+ kind: "cleanup",
2986
+ dispose: () => f.dispose()
2987
+ });
2988
+ bindFieldDevtoolsOwner(f, {
2989
+ controllerPath: self.path,
2990
+ fieldName: "(field)",
2991
+ emitter: self.rootShared.devtools
2992
+ });
2993
+ return f;
2994
+ },
2995
+ form(schema, options) {
2996
+ const f = createForm(schema, options);
2997
+ self.entries.push({
2998
+ kind: "cleanup",
2999
+ dispose: () => f.dispose()
3000
+ });
3001
+ const stop = bindTreeToDevtools(f, "", self.path, self.rootShared.devtools);
3002
+ self.entries.push({
3003
+ kind: "cleanup",
3004
+ dispose: stop
3005
+ });
3006
+ return f;
3007
+ },
3008
+ fieldArray(itemFactory, options) {
3009
+ const fa = createFieldArray(itemFactory, options);
3010
+ self.entries.push({
3011
+ kind: "cleanup",
3012
+ dispose: () => fa.dispose()
3013
+ });
3014
+ const stop = bindTreeToDevtools(fa, "", self.path, self.rootShared.devtools);
3015
+ self.entries.push({
3016
+ kind: "cleanup",
3017
+ dispose: stop
3018
+ });
3019
+ return fa;
3020
+ },
3021
+ provide(scope, value) {
3022
+ if (self.scopes === null) self.scopes = /* @__PURE__ */ new Map();
3023
+ self.scopes.set(scope.__id, value);
3024
+ },
3025
+ inject(scope) {
3026
+ let node = self;
3027
+ while (node !== null) {
3028
+ const map = node.scopes;
3029
+ if (map?.has(scope.__id)) return map.get(scope.__id);
3030
+ node = node.parent;
3031
+ }
3032
+ if (scope.hasDefault) return scope.default;
3033
+ const label = scope.name ?? scope.__id.description ?? "unnamed";
3034
+ throw new Error(`[olas] ctx.inject(): no provider for scope '${label}' and no default. Provide it on an ancestor via ctx.provide(${label}, ...) or pass a default to defineScope.`);
3035
+ },
3036
+ on(emitter, handler) {
3037
+ const wrapped = (value) => {
3038
+ try {
3039
+ handler(value);
3040
+ } catch (err) {
3041
+ dispatchError(self.rootShared.onError, err, {
3042
+ kind: "emitter",
3043
+ controllerPath: self.path
3044
+ });
3045
+ }
3046
+ };
3047
+ const unsubscribe = emitter.on(wrapped);
3048
+ self.entries.push({
3049
+ kind: "subscription",
3050
+ unsubscribe
3051
+ });
3052
+ },
3053
+ child(def, props, options) {
3054
+ const segment = self.makeChildSegment(getFactory(def), getName(def));
3055
+ const override = options?.deps;
3056
+ const childDeps = override !== void 0 ? {
3057
+ ...self.deps,
3058
+ ...override
3059
+ } : self.deps;
3060
+ const childInstance = new ControllerInstance(self, self.rootShared, segment, childDeps);
3061
+ const api = childInstance.construct(getFactory(def), props);
3062
+ self.entries.push({
3063
+ kind: "child",
3064
+ instance: childInstance
3065
+ });
3066
+ return api;
3067
+ },
3068
+ attach(def, props, options) {
3069
+ const segment = self.makeChildSegment(getFactory(def), getName(def));
3070
+ const override = options?.deps;
3071
+ const childDeps = override !== void 0 ? {
3072
+ ...self.deps,
3073
+ ...override
3074
+ } : self.deps;
3075
+ const childInstance = new ControllerInstance(self, self.rootShared, segment, childDeps);
3076
+ const api = childInstance.construct(getFactory(def), props);
3077
+ const entry = {
3078
+ kind: "child",
3079
+ instance: childInstance
3080
+ };
3081
+ self.entries.push(entry);
3082
+ let disposed = false;
3083
+ return {
3084
+ api,
3085
+ dispose: () => {
3086
+ if (disposed) return;
3087
+ disposed = true;
3088
+ const idx = self.entries.indexOf(entry);
3089
+ if (idx >= 0) self.entries.splice(idx, 1);
3090
+ try {
3091
+ childInstance.dispose();
3092
+ } catch (err) {
3093
+ dispatchError(self.rootShared.onError, err, {
3094
+ kind: "effect",
3095
+ controllerPath: self.path
3096
+ });
3097
+ }
3098
+ }
3099
+ };
3100
+ },
3101
+ onDispose(fn) {
3102
+ self.entries.push({
3103
+ kind: "onDispose",
3104
+ fn: () => {
3105
+ try {
3106
+ fn();
3107
+ } catch (err) {
3108
+ dispatchError(self.rootShared.onError, err, {
3109
+ kind: "effect",
3110
+ controllerPath: self.path
3111
+ });
3112
+ }
3113
+ }
3114
+ });
3115
+ },
3116
+ onSuspend(fn) {
3117
+ self.entries.push({
3118
+ kind: "onSuspend",
3119
+ fn: () => {
3120
+ try {
3121
+ fn();
3122
+ } catch (err) {
3123
+ dispatchError(self.rootShared.onError, err, {
3124
+ kind: "effect",
3125
+ controllerPath: self.path
3126
+ });
3127
+ }
3128
+ }
3129
+ });
3130
+ },
3131
+ onResume(fn) {
3132
+ self.entries.push({
3133
+ kind: "onResume",
3134
+ fn: () => {
3135
+ try {
3136
+ fn();
3137
+ } catch (err) {
3138
+ dispatchError(self.rootShared.onError, err, {
3139
+ kind: "effect",
3140
+ controllerPath: self.path
3141
+ });
3142
+ }
3143
+ }
3144
+ });
3145
+ }
3146
+ };
3147
+ }
3148
+ isTerminal() {
3149
+ return this.state === "disposed";
3150
+ }
3151
+ makeChildSegment(factory, explicitName) {
3152
+ const idx = this.childCounter++;
3153
+ const base = explicitName ?? factory.name ?? "";
3154
+ return `${base !== "" ? base : "anonymous"}[${idx}]`;
3155
+ }
3156
+ };
3157
+ //#endregion
3158
+ //#region src/controller/root.ts
3159
+ const ROOT_METHODS = [
3160
+ "dispose",
3161
+ "suspend",
3162
+ "resume",
3163
+ "dehydrate",
3164
+ "waitForIdle",
3165
+ "__debug"
3166
+ ];
3167
+ /**
3168
+ * Construct a root controller.
3169
+ *
3170
+ * Internal: this is the shared engine. The public `createRoot` (props-less)
3171
+ * and `createTestController` (props-allowing) both call through here.
3172
+ */
3173
+ function createRootWithProps(def, props, options) {
3174
+ const devtools = new DevtoolsEmitter();
3175
+ const queryClient = new QueryClient({
3176
+ onError: options.onError,
3177
+ hydrate: options.hydrate,
3178
+ devtools,
3179
+ deps: options.deps,
3180
+ refetchOnWindowFocus: options.refetchOnWindowFocus,
3181
+ refetchOnReconnect: options.refetchOnReconnect,
3182
+ plugins: options.plugins
3183
+ });
3184
+ const instance = new ControllerInstance(null, {
3185
+ devtools,
3186
+ onError: options.onError,
3187
+ queryClient
3188
+ }, "root", options.deps);
3189
+ const api = instance.construct(getFactory(def), props);
3190
+ if (typeof api !== "object" || api === null) return attachRootControls({ value: api }, instance, devtools, queryClient);
3191
+ return attachRootControls(api, instance, devtools, queryClient);
3192
+ }
3193
+ function attachRootControls(api, instance, devtools, queryClient) {
3194
+ let suspendTimer = null;
3195
+ const dispose = () => {
3196
+ if (suspendTimer != null) {
3197
+ clearTimeout(suspendTimer);
3198
+ suspendTimer = null;
3199
+ }
3200
+ instance.dispose();
3201
+ queryClient.dispose();
3202
+ };
3203
+ const suspend = (opts) => {
3204
+ instance.suspend();
3205
+ if (suspendTimer != null) {
3206
+ clearTimeout(suspendTimer);
3207
+ suspendTimer = null;
3208
+ }
3209
+ const maxIdle = opts?.maxIdle;
3210
+ if (maxIdle != null && maxIdle !== Number.POSITIVE_INFINITY) suspendTimer = setTimeout(() => {
3211
+ suspendTimer = null;
3212
+ dispose();
3213
+ }, maxIdle);
3214
+ };
3215
+ const resume = () => {
3216
+ if (suspendTimer != null) {
3217
+ clearTimeout(suspendTimer);
3218
+ suspendTimer = null;
3219
+ }
3220
+ instance.resume();
3221
+ };
3222
+ const debug = {
3223
+ subscribe: (handler) => devtools.subscribe(handler),
3224
+ queryEntries: () => queryClient.queryEntriesSnapshot()
3225
+ };
3226
+ const target = api;
3227
+ for (const method of ROOT_METHODS) if (Object.hasOwn(target, method)) throw new Error(`[olas] Root controller api defines '${method}' which conflicts with the root controls.`);
3228
+ Object.defineProperty(target, "dispose", {
3229
+ value: dispose,
3230
+ enumerable: false,
3231
+ configurable: true
3232
+ });
3233
+ Object.defineProperty(target, "suspend", {
3234
+ value: suspend,
3235
+ enumerable: false,
3236
+ configurable: true
3237
+ });
3238
+ Object.defineProperty(target, "resume", {
3239
+ value: resume,
3240
+ enumerable: false,
3241
+ configurable: true
3242
+ });
3243
+ Object.defineProperty(target, "__debug", {
3244
+ value: debug,
3245
+ enumerable: false,
3246
+ configurable: true
3247
+ });
3248
+ Object.defineProperty(target, "dehydrate", {
3249
+ value: () => queryClient.dehydrate(),
3250
+ enumerable: false,
3251
+ configurable: true
3252
+ });
3253
+ Object.defineProperty(target, "waitForIdle", {
3254
+ value: () => queryClient.waitForIdle(),
3255
+ enumerable: false,
3256
+ configurable: true
3257
+ });
3258
+ return api;
3259
+ }
3260
+ /**
3261
+ * Construct a root controller. Root factories take no props — startup config
3262
+ * goes in `deps`.
3263
+ */
3264
+ function createRoot(def, options) {
3265
+ return createRootWithProps(def, void 0, options);
3266
+ }
3267
+ //#endregion
3268
+ Object.defineProperty(exports, "batch", {
3269
+ enumerable: true,
3270
+ get: function() {
3271
+ return batch;
3272
+ }
3273
+ });
3274
+ Object.defineProperty(exports, "computed", {
3275
+ enumerable: true,
3276
+ get: function() {
3277
+ return computed;
3278
+ }
3279
+ });
3280
+ Object.defineProperty(exports, "createEmitter", {
3281
+ enumerable: true,
3282
+ get: function() {
3283
+ return createEmitter;
3284
+ }
3285
+ });
3286
+ Object.defineProperty(exports, "createRoot", {
3287
+ enumerable: true,
3288
+ get: function() {
3289
+ return createRoot;
3290
+ }
3291
+ });
3292
+ Object.defineProperty(exports, "createRootWithProps", {
3293
+ enumerable: true,
3294
+ get: function() {
3295
+ return createRootWithProps;
3296
+ }
3297
+ });
3298
+ Object.defineProperty(exports, "debouncedValidator", {
3299
+ enumerable: true,
3300
+ get: function() {
3301
+ return debouncedValidator;
3302
+ }
3303
+ });
3304
+ Object.defineProperty(exports, "defineController", {
3305
+ enumerable: true,
3306
+ get: function() {
3307
+ return defineController;
3308
+ }
3309
+ });
3310
+ Object.defineProperty(exports, "effect", {
3311
+ enumerable: true,
3312
+ get: function() {
3313
+ return effect;
3314
+ }
3315
+ });
3316
+ Object.defineProperty(exports, "isAbortError", {
3317
+ enumerable: true,
3318
+ get: function() {
3319
+ return isAbortError;
3320
+ }
3321
+ });
3322
+ Object.defineProperty(exports, "lookupRegisteredQuery", {
3323
+ enumerable: true,
3324
+ get: function() {
3325
+ return lookupRegisteredQuery;
3326
+ }
3327
+ });
3328
+ Object.defineProperty(exports, "registerQueryById", {
3329
+ enumerable: true,
3330
+ get: function() {
3331
+ return registerQueryById;
3332
+ }
3333
+ });
3334
+ Object.defineProperty(exports, "signal", {
3335
+ enumerable: true,
3336
+ get: function() {
3337
+ return signal;
3338
+ }
3339
+ });
3340
+ Object.defineProperty(exports, "untracked", {
3341
+ enumerable: true,
3342
+ get: function() {
3343
+ return untracked;
3344
+ }
3345
+ });
3346
+
3347
+ //# sourceMappingURL=root-Bazp5_Ik.cjs.map