@askrjs/askr 0.3.0 → 0.4.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 (123) hide show
  1. package/CHANGELOG.md +874 -0
  2. package/README.md +61 -29
  3. package/capabilities.json +19 -9
  4. package/dist/actions/index.js +56 -12
  5. package/dist/benchmark.js +10 -15
  6. package/dist/boot/index.d.ts +3 -2
  7. package/dist/boot/index.js +1 -1
  8. package/dist/boot-DaoTYoCw.js +3989 -0
  9. package/dist/browser-activity-CB8e-V4f.js +220 -0
  10. package/dist/components/index.d.ts +8 -1
  11. package/dist/components/index.js +1 -43
  12. package/dist/{aria-hKjThHcp.js → compose-ref-DWzdbxsD.js} +41 -4
  13. package/dist/control/index.d.ts +2 -0
  14. package/dist/control/index.js +1 -1
  15. package/dist/control-nfpT2fbK.js +209 -0
  16. package/dist/core.d.ts +5 -9
  17. package/dist/csp-nonce-DwGw1A6Q.js +35 -0
  18. package/dist/data/index.js +3 -3
  19. package/dist/{data-2edW0jhF.js → data-BCwClJiF.js} +283 -94
  20. package/dist/data-runtime-C1bFAlba.js +242 -0
  21. package/dist/{deferred-CbgNhf67.js → deferred-bEEHHvic.js} +42 -20
  22. package/dist/domains/actions.d.ts +8 -3
  23. package/dist/domains/component.d.ts +1 -107
  24. package/dist/domains/context.d.ts +0 -15
  25. package/dist/domains/control.d.ts +36 -249
  26. package/dist/domains/data.d.ts +31 -13
  27. package/dist/domains/diagnostics.d.ts +1 -2
  28. package/dist/domains/lifecycle.d.ts +2 -5
  29. package/dist/domains/refs.d.ts +0 -2
  30. package/dist/domains/routing.d.ts +43 -8
  31. package/dist/domains/scheduler.d.ts +1 -76
  32. package/dist/domains/server.d.ts +5 -2
  33. package/dist/domains/state.d.ts +2 -5
  34. package/dist/domains/telemetry.d.ts +0 -2
  35. package/dist/elements.d.ts +214 -51
  36. package/dist/error-boundary-Bpwofwps.js +293 -0
  37. package/dist/foundations/icon/index.d.ts +0 -58
  38. package/dist/foundations/icon/index.js +2 -2
  39. package/dist/foundations/index.js +2 -1
  40. package/dist/foundations/interactions/index.js +1 -1
  41. package/dist/foundations/state/index.d.ts +3 -3
  42. package/dist/foundations/state/index.js +5 -3
  43. package/dist/foundations/structures/index.js +2 -1
  44. package/dist/foundations/utilities/index.d.ts +2 -0
  45. package/dist/foundations/utilities/index.js +1 -2
  46. package/dist/fx/index.d.ts +31 -58
  47. package/dist/fx/index.js +313 -195
  48. package/dist/hooks-BimKStr4.js +202 -0
  49. package/dist/index.d.ts +11 -74
  50. package/dist/index.js +8 -447
  51. package/dist/instance-BAnfmXOR.js +988 -0
  52. package/dist/{interactions-CTeACdT_.js → interactions-D2BoREb-.js} +3 -2
  53. package/dist/jsx-dev-runtime.d.ts +41 -18
  54. package/dist/jsx-dev-runtime.js +1 -6
  55. package/dist/{jsx-runtime-CPJXAOBF.js → jsx-runtime-C4idqHY0.js} +18 -7
  56. package/dist/jsx-runtime.js +1 -1
  57. package/dist/jsx.d.ts +61 -37
  58. package/dist/{manifest-D1P3UNWV.js → manifest-CW9gyNBY.js} +3 -3
  59. package/dist/{navigate-prC5V8XV.js → navigate-CP_0Zy5U.js} +454 -87
  60. package/dist/navigation.d.ts +12 -3
  61. package/dist/peer-types.d.ts +8 -0
  62. package/dist/portal-DuOKfnLa.js +349 -0
  63. package/dist/promise-Dz8iIHAm.js +16 -0
  64. package/dist/query-registry-WMCN0BJL.js +320 -0
  65. package/dist/readable-DFWOsTac.js +21 -0
  66. package/dist/{render-context-B7ohlh6t.js → render-context-DGHnEveC.js} +27 -11
  67. package/dist/render-sync-IC5lMFpq.js +1240 -0
  68. package/dist/resolution-Cx82-xjW.js +1244 -0
  69. package/dist/resource-CLjB7RBQ.js +377 -0
  70. package/dist/resources/index.d.ts +21 -11
  71. package/dist/resources/index.js +45 -244
  72. package/dist/router/index.d.ts +14 -5
  73. package/dist/router/index.js +22 -53
  74. package/dist/scope-CwpVSHVf.js +55 -0
  75. package/dist/{shared-kU8yvryp.js → shared-O2ZnmYAf.js} +1 -7
  76. package/dist/{snapshot-source-An4iKajG.js → snapshot-Cjx4N988.js} +6 -1
  77. package/dist/ssg/index.d.ts +1 -2
  78. package/dist/ssg/index.js +5 -13
  79. package/dist/ssr/index.d.ts +26 -28
  80. package/dist/ssr/index.js +4 -4
  81. package/dist/{ssr-CiNm6Vzl.js → ssr-BJ8ug7zH.js} +133 -122
  82. package/dist/ssr-DoV-vJ-5.js +229 -0
  83. package/dist/state-DWO1KzkX.js +232 -0
  84. package/dist/structures-D4dDMqHX.js +231 -0
  85. package/dist/testing/index.d.ts +8 -4
  86. package/dist/testing/index.js +29 -34
  87. package/dist/url-CnbfpX0K.js +185 -0
  88. package/dist/utilities.d.ts +48 -2
  89. package/dist/verify-hydration-DpVaDnqz.js +152 -0
  90. package/package.json +19 -9
  91. package/dist/access-CEA2pOry.js +0 -1046
  92. package/dist/activity-oy2YFTFF.js +0 -201
  93. package/dist/boot-BMBMWUGI.js +0 -1084
  94. package/dist/capabilities-Bbf0mlp9.js +0 -42
  95. package/dist/compose-ref-v0qGlHoN.js +0 -26
  96. package/dist/control-BR_mrX__.js +0 -173
  97. package/dist/control-C-ApnFs1.js +0 -20
  98. package/dist/csp-nonce-BfFWcd0w.js +0 -173
  99. package/dist/data-runtime-CxoY98lX.js +0 -149
  100. package/dist/default-portal-runtime-DvMvWJVp.js +0 -20
  101. package/dist/dom-renderer.d.ts +0 -77
  102. package/dist/domains/renderer.d.ts +0 -127
  103. package/dist/eager-control.d.ts +0 -8
  104. package/dist/effect-C3Zsc1k6.js +0 -292
  105. package/dist/for-state-BDykeOJL.js +0 -1753
  106. package/dist/instance-VAFYVJOa.js +0 -1027
  107. package/dist/notify-DxxuDPeY.js +0 -266
  108. package/dist/portal-w3oQ6oQU.js +0 -29
  109. package/dist/query-registry-LrYpQsDJ.js +0 -83
  110. package/dist/render-resolved-BC0W7_5b.js +0 -1123
  111. package/dist/renderer-CT0HeY58.js +0 -8623
  112. package/dist/resolution-3DskUkjG.js +0 -577
  113. package/dist/resource-18-sFRl9.js +0 -344
  114. package/dist/route-matching-CUzZ31gT.js +0 -551
  115. package/dist/router-internal-7nuNEHfG.js +0 -36
  116. package/dist/scope-Cq_iYbTR.js +0 -403
  117. package/dist/selector-BSPVkjKP.js +0 -311
  118. package/dist/ssr-EPjwJH5R.js +0 -29
  119. package/dist/state-BXJfudq1.js +0 -91
  120. package/dist/structures-CjEzfj4h.js +0 -654
  121. package/dist/timer-C7uqnJVt.js +0 -85
  122. package/dist/verify-hydration-ikaxotf7.js +0 -27
  123. package/dist/vnode-D24p6IW2.js +0 -7
@@ -1,11 +1,9 @@
1
- import { b as ScheduledWork, f as requestRuntimeWork, v as logger } from "./access-CEA2pOry.js";
2
- import { d as getCurrentAppRenderRuntime, f as getCurrentComponentInstance, i as claimHookIndex } from "./scope-Cq_iYbTR.js";
3
- import { a as ownComponentCleanup, r as getComponentLifetimeIdentity } from "./capabilities-Bbf0mlp9.js";
4
- import { v as recordReadableRead } from "./notify-DxxuDPeY.js";
5
- import { n as getActiveRenderContext } from "./render-context-B7ohlh6t.js";
6
- import { a as routeActive, i as windowFocused, n as documentVisible, t as timer } from "./timer-C7uqnJVt.js";
7
- import { a as getMutationSlotStore, c as resolveDataRuntimeState, i as getDefaultDataRuntime, n as ensureMutationCleanup, o as getQuerySlotStore, r as ensureQueryCleanup, s as invalidateQueriesForRuntime } from "./data-runtime-CxoY98lX.js";
8
- import { a as normalizeAsyncDataError, i as isCurrentAsyncOperation, n as isAbortError, o as notifySource, t as createReadableSource } from "./shared-kU8yvryp.js";
1
+ import { C as schedule, F as logger } from "./instance-BAnfmXOR.js";
2
+ import { i as currentComponent, n as createSource, r as currentAppRuntime, s as notify, t as claimHookIndex, u as readSource } from "./hooks-BimKStr4.js";
3
+ import { n as getActiveRenderContext } from "./render-context-DGHnEveC.js";
4
+ import { a as getDefaultDataRuntime, c as invalidateQueriesForRuntime, f as invalidateCollectionCell, l as readQueryData, m as unregisterCollectionCell, n as ensureMutationCleanup, o as getMutationSlotStore, p as registerCollectionCell, r as ensureQueryCleanup, s as getQuerySlotStore, u as resolveDataRuntimeState } from "./data-runtime-C1bFAlba.js";
5
+ import { a as routeActive, n as windowFocused, s as timer, t as documentVisible } from "./browser-activity-CB8e-V4f.js";
6
+ import { i as normalizeAsyncDataError, r as isCurrentAsyncOperation, t as isAbortError } from "./shared-O2ZnmYAf.js";
9
7
  //#region src/data/query-key.ts
10
8
  function serializeQueryKeyNumber(part) {
11
9
  if (Number.isNaN(part)) return "NaN";
@@ -46,18 +44,28 @@ function createQueryScope(namespace, invalidate) {
46
44
  }
47
45
  //#endregion
48
46
  //#region src/data/invalidation.ts
49
- /** Mark all cached queries whose key starts with `prefix` as stale, triggering a refresh. */
47
+ /**
48
+ * Mark all cached queries under `prefix` as stale, triggering a refresh.
49
+ * Matching is by `:`-delimited segment: a key matches when it equals `prefix`,
50
+ * when `prefix` ends in `:`, or when the key continues `prefix` at a `:`
51
+ * (`'user:1'` matches `user:1:posts` but not `user:10`). Only `:` is a
52
+ * segment boundary; the empty prefix matches every key.
53
+ */
50
54
  function invalidate(prefix, options) {
51
55
  invalidateQueriesForRuntime(resolveDataRuntimeState(options?.runtime), prefix, options?.markPendingWrite ?? false);
52
56
  }
53
- /** Create a {@link QueryScope} that namespaces keys and invalidations under `namespace`. */
54
- function queryScope(namespace) {
55
- return createQueryScope(namespace, invalidate);
57
+ /** Create a {@link QueryScope} that namespaces keys and can bind invalidations to a runtime. */
58
+ function queryScope(namespace, options) {
59
+ return createQueryScope(namespace, (prefix, callOptions) => invalidate(prefix, {
60
+ ...callOptions,
61
+ runtime: callOptions?.runtime ?? options?.runtime
62
+ }));
56
63
  }
57
64
  const INVALIDATE_ON_INTERVAL_OPTIONS_ERROR = "[Askr] invalidateOnInterval() requires an options object with a finite numeric intervalMs.";
58
65
  /**
59
66
  * Periodically invalidate queries matching `prefix` on a fixed interval,
60
67
  * optionally gated by active route, document visibility, or window focus.
68
+ * `prefix` matches key segments the same way as {@link invalidate}.
61
69
  */
62
70
  function invalidateOnInterval(prefix, options) {
63
71
  if (!options || typeof options !== "object" || typeof options.intervalMs !== "number" || !Number.isFinite(options.intervalMs)) throw new Error(INVALIDATE_ON_INTERVAL_OPTIONS_ERROR);
@@ -74,8 +82,10 @@ function invalidateOnInterval(prefix, options) {
74
82
  //#region src/data/mutation-cell.ts
75
83
  var MutationCell = class {
76
84
  constructor(options, runtimeState) {
77
- this.source = createReadableSource();
85
+ this.source = createSource();
78
86
  this.controller = null;
87
+ this.activeControllers = /* @__PURE__ */ new Set();
88
+ this.rollbacks = /* @__PURE__ */ new Map();
79
89
  this.generation = 0;
80
90
  this.state = {
81
91
  status: "idle",
@@ -84,28 +94,30 @@ var MutationCell = class {
84
94
  };
85
95
  this.runtimeState = runtimeState;
86
96
  this.action = options.action;
97
+ this.optimistic = options.optimistic;
87
98
  this.affects = options.affects;
88
99
  this.afterSuccess = options.afterSuccess;
89
100
  }
90
101
  setOptions(options) {
91
102
  this.action = options.action;
103
+ this.optimistic = options.optimistic;
92
104
  this.affects = options.affects;
93
105
  this.afterSuccess = options.afterSuccess;
94
106
  }
95
107
  get status() {
96
- recordReadableRead(this.source);
108
+ readSource(this.source);
97
109
  return this.state.status;
98
110
  }
99
111
  get pending() {
100
- recordReadableRead(this.source);
112
+ readSource(this.source);
101
113
  return this.state.status === "pending";
102
114
  }
103
115
  get error() {
104
- recordReadableRead(this.source);
116
+ readSource(this.source);
105
117
  return this.state.error;
106
118
  }
107
119
  get result() {
108
- recordReadableRead(this.source);
120
+ readSource(this.source);
109
121
  return this.state.result;
110
122
  }
111
123
  setState(next) {
@@ -113,17 +125,18 @@ var MutationCell = class {
113
125
  ...this.state,
114
126
  ...next
115
127
  };
116
- notifySource(this.source);
128
+ notify(this.source);
117
129
  }
118
130
  async execute(input) {
119
131
  const action = this.action;
132
+ const optimistic = this.optimistic;
120
133
  const affects = this.affects;
121
134
  const afterSuccess = this.afterSuccess;
122
135
  this.generation += 1;
123
136
  const generation = this.generation;
124
- this.controller?.abort();
125
137
  const controller = new AbortController();
126
138
  this.controller = controller;
139
+ this.activeControllers.add(controller);
127
140
  this.setState({
128
141
  status: "pending",
129
142
  error: null,
@@ -131,8 +144,17 @@ var MutationCell = class {
131
144
  });
132
145
  let result;
133
146
  try {
147
+ const rollback = optimistic?.(input, { signal: controller.signal });
148
+ if (rollback) this.rollbacks.set(controller, rollback);
134
149
  result = await action(input, { signal: controller.signal });
135
- } catch (error) {
150
+ this.rollbacks.delete(controller);
151
+ } catch (cause) {
152
+ let error = cause;
153
+ try {
154
+ this.rollbackOptimistic(controller);
155
+ } catch (rollbackError) {
156
+ error = new AggregateError([cause, rollbackError], "Mutation failed and its optimistic rollback failed");
157
+ }
136
158
  if (!isCurrentAsyncOperation(this.generation, generation, this.controller, controller)) throw error;
137
159
  if (isAbortError(error, controller.signal)) {
138
160
  this.setState({
@@ -146,6 +168,9 @@ var MutationCell = class {
146
168
  error: normalizeAsyncDataError(error, "Unknown mutation error")
147
169
  });
148
170
  throw error;
171
+ } finally {
172
+ this.activeControllers.delete(controller);
173
+ this.rollbacks.delete(controller);
149
174
  }
150
175
  if (isCurrentAsyncOperation(this.generation, generation, this.controller, controller)) this.setState({
151
176
  status: "success",
@@ -159,25 +184,44 @@ var MutationCell = class {
159
184
  return result;
160
185
  }
161
186
  abort() {
162
- if (this.state.status !== "pending") return;
187
+ if (this.activeControllers.size === 0) return;
163
188
  this.generation += 1;
164
- this.controller?.abort();
165
189
  this.controller = null;
166
- this.setState({
190
+ if (this.state.status === "pending") this.setState({
167
191
  status: "idle",
168
192
  error: null,
169
193
  result: null
170
194
  });
195
+ this.cancelActive();
171
196
  }
172
197
  reset() {
173
198
  this.generation += 1;
174
- this.controller?.abort();
175
199
  this.controller = null;
176
200
  this.setState({
177
201
  status: "idle",
178
202
  error: null,
179
203
  result: null
180
204
  });
205
+ this.cancelActive();
206
+ }
207
+ rollbackOptimistic(controller) {
208
+ const rollback = this.rollbacks.get(controller);
209
+ this.rollbacks.delete(controller);
210
+ rollback?.();
211
+ }
212
+ cancelActive() {
213
+ const controllers = [...this.activeControllers];
214
+ this.activeControllers.clear();
215
+ const errors = [];
216
+ for (const controller of controllers) {
217
+ try {
218
+ this.rollbackOptimistic(controller);
219
+ } catch (error) {
220
+ errors.push(error);
221
+ }
222
+ controller.abort();
223
+ }
224
+ if (errors.length > 0) throw new AggregateError(errors, "Optimistic mutation rollback failed");
181
225
  }
182
226
  };
183
227
  /**
@@ -187,14 +231,14 @@ var MutationCell = class {
187
231
  */
188
232
  function createMutation(options) {
189
233
  if (options.key !== void 0 && options.key.length === 0) throw new TypeError("createMutation key must be a non-empty string.");
190
- const instance = getCurrentComponentInstance();
234
+ const instance = currentComponent();
191
235
  const runtimeState = resolveDataRuntimeState(options.runtime);
192
236
  const override = options.key ? runtimeState.mutationTestOverrides.get(options.key) : void 0;
193
237
  if (!instance) {
194
238
  if (override) return override;
195
239
  return new MutationCell(options, runtimeState);
196
240
  }
197
- const hookIndex = claimHookIndex(instance, "mutation");
241
+ const hookIndex = claimHookIndex(instance, "createMutation");
198
242
  ensureMutationCleanup(runtimeState, instance);
199
243
  const slotStore = getMutationSlotStore(runtimeState, instance);
200
244
  if (override) {
@@ -281,19 +325,24 @@ function errorQueryState(data, error) {
281
325
  //#region src/data/query-cell.ts
282
326
  const RECONCILE_MAX_ATTEMPTS = 3;
283
327
  const RECONCILE_RETRY_DELAY_MS = 25;
284
- var QueryStartWork = class extends ScheduledWork {
328
+ const MAX_GC_TIME_MS = 2147483647;
329
+ const DEFAULT_OWNERLESS_GC_TIME_MS = 3e5;
330
+ function validateGcTime(gcTime) {
331
+ if (gcTime !== void 0 && (!Number.isFinite(gcTime) || gcTime < 0 || gcTime > MAX_GC_TIME_MS)) throw new RangeError("Query gcTime must be a finite, non-negative timer delay.");
332
+ }
333
+ /** Starts a query fetch in the flush; settles its promise if dropped. */
334
+ var QueryStartWork = class {
285
335
  constructor(run, settle) {
286
- super(run);
336
+ this.run = run;
287
337
  this.settle = settle;
288
338
  }
289
339
  cancel() {
290
- super.cancel();
291
340
  this.settle();
292
341
  }
293
342
  };
294
343
  var QueryCell = class {
295
344
  constructor(options, key, cache) {
296
- this.source = createReadableSource();
345
+ this.source = createSource();
297
346
  this.controller = null;
298
347
  this.generation = 0;
299
348
  this.pendingRefresh = null;
@@ -304,22 +353,36 @@ var QueryCell = class {
304
353
  this.reconcileSequence = 0;
305
354
  this.destroyed = false;
306
355
  this.ownerCount = 0;
356
+ this.gcTimer = null;
357
+ this.unownedTimer = null;
307
358
  this.owners = /* @__PURE__ */ new Map();
308
359
  this.warnedDefinitionConflictKeys = /* @__PURE__ */ new Set();
360
+ this.definitionOwner = null;
361
+ this.definitionOwnerHook = -1;
362
+ this.conflictCheck = null;
309
363
  this.state = loadingQueryState();
364
+ validateGcTime(options.gcTime);
310
365
  this.options = options;
311
366
  this.key = key;
312
367
  this.cache = cache;
313
368
  if (options.initialData !== void 0) this.state = freshQueryState(options.initialData);
314
369
  }
315
370
  attach(generation, hookIndex) {
371
+ if (this.unownedTimer !== null) {
372
+ clearTimeout(this.unownedTimer);
373
+ this.unownedTimer = null;
374
+ }
375
+ if (this.gcTimer !== null) {
376
+ clearTimeout(this.gcTimer);
377
+ this.gcTimer = null;
378
+ }
316
379
  let hooks = this.owners.get(generation);
317
380
  if (!hooks) {
318
- hooks = /* @__PURE__ */ new Set();
381
+ hooks = /* @__PURE__ */ new Map();
319
382
  this.owners.set(generation, hooks);
320
383
  }
321
384
  if (hooks.has(hookIndex)) return;
322
- hooks.add(hookIndex);
385
+ hooks.set(hookIndex, null);
323
386
  this.ownerCount += 1;
324
387
  }
325
388
  detach(generation, hookIndex) {
@@ -327,7 +390,51 @@ var QueryCell = class {
327
390
  if (!hooks || !hooks.delete(hookIndex)) return;
328
391
  this.ownerCount -= 1;
329
392
  if (hooks.size === 0) this.owners.delete(generation);
330
- if (this.ownerCount <= 0) this.destroy();
393
+ if (this.ownerCount <= 0) {
394
+ const gcTime = this.options.gcTime ?? 0;
395
+ if (gcTime === 0 || this.state.data === null || isServerRender()) this.destroy();
396
+ else {
397
+ this.generation += 1;
398
+ this.controller?.abort();
399
+ this.controller = null;
400
+ this.finishPendingRefresh();
401
+ this.definitionOwner = null;
402
+ this.definitionOwnerHook = -1;
403
+ this.gcTimer = setTimeout(() => this.destroy(), gcTime);
404
+ }
405
+ return;
406
+ }
407
+ if (this.definitionOwner === generation && this.definitionOwnerHook === hookIndex) this.promoteDefinitionOwner();
408
+ }
409
+ promoteDefinitionOwner() {
410
+ this.definitionOwner = null;
411
+ for (const [generation, hooks] of this.owners) for (const [hookIndex, options] of hooks) if (options) {
412
+ this.options = options;
413
+ this.definitionOwner = generation;
414
+ this.definitionOwnerHook = hookIndex;
415
+ return;
416
+ }
417
+ }
418
+ /**
419
+ * Record an attached reader's definition for this key. The owning reader's
420
+ * renders replace the definition; with no owner, the reader takes over.
421
+ * Other readers are checked for conflicts once render work settles.
422
+ */
423
+ define(options, generation, hookIndex) {
424
+ validateGcTime(options.gcTime);
425
+ const hooks = this.owners.get(generation);
426
+ if (this.destroyed || !hooks?.has(hookIndex)) return;
427
+ hooks.set(hookIndex, options);
428
+ if (this.definitionOwner === null || this.definitionOwner === generation && this.definitionOwnerHook === hookIndex) {
429
+ this.options = options;
430
+ this.definitionOwner = generation;
431
+ this.definitionOwnerHook = hookIndex;
432
+ return;
433
+ }
434
+ }
435
+ warnOnConflictingReaders() {
436
+ if (this.destroyed) return;
437
+ for (const hooks of this.owners.values()) for (const options of hooks.values()) if (options) this.warnOnConflictingDefinition(options);
331
438
  }
332
439
  warnOnConflictingDefinition(options) {
333
440
  const conflicts = this.getDefinitionConflicts(options);
@@ -341,14 +448,45 @@ var QueryCell = class {
341
448
  destroy() {
342
449
  if (this.destroyed) return;
343
450
  this.destroyed = true;
451
+ if (this.gcTimer !== null) {
452
+ clearTimeout(this.gcTimer);
453
+ this.gcTimer = null;
454
+ }
455
+ if (this.unownedTimer !== null) {
456
+ clearTimeout(this.unownedTimer);
457
+ this.unownedTimer = null;
458
+ }
344
459
  this.controller?.abort();
345
460
  this.controller = null;
346
461
  this.reconcileAttemptCount = 0;
347
462
  this.ownerCount = 0;
348
463
  this.owners.clear();
349
- this.cache.delete(this.key);
464
+ this.evictFromCache();
350
465
  this.finishPendingRefresh();
351
466
  }
467
+ evictFromCache() {
468
+ if (this.cache.get(this.key) === this) this.cache.delete(this.key);
469
+ }
470
+ /** A component's inactive definition must not become an ownerless fetcher. */
471
+ retireInactiveReaderCacheEntry() {
472
+ if (this.gcTimer === null) return false;
473
+ this.destroy();
474
+ return true;
475
+ }
476
+ /** Ownerless handles stay usable after their cache lookup window expires. */
477
+ scheduleUnownedCacheEviction(gcTime) {
478
+ if (isServerRender() || this.ownerCount > 0 || this.destroyed) return;
479
+ if (this.unownedTimer !== null) clearTimeout(this.unownedTimer);
480
+ this.unownedTimer = null;
481
+ if (gcTime === 0) {
482
+ this.evictFromCache();
483
+ return;
484
+ }
485
+ this.unownedTimer = setTimeout(() => {
486
+ this.unownedTimer = null;
487
+ if (this.ownerCount === 0) this.evictFromCache();
488
+ }, gcTime);
489
+ }
352
490
  getDefinitionConflicts(options) {
353
491
  const conflicts = [];
354
492
  if ((this.options.definitionIdentity ?? this.options.fetch) !== (options.definitionIdentity ?? options.fetch)) conflicts.push("fetch");
@@ -357,31 +495,31 @@ var QueryCell = class {
357
495
  return conflicts;
358
496
  }
359
497
  get data() {
360
- recordReadableRead(this.source);
498
+ readSource(this.source);
361
499
  return this.state.data;
362
500
  }
363
501
  get error() {
364
- recordReadableRead(this.source);
502
+ readSource(this.source);
365
503
  return this.state.error;
366
504
  }
367
505
  get loading() {
368
- recordReadableRead(this.source);
506
+ readSource(this.source);
369
507
  return this.state.loading;
370
508
  }
371
509
  get refreshing() {
372
- recordReadableRead(this.source);
510
+ readSource(this.source);
373
511
  return this.state.refreshing;
374
512
  }
375
513
  get stale() {
376
- recordReadableRead(this.source);
514
+ readSource(this.source);
377
515
  return this.state.stale;
378
516
  }
379
517
  get consistency() {
380
- recordReadableRead(this.source);
518
+ readSource(this.source);
381
519
  return this.state.consistency;
382
520
  }
383
521
  get staleReason() {
384
- recordReadableRead(this.source);
522
+ readSource(this.source);
385
523
  return this.state.staleReason;
386
524
  }
387
525
  /** @internal Whether a collection should schedule this cell's first load. */
@@ -394,6 +532,10 @@ var QueryCell = class {
394
532
  }
395
533
  refresh() {
396
534
  if (this.destroyed) return Promise.resolve();
535
+ if (this.gcTimer !== null) {
536
+ this.destroy();
537
+ return Promise.resolve();
538
+ }
397
539
  if (this.pendingRefresh) {
398
540
  if (this.pendingRefreshKind === "invalidation") {
399
541
  this.controller?.abort();
@@ -404,14 +546,26 @@ var QueryCell = class {
404
546
  this.queueStart(void 0, "manual");
405
547
  return this.pendingRefresh ?? Promise.resolve();
406
548
  }
407
- invalidate() {
549
+ /** @internal Show an invalidation while a collection waits for a fetch slot. */
550
+ markQueuedInvalidation() {
408
551
  if (this.destroyed) return;
552
+ this.generation += 1;
553
+ this.controller?.abort();
554
+ this.setState(this.state.data === null ? loadingQueryState() : refreshingQueryState(this.state.data, this.state.consistency === "pending-write" ? "pending-write" : "refreshing"));
555
+ }
556
+ invalidate() {
557
+ if (this.destroyed) return Promise.resolve();
558
+ if (this.gcTimer !== null) {
559
+ this.destroy();
560
+ return Promise.resolve();
561
+ }
409
562
  if (this.pendingRefresh) {
410
563
  this.controller?.abort();
411
564
  this.queueStart(void 0, "invalidation", true);
412
- return;
565
+ return this.pendingRefresh ?? Promise.resolve();
413
566
  }
414
567
  this.queueStart(void 0, "invalidation");
568
+ return this.pendingRefresh ?? Promise.resolve();
415
569
  }
416
570
  markPendingWrite() {
417
571
  if (this.destroyed) return;
@@ -429,7 +583,7 @@ var QueryCell = class {
429
583
  if (!continuePending || !this.pendingRefresh) this.pendingRefresh = new Promise((resolve) => {
430
584
  this.pendingRefreshResolve = resolve;
431
585
  });
432
- requestRuntimeWork("component", new QueryStartWork(() => {
586
+ schedule(new QueryStartWork(() => {
433
587
  if (token !== this.pendingRefreshToken) return;
434
588
  if (this.destroyed) {
435
589
  this.finishPendingRefresh(token);
@@ -442,7 +596,7 @@ var QueryCell = class {
442
596
  if (token !== this.pendingRefreshToken) return;
443
597
  this.generation += 1;
444
598
  this.finishPendingRefresh(token);
445
- }));
599
+ }), "effect");
446
600
  }
447
601
  finishPendingRefresh(token = this.pendingRefreshToken) {
448
602
  if (token !== this.pendingRefreshToken) return;
@@ -455,7 +609,7 @@ var QueryCell = class {
455
609
  setState(next) {
456
610
  if (this.destroyed) return;
457
611
  this.state = next;
458
- notifySource(this.source);
612
+ notify(this.source);
459
613
  }
460
614
  async start(reconcileSequence) {
461
615
  if (this.destroyed) return;
@@ -466,9 +620,10 @@ var QueryCell = class {
466
620
  this.controller = controller;
467
621
  const hasData = this.state.data !== null;
468
622
  this.setState(hasData ? refreshingQueryState(this.state.data) : loadingQueryState());
623
+ const { fetch, isConsistent: checkConsistent, reconcile } = this.options;
469
624
  let nextData;
470
625
  try {
471
- nextData = await this.options.fetch({ signal: controller.signal });
626
+ nextData = await fetch({ signal: controller.signal });
472
627
  } catch (error) {
473
628
  if (this.destroyed || !isCurrentAsyncOperation(this.generation, generation, this.controller, controller)) return;
474
629
  if (isAbortError(error, controller.signal)) {
@@ -481,7 +636,7 @@ var QueryCell = class {
481
636
  if (this.destroyed || !isCurrentAsyncOperation(this.generation, generation, this.controller, controller)) return;
482
637
  let isConsistent;
483
638
  try {
484
- isConsistent = this.options.isConsistent?.(nextData) ?? true;
639
+ isConsistent = checkConsistent?.(nextData) ?? true;
485
640
  } catch (error) {
486
641
  this.setState(errorQueryState(this.state.data, normalizeAsyncDataError(error, "Query consistency check failed")));
487
642
  return;
@@ -489,7 +644,7 @@ var QueryCell = class {
489
644
  if (!isConsistent) {
490
645
  this.setState(staleQueryState(nextData, "inconsistent"));
491
646
  try {
492
- await this.reconcile(nextData, generation, controller, reconcileSequence);
647
+ await this.reconcile(reconcile, nextData, generation, controller, reconcileSequence);
493
648
  } catch (error) {
494
649
  if (isCurrentAsyncOperation(this.generation, generation, this.controller, controller)) this.setState(errorQueryState(this.state.data, normalizeAsyncDataError(error, "Query reconciliation failed")));
495
650
  }
@@ -498,8 +653,8 @@ var QueryCell = class {
498
653
  this.reconcileAttemptCount = 0;
499
654
  this.setState(freshQueryState(nextData));
500
655
  }
501
- async reconcile(data, generation, controller, reconcileSequence) {
502
- if (!await (this.options.reconcile?.(data, { key: this.options.key }) ?? false) || this.destroyed || reconcileSequence !== this.reconcileSequence || !isCurrentAsyncOperation(this.generation, generation, this.controller, controller)) return;
656
+ async reconcile(reconcile, data, generation, controller, reconcileSequence) {
657
+ if (!await (reconcile?.(data, { key: this.key }) ?? false) || this.destroyed || reconcileSequence !== this.reconcileSequence || !isCurrentAsyncOperation(this.generation, generation, this.controller, controller)) return;
503
658
  this.reconcileAttemptCount += 1;
504
659
  if (this.reconcileAttemptCount > RECONCILE_MAX_ATTEMPTS) {
505
660
  this.setState(staleQueryState(this.state.data, "inconsistent"));
@@ -511,63 +666,71 @@ var QueryCell = class {
511
666
  this.queueStart(reconcileSequence, "reconcile", true);
512
667
  }
513
668
  };
669
+ function isServerRender() {
670
+ const context = getActiveRenderContext();
671
+ return context?.mode === "ssr" || context?.mode === void 0 && typeof window === "undefined";
672
+ }
673
+ function createCell(options, cache) {
674
+ const cell = new QueryCell(options.takeInitialData ? {
675
+ ...options,
676
+ initialData: options.takeInitialData()
677
+ } : options, options.key, cache);
678
+ cache.set(options.key, cell);
679
+ cell.ensureStarted();
680
+ return cell;
681
+ }
514
682
  function createLegacyQuery(options) {
515
- const instance = getCurrentComponentInstance();
683
+ validateGcTime(options.gcTime);
684
+ const instance = currentComponent();
516
685
  const runtimeState = resolveDataRuntimeState(options.runtime);
517
686
  const cache = runtimeState.queryCache;
518
687
  const override = runtimeState.queryTestOverrides.get(options.key);
519
688
  if (!instance) {
520
689
  if (override) return override;
521
690
  let cell = cache.get(options.key);
522
- if (!cell) {
523
- cell = new QueryCell(options, options.key, cache);
524
- cache.set(options.key, cell);
525
- cell.ensureStarted();
526
- } else cell.warnOnConflictingDefinition(options);
691
+ if (cell?.retireInactiveReaderCacheEntry()) cell = void 0;
692
+ if (!cell) cell = createCell(options, cache);
693
+ else cell.warnOnConflictingDefinition(options);
694
+ cell.scheduleUnownedCacheEviction(options.gcTime ?? DEFAULT_OWNERLESS_GC_TIME_MS);
527
695
  return cell;
528
696
  }
529
- const hookIndex = claimHookIndex(instance, "query");
697
+ const hookIndex = claimHookIndex(instance, "createQuery");
530
698
  ensureQueryCleanup(runtimeState, instance);
699
+ const generation = instance;
531
700
  const slotStore = getQuerySlotStore(runtimeState, instance);
532
701
  const existingSlot = slotStore.get(hookIndex);
533
702
  if (existingSlot && existingSlot.key === options.key) {
534
- existingSlot.cell.warnOnConflictingDefinition(options);
703
+ existingSlot.cell.define(options, generation, hookIndex);
535
704
  return existingSlot.cell;
536
705
  }
537
- if (existingSlot) existingSlot.cell.detach(getComponentLifetimeIdentity(instance), hookIndex);
706
+ if (existingSlot) existingSlot.cell.detach(generation, hookIndex);
538
707
  if (override) return override;
539
- let cell = cache.get(options.key);
540
- if (!cell) {
541
- cell = new QueryCell(options, options.key, cache);
542
- cache.set(options.key, cell);
543
- cell.ensureStarted();
544
- } else cell.warnOnConflictingDefinition(options);
708
+ const cell = cache.get(options.key) ?? createCell(options, cache);
545
709
  slotStore.set(hookIndex, {
546
710
  key: options.key,
547
711
  cell
548
712
  });
549
- cell.attach(getComponentLifetimeIdentity(instance), hookIndex);
713
+ cell.attach(generation, hookIndex);
714
+ cell.define(options, generation, hookIndex);
715
+ cell.ensureStarted();
550
716
  return cell;
551
717
  }
552
718
  function createDefinedQuery(definition, input, options = {}) {
553
719
  const runtime = options.runtime;
554
- const context = getActiveRenderContext();
555
720
  const key = definition.key(input);
556
- const dataRuntime = runtime ?? getActiveRenderContext()?.dataRuntime ?? getCurrentAppRenderRuntime()?.dataRuntime ?? getDefaultDataRuntime();
557
- const initialData = dataRuntime?.queryData.get(key);
721
+ const dataRuntime = runtime ?? getActiveRenderContext()?.dataRuntime ?? currentAppRuntime()?.dataRuntime ?? getDefaultDataRuntime();
722
+ const serverRender = isServerRender();
723
+ const runtimeState = resolveDataRuntimeState(dataRuntime);
558
724
  return createLegacyQuery({
559
725
  ...options,
560
726
  key,
561
727
  definitionIdentity: definition,
562
- fetch: ({ signal }) => definition.fetch({
563
- ...input,
564
- signal
565
- }),
728
+ fetch: ({ signal }) => definition.fetch(input, { signal }),
566
729
  isConsistent: definition.isConsistent,
567
730
  reconcile: definition.reconcile,
568
- initialData,
569
- skipInitialFetch: context?.mode === "ssr" || context?.mode === void 0 && typeof window === "undefined",
570
- runtime: dataRuntime ?? options.runtime
731
+ takeInitialData: () => readQueryData(runtimeState, key, !serverRender),
732
+ skipInitialFetch: serverRender,
733
+ runtime: dataRuntime
571
734
  });
572
735
  }
573
736
  function createQuery(optionsOrDefinition, input, options) {
@@ -592,17 +755,13 @@ function getCollectionStore(generation) {
592
755
  }
593
756
  return store;
594
757
  }
595
- function createCellOptions(runtimeState, query, input, queryKey) {
758
+ function createCellOptions(query, input, queryKey) {
596
759
  return {
597
760
  key: queryKey,
598
761
  definitionIdentity: query,
599
- fetch: ({ signal }) => query.fetch({
600
- ...input,
601
- signal
602
- }),
762
+ fetch: ({ signal }) => query.fetch(input, { signal }),
603
763
  isConsistent: query.isConsistent,
604
764
  reconcile: query.reconcile,
605
- initialData: runtimeState.queryData.get(queryKey),
606
765
  skipInitialFetch: true
607
766
  };
608
767
  }
@@ -611,12 +770,25 @@ var QueryCollectionCell = class {
611
770
  this.runtimeState = runtimeState;
612
771
  this.records = /* @__PURE__ */ new Map();
613
772
  this.ordered = [];
773
+ this.registeredCells = /* @__PURE__ */ new Set();
614
774
  this.tasks = /* @__PURE__ */ new Map();
615
775
  this.queue = [];
616
776
  this.activeCount = 0;
617
777
  this.concurrency = DEFAULT_QUERY_COLLECTION_CONCURRENCY;
618
778
  this.disposed = false;
619
779
  }
780
+ get invalidationConcurrency() {
781
+ return this.concurrency;
782
+ }
783
+ invalidateCell(cell) {
784
+ const ownedCell = cell;
785
+ if (this.tasks.get(ownedCell)?.state === "active") {
786
+ ownedCell.invalidate();
787
+ return;
788
+ }
789
+ ownedCell.markQueuedInvalidation();
790
+ this.schedule(ownedCell, "invalidation");
791
+ }
620
792
  get entries() {
621
793
  return this.ordered;
622
794
  }
@@ -663,12 +835,12 @@ var QueryCollectionCell = class {
663
835
  const startCandidates = /* @__PURE__ */ new Set();
664
836
  const detachedCells = /* @__PURE__ */ new Set();
665
837
  for (const { input, key, queryKey } of desired) {
666
- const cellOptions = createCellOptions(this.runtimeState, query, input, queryKey);
838
+ const cellOptions = createCellOptions(query, input, queryKey);
667
839
  const previous = this.records.get(key);
668
840
  let record;
669
841
  if (previous?.queryKey === queryKey) {
670
842
  previous.input = input;
671
- previous.cell.warnOnConflictingDefinition(cellOptions);
843
+ previous.cell.define(cellOptions, previous.owner, 0);
672
844
  record = previous;
673
845
  } else {
674
846
  if (previous) {
@@ -678,11 +850,16 @@ var QueryCollectionCell = class {
678
850
  const cache = this.runtimeState.queryCache;
679
851
  let cell = cache.get(queryKey);
680
852
  if (!cell) {
681
- cell = new QueryCell(cellOptions, queryKey, cache);
853
+ const initialData = readQueryData(this.runtimeState, queryKey, startInitialFetches);
854
+ cell = new QueryCell({
855
+ ...cellOptions,
856
+ initialData
857
+ }, queryKey, cache);
682
858
  cache.set(queryKey, cell);
683
- } else cell.warnOnConflictingDefinition(cellOptions);
859
+ }
684
860
  const owner = {};
685
861
  cell.attach(owner, 0);
862
+ cell.define(cellOptions, owner, 0);
686
863
  record = {
687
864
  key,
688
865
  input,
@@ -700,8 +877,12 @@ var QueryCollectionCell = class {
700
877
  this.detach(record);
701
878
  detachedCells.add(record.cell);
702
879
  }
880
+ const nextCells = new Set(nextOrdered.map((record) => record.cell));
703
881
  this.records = nextRecords;
704
882
  this.ordered = Object.freeze(nextOrdered);
883
+ for (const cell of this.registeredCells) if (!nextCells.has(cell)) unregisterCollectionCell(cell, this);
884
+ for (const cell of nextCells) if (!this.registeredCells.has(cell)) registerCollectionCell(cell, this);
885
+ this.registeredCells = nextCells;
705
886
  for (const cell of detachedCells) this.cancelIfUnused(cell);
706
887
  for (const cell of startCandidates) this.schedule(cell);
707
888
  this.pump();
@@ -709,6 +890,8 @@ var QueryCollectionCell = class {
709
890
  dispose() {
710
891
  if (this.disposed) return;
711
892
  this.disposed = true;
893
+ for (const cell of this.registeredCells) unregisterCollectionCell(cell, this);
894
+ this.registeredCells.clear();
712
895
  const cells = /* @__PURE__ */ new Set();
713
896
  for (const record of this.records.values()) {
714
897
  cells.add(record.cell);
@@ -730,15 +913,20 @@ var QueryCollectionCell = class {
730
913
  const task = this.tasks.get(cell);
731
914
  if (!task || task.state === "cancelled" || task.state === "done") return;
732
915
  if (task.state === "active") this.activeCount -= 1;
916
+ const rescheduleInvalidation = task.state === "queued" && task.kind === "invalidation";
733
917
  task.state = "cancelled";
734
918
  this.tasks.delete(cell);
735
919
  task.resolve();
920
+ if (rescheduleInvalidation && !invalidateCollectionCell(cell)) cell.invalidate();
736
921
  this.pump();
737
922
  }
738
- schedule(cell) {
923
+ schedule(cell, kind = "refresh") {
739
924
  if (this.disposed) return Promise.resolve();
740
925
  const existing = this.tasks.get(cell);
741
- if (existing) return existing.promise;
926
+ if (existing) {
927
+ if (kind === "invalidation") existing.kind = kind;
928
+ return existing.promise;
929
+ }
742
930
  let resolve;
743
931
  const promise = new Promise((done) => {
744
932
  resolve = done;
@@ -747,7 +935,8 @@ var QueryCollectionCell = class {
747
935
  cell,
748
936
  promise,
749
937
  resolve,
750
- state: "queued"
938
+ state: "queued",
939
+ kind
751
940
  };
752
941
  this.tasks.set(cell, task);
753
942
  this.queue.push(task);
@@ -762,7 +951,7 @@ var QueryCollectionCell = class {
762
951
  if (task.state !== "queued" || this.tasks.get(task.cell) !== task) continue;
763
952
  task.state = "active";
764
953
  this.activeCount += 1;
765
- task.cell.refresh().finally(() => this.finish(task));
954
+ (task.kind === "invalidation" ? task.cell.invalidate() : task.cell.refresh()).finally(() => this.finish(task));
766
955
  }
767
956
  }
768
957
  finish(task) {
@@ -779,13 +968,13 @@ var QueryCollectionCell = class {
779
968
  * reusable query definition, with bounded collection-started fetches.
780
969
  */
781
970
  function createQueryCollection(options) {
782
- const instance = getCurrentComponentInstance();
971
+ const instance = currentComponent();
783
972
  if (!instance) throw new Error("[Askr] createQueryCollection() must be called during component render execution.");
784
973
  const hookIndex = claimHookIndex(instance, "createQueryCollection");
785
974
  const concurrency = normalizeQueryCollectionConcurrency(options.concurrency);
786
975
  const inputs = options.inputs();
787
976
  if (!Array.isArray(inputs)) throw new Error("[Askr] createQueryCollection() inputs must return a readonly array.");
788
- const generation = getComponentLifetimeIdentity(instance);
977
+ const generation = instance;
789
978
  const runtimeState = resolveDataRuntimeState(options.runtime);
790
979
  const store = getCollectionStore(generation);
791
980
  let slot = store.get(hookIndex);
@@ -800,7 +989,7 @@ function createQueryCollection(options) {
800
989
  collection: new QueryCollectionCell(runtimeState)
801
990
  };
802
991
  store.set(hookIndex, slot);
803
- ownComponentCleanup(instance, () => {
992
+ instance.onCleanup(() => {
804
993
  const current = store.get(hookIndex);
805
994
  try {
806
995
  current?.collection.dispose();