@askrjs/askr 0.0.41 → 0.0.43

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 (55) hide show
  1. package/dist/benchmark.js +2 -2
  2. package/dist/boot/index.d.ts.map +1 -1
  3. package/dist/boot/index.js +2 -0
  4. package/dist/boot/index.js.map +1 -1
  5. package/dist/common/route-activity.js +30 -0
  6. package/dist/common/route-activity.js.map +1 -0
  7. package/dist/common/router.d.ts +4 -2
  8. package/dist/common/router.d.ts.map +1 -1
  9. package/dist/data/index.d.ts +21 -2
  10. package/dist/data/index.d.ts.map +1 -1
  11. package/dist/data/index.js +56 -3
  12. package/dist/data/index.js.map +1 -1
  13. package/dist/data/invalidation-listeners.js +15 -0
  14. package/dist/data/invalidation-listeners.js.map +1 -0
  15. package/dist/renderer/dom.js +35 -20
  16. package/dist/renderer/dom.js.map +1 -1
  17. package/dist/renderer/evaluate.js +27 -17
  18. package/dist/renderer/evaluate.js.map +1 -1
  19. package/dist/renderer/for-commit.js +9 -0
  20. package/dist/renderer/for-commit.js.map +1 -1
  21. package/dist/resources/index.d.ts +2 -2
  22. package/dist/resources/index.js +2 -2
  23. package/dist/router/match.js +31 -6
  24. package/dist/router/match.js.map +1 -1
  25. package/dist/router/navigate.d.ts.map +1 -1
  26. package/dist/router/navigate.js +30 -4
  27. package/dist/router/navigate.js.map +1 -1
  28. package/dist/router/route.d.ts +6 -1
  29. package/dist/router/route.d.ts.map +1 -1
  30. package/dist/router/route.js +35 -8
  31. package/dist/router/route.js.map +1 -1
  32. package/dist/runtime/child-scope.js +1 -0
  33. package/dist/runtime/child-scope.js.map +1 -1
  34. package/dist/runtime/component.d.ts +8 -1
  35. package/dist/runtime/component.d.ts.map +1 -1
  36. package/dist/runtime/component.js +207 -44
  37. package/dist/runtime/component.js.map +1 -1
  38. package/dist/runtime/fastlane.js +6 -0
  39. package/dist/runtime/fastlane.js.map +1 -1
  40. package/dist/runtime/for.d.ts.map +1 -1
  41. package/dist/runtime/for.js +18 -1
  42. package/dist/runtime/for.js.map +1 -1
  43. package/dist/runtime/operations.d.ts +11 -3
  44. package/dist/runtime/operations.d.ts.map +1 -1
  45. package/dist/runtime/operations.js +161 -22
  46. package/dist/runtime/operations.js.map +1 -1
  47. package/dist/runtime/readable.d.ts +1 -0
  48. package/dist/runtime/readable.d.ts.map +1 -1
  49. package/dist/runtime/readable.js +7 -4
  50. package/dist/runtime/readable.js.map +1 -1
  51. package/dist/testing/index.d.ts +53 -0
  52. package/dist/testing/index.d.ts.map +1 -0
  53. package/dist/testing/index.js +172 -0
  54. package/dist/testing/index.js.map +1 -0
  55. package/package.json +11 -3
@@ -1,12 +1,41 @@
1
1
  import { globalScheduler } from "./scheduler.js";
2
2
  import { getCurrentContextFrame } from "./context.js";
3
- import { getCurrentComponentInstance, registerMountOperation } from "./component.js";
3
+ import { claimHookIndex, getCurrentComponentInstance, registerCommitOperation } from "./component.js";
4
4
  import { state } from "./state.js";
5
5
  import { brandSnapshotSource } from "./snapshot-source.js";
6
6
  import { SSRDataMissingError } from "../common/ssr-errors.js";
7
+ import { isRouteActivityActive } from "../common/route-activity.js";
7
8
  import { getSSRBridge } from "./ssr-bridge.js";
8
9
  import { ResourceCell } from "./resource-cell.js";
9
10
  //#region src/runtime/operations.ts
11
+ function normalizePredicates(predicates) {
12
+ if (!predicates) return [];
13
+ return typeof predicates === "function" ? [predicates] : predicates;
14
+ }
15
+ function allPredicatesPass(predicates) {
16
+ for (const predicate of predicates) if (!predicate()) return false;
17
+ return true;
18
+ }
19
+ function getLifecycleSlot(instance, index, kind, create) {
20
+ const existing = instance.lifecycleSlots[index];
21
+ if (existing) {
22
+ const slot = existing;
23
+ if (slot.kind !== kind) throw new Error(`${kind}() lifecycle order violation: slot ${index} already belongs to ${slot.kind}(). Keep lifecycle primitives in a stable top-level order.`);
24
+ return existing;
25
+ }
26
+ const slot = create();
27
+ instance.lifecycleSlots[index] = slot;
28
+ return slot;
29
+ }
30
+ function routeActive(pathOrPaths) {
31
+ return () => isRouteActivityActive(pathOrPaths);
32
+ }
33
+ function documentVisible() {
34
+ return () => typeof document === "undefined" || document.visibilityState !== "hidden";
35
+ }
36
+ function windowFocused() {
37
+ return () => typeof document === "undefined" || typeof document.hasFocus !== "function" || document.hasFocus();
38
+ }
10
39
  /**
11
40
  * Resource primitive — simple, deterministic async primitive
12
41
  * Usage: resource(fn, deps)
@@ -165,24 +194,124 @@ function resource(fn, deps = []) {
165
194
  }
166
195
  return h.snapshot;
167
196
  }
168
- function on(target, event, handler) {
169
- const ownerIsRoot = getCurrentComponentInstance()?.isRoot ?? false;
170
- registerMountOperation(() => {
171
- if (!ownerIsRoot) throw new Error("[Askr] on() may only be used in root components");
172
- target.addEventListener(event, handler);
173
- return () => {
174
- target.removeEventListener(event, handler);
197
+ function normalizeListenerOptions(options) {
198
+ if (options === void 0 || typeof options === "boolean") return options;
199
+ return {
200
+ ...options.capture !== void 0 ? { capture: options.capture } : {},
201
+ ...options.once !== void 0 ? { once: options.once } : {},
202
+ ...options.passive !== void 0 ? { passive: options.passive } : {},
203
+ ...options.signal !== void 0 ? { signal: options.signal } : {}
204
+ };
205
+ }
206
+ function listenerOptionsEqual(a, b) {
207
+ if (a === b) return true;
208
+ if (typeof a === "boolean" || typeof b === "boolean") return a === b;
209
+ if (!a || !b) return a === b;
210
+ return a.capture === b.capture && a.once === b.once && a.passive === b.passive && a.signal === b.signal;
211
+ }
212
+ function detachListenerSlot(slot) {
213
+ if (!slot.attached || !slot.target) return;
214
+ slot.target.removeEventListener(slot.event, slot.listener, slot.options);
215
+ slot.attached = false;
216
+ }
217
+ function commitListenerSlot(instance, slot) {
218
+ slot.handler = slot.pendingHandler;
219
+ if (!slot.attached || slot.target !== slot.pendingTarget || slot.event !== slot.pendingEvent || !listenerOptionsEqual(slot.options, slot.pendingOptions)) {
220
+ detachListenerSlot(slot);
221
+ slot.target = slot.pendingTarget;
222
+ slot.event = slot.pendingEvent;
223
+ slot.options = slot.pendingOptions;
224
+ slot.target.addEventListener(slot.event, slot.listener, slot.options);
225
+ slot.attached = true;
226
+ }
227
+ if (!slot.cleanupRegistered) {
228
+ slot.cleanupRegistered = true;
229
+ instance.cleanupFns.push(() => {
230
+ detachListenerSlot(slot);
231
+ slot.cleanupRegistered = false;
232
+ });
233
+ }
234
+ }
235
+ function on(target, event, handler, options) {
236
+ const instance = getCurrentComponentInstance();
237
+ if (!instance) return;
238
+ const index = claimHookIndex(instance, "on");
239
+ const normalizedOptions = normalizeListenerOptions(options);
240
+ const slot = getLifecycleSlot(instance, index, "listener", () => {
241
+ const createdSlot = {
242
+ kind: "listener",
243
+ target: null,
244
+ event,
245
+ handler,
246
+ listener: ((evt) => {
247
+ createdSlot.handler.call(createdSlot.target, evt);
248
+ }),
249
+ options: void 0,
250
+ pendingTarget: target,
251
+ pendingEvent: event,
252
+ pendingHandler: handler,
253
+ pendingOptions: normalizedOptions,
254
+ attached: false,
255
+ cleanupRegistered: false
175
256
  };
257
+ return createdSlot;
258
+ });
259
+ slot.pendingTarget = target;
260
+ slot.pendingEvent = event;
261
+ slot.pendingHandler = handler;
262
+ slot.pendingOptions = normalizedOptions;
263
+ registerCommitOperation(() => {
264
+ commitListenerSlot(instance, slot);
176
265
  });
177
266
  }
178
- function timer(intervalMs, fn) {
179
- const ownerIsRoot = getCurrentComponentInstance()?.isRoot ?? false;
180
- registerMountOperation(() => {
181
- if (!ownerIsRoot) throw new Error("[Askr] timer() may only be used in root components");
182
- const id = setInterval(fn, intervalMs);
183
- return () => {
184
- clearInterval(id);
185
- };
267
+ function stopTimerSlot(slot) {
268
+ if (slot.id === null) return;
269
+ clearInterval(slot.id);
270
+ slot.id = null;
271
+ }
272
+ function startTimerSlot(slot) {
273
+ slot.id = setInterval(() => {
274
+ if (allPredicatesPass(slot.predicates)) slot.callback();
275
+ }, slot.intervalMs ?? slot.pendingIntervalMs);
276
+ }
277
+ function commitTimerSlot(instance, slot) {
278
+ const intervalChanged = slot.intervalMs !== slot.pendingIntervalMs;
279
+ slot.callback = slot.pendingCallback;
280
+ slot.predicates = slot.pendingPredicates;
281
+ if (slot.id === null || intervalChanged) {
282
+ stopTimerSlot(slot);
283
+ slot.intervalMs = slot.pendingIntervalMs;
284
+ startTimerSlot(slot);
285
+ }
286
+ if (!slot.cleanupRegistered) {
287
+ slot.cleanupRegistered = true;
288
+ instance.cleanupFns.push(() => {
289
+ stopTimerSlot(slot);
290
+ slot.cleanupRegistered = false;
291
+ });
292
+ }
293
+ }
294
+ function timer(intervalMs, fn, options) {
295
+ const instance = getCurrentComponentInstance();
296
+ if (!instance) return;
297
+ const index = claimHookIndex(instance, "timer");
298
+ const predicates = normalizePredicates(options?.when);
299
+ const slot = getLifecycleSlot(instance, index, "timer", () => ({
300
+ kind: "timer",
301
+ id: null,
302
+ intervalMs: null,
303
+ pendingIntervalMs: intervalMs,
304
+ callback: fn,
305
+ predicates,
306
+ pendingCallback: fn,
307
+ pendingPredicates: predicates,
308
+ cleanupRegistered: false
309
+ }));
310
+ slot.pendingIntervalMs = intervalMs;
311
+ slot.pendingCallback = fn;
312
+ slot.pendingPredicates = predicates;
313
+ registerCommitOperation(() => {
314
+ commitTimerSlot(instance, slot);
186
315
  });
187
316
  }
188
317
  function stream(_source, _options) {
@@ -193,11 +322,21 @@ function stream(_source, _options) {
193
322
  };
194
323
  }
195
324
  function task(fn) {
196
- const ownerIsRoot = getCurrentComponentInstance()?.isRoot ?? false;
197
- registerMountOperation(async () => {
198
- if (!ownerIsRoot) throw new Error("[Askr] task() may only be used in root components");
199
- return await fn();
200
- });
325
+ const instance = getCurrentComponentInstance();
326
+ if (!instance) return;
327
+ const slot = getLifecycleSlot(instance, claimHookIndex(instance, "task"), "task", () => ({
328
+ kind: "task",
329
+ started: false,
330
+ task: fn
331
+ }));
332
+ if (!slot.started) {
333
+ slot.task = fn;
334
+ registerCommitOperation(async () => {
335
+ if (slot.started) return;
336
+ slot.started = true;
337
+ return await slot.task();
338
+ });
339
+ }
201
340
  }
202
341
  /**
203
342
  * Capture the result of a synchronous expression at call time and return a
@@ -214,6 +353,6 @@ function capture(fn) {
214
353
  return () => value;
215
354
  }
216
355
  //#endregion
217
- export { capture, on, resource, stream, task, timer };
356
+ export { capture, documentVisible, on, resource, routeActive, stream, task, timer, windowFocused };
218
357
 
219
358
  //# sourceMappingURL=operations.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"operations.js","names":[],"sources":["../../src/runtime/operations.ts"],"sourcesContent":["import {\n getCurrentComponentInstance,\n registerMountOperation,\n type ComponentInstance,\n} from './component';\nimport { getCurrentContextFrame } from './context';\nimport { ResourceCell } from './resource-cell';\nimport { state } from './state';\nimport { globalScheduler } from './scheduler';\nimport { getSSRBridge } from './ssr-bridge';\nimport { brandSnapshotSource } from './snapshot-source';\nimport { SSRDataMissingError } from '../common/ssr-errors';\n\nexport interface ResourceResult<T> {\n value: T | null;\n pending: boolean;\n error: Error | null;\n refresh(): void;\n}\n\nexport function resource<T, const TDeps extends readonly unknown[]>(\n fn: (opts: { signal: AbortSignal }) => PromiseLike<T> | T,\n deps: TDeps\n): ResourceResult<T>;\n\n/**\n * Resource primitive — simple, deterministic async primitive\n * Usage: resource(fn, deps)\n * - fn receives { signal }\n * - captures execution context once at creation (synchronous step only)\n * - executes at most once per generation; stale async results are ignored\n * - refresh() cancels in-flight execution, increments generation and re-runs\n * - exposes { value, pending, error, refresh }\n * - during SSR, async results are disallowed and will throw synchronously\n */\nexport function resource<T>(\n fn: (opts: { signal: AbortSignal }) => PromiseLike<T> | T,\n deps: readonly unknown[] = []\n): ResourceResult<T> {\n const instance = getCurrentComponentInstance();\n // Create a non-null alias early so it can be used in nested closures\n // without TypeScript complaining about possible null access.\n const inst = instance as ComponentInstance;\n\n if (!instance) {\n const ssr = getSSRBridge();\n // If we're in a synchronous SSR render that has resolved data, use it.\n const renderData = ssr.getCurrentRenderData();\n if (renderData) {\n const key = ssr.getNextKey();\n if (!(key in renderData)) {\n ssr.throwSSRDataMissing();\n }\n const val = renderData[key] as T;\n return brandSnapshotSource({\n value: val,\n pending: false,\n error: null,\n refresh: () => {},\n }) as ResourceResult<T>;\n }\n\n // If we are in an SSR render pass without supplied data, throw for clarity.\n const ssrCtx = ssr.getCurrentSSRContext();\n if (ssrCtx) {\n ssr.throwSSRDataMissing();\n }\n\n // No active component instance and not in SSR render with data.\n // Autopilot invariant: resources must be created during render within an app.\n throw new Error(\n '[Askr] resource() must be called during component render inside an app. ' +\n 'Do not create resources at module scope or outside render.'\n );\n }\n\n // Internal ResourceCell — pure state machine now moved to its own module\n // to keep component wiring separate and ensure no component access here.\n // (See ./resource-cell.ts)\n\n // If we're in a synchronous SSR render that was supplied resolved data, use it\n const ssr = getSSRBridge();\n const renderData = ssr.getCurrentRenderData();\n if (renderData) {\n // Deterministic key generation: the collection step and render step use\n // the same incremental key generation to align resources.\n const key = ssr.getNextKey();\n if (!(key in renderData)) {\n ssr.throwSSRDataMissing();\n }\n\n // Commit synchronous value from render data and return a stable snapshot\n const val = renderData[key] as T;\n\n const holder = state<{\n cell?: ResourceCell<T>;\n snapshot: ResourceResult<T>;\n }>({\n cell: undefined,\n snapshot: brandSnapshotSource({\n value: val,\n pending: false,\n error: null,\n refresh: () => {},\n }) as ResourceResult<T>,\n });\n\n const h = holder();\n h.snapshot.value = val;\n h.snapshot.pending = false;\n h.snapshot.error = null;\n return h.snapshot;\n }\n\n // Persist a holder so the snapshot identity is stable across renders.\n const holder = state<{ cell?: ResourceCell<T>; snapshot: ResourceResult<T> }>(\n {\n cell: undefined,\n snapshot: brandSnapshotSource({\n value: null,\n pending: true,\n error: null,\n refresh: () => {},\n }) as ResourceResult<T>,\n }\n );\n\n const h = holder();\n\n // Initialize cell on first call\n if (!h.cell) {\n const frame = getCurrentContextFrame();\n const cell = new ResourceCell<T>(fn, deps, frame);\n // Attach debug label (component name) for richer logs\n cell.ownerName = inst.fn?.name || '<anonymous>';\n h.cell = cell;\n h.snapshot = cell.snapshot as ResourceResult<T>;\n\n // Subscribe and schedule component updates when cell changes\n const unsubscribe = cell.subscribe(() => {\n const cur = holder();\n cur.snapshot.value = cell.snapshot.value;\n cur.snapshot.pending = cell.snapshot.pending;\n cur.snapshot.error = cell.snapshot.error;\n holder.set(cur);\n try {\n inst.notifyUpdate?.();\n } catch {\n // ignore\n }\n });\n\n // Cleanup on unmount\n (inst.cleanupFns ??= []).push(() => {\n unsubscribe();\n cell.dispose();\n });\n\n // Render invariant: do NOT start async work during render on the client.\n // SSR remains strict/synchronous and must throw immediately if async is encountered.\n if (inst.ssr) {\n // SSR: must run synchronously so missing data throws during render\n cell.start(true, false);\n if (!cell.pending) {\n const cur = holder();\n cur.snapshot.value = cell.value;\n cur.snapshot.pending = cell.pending;\n cur.snapshot.error = cell.error;\n }\n } else {\n // Client: start after render via scheduler (never inline)\n const scheduledGeneration = cell.generation;\n globalScheduler.enqueueInLane('post', () => {\n if (!inst.notifyUpdate || cell.generation !== scheduledGeneration) {\n return;\n }\n\n try {\n cell.start(false, false);\n } catch (err) {\n // Non-SSR: reflect synchronous errors into snapshot via manual update\n const cur = holder();\n cur.snapshot.value = cell.value;\n cur.snapshot.pending = cell.pending;\n cur.snapshot.error = (err as Error) ?? null;\n holder.set(cur);\n inst.notifyUpdate?.();\n return;\n }\n\n // If the resource completed synchronously, subscribers were not notified.\n // Force a re-render so the component can observe the value.\n if (!cell.pending) {\n const cur = holder();\n cur.snapshot.value = cell.value;\n cur.snapshot.pending = cell.pending;\n cur.snapshot.error = cell.error;\n holder.set(cur);\n inst.notifyUpdate?.();\n }\n });\n }\n }\n\n const cell = h.cell!;\n cell.setLoader(fn);\n\n // Detect dependency changes and refresh immediately\n const depsChanged =\n !cell.deps ||\n cell.deps.length !== deps.length ||\n cell.deps.some((d, i) => !Object.is(d, deps[i]));\n\n if (depsChanged) {\n cell.deps = deps.slice();\n cell.generation++;\n cell.pending = true;\n cell.error = null;\n\n // Synchronously reflect the pending state into the stable snapshot so the\n // render that triggered the deps change can surface a loading indicator.\n // The async start() runs with notify=false and the deps-change branch never\n // re-published the snapshot, so without this a deps-driven refetch jumped\n // straight from the old value to the new value, never exposing pending.\n // Stale-while-revalidate: the previous value is retained until the new\n // fetch resolves.\n h.snapshot.pending = true;\n h.snapshot.error = null;\n try {\n if (inst.ssr) {\n cell.start(true, false);\n if (!cell.pending) {\n const cur = holder();\n cur.snapshot.value = cell.value;\n cur.snapshot.pending = cell.pending;\n cur.snapshot.error = cell.error;\n }\n } else {\n const scheduledGeneration = cell.generation;\n globalScheduler.enqueueInLane('post', () => {\n if (!inst.notifyUpdate || cell.generation !== scheduledGeneration) {\n return;\n }\n\n cell.start(false, false);\n if (!cell.pending) {\n const cur = holder();\n cur.snapshot.value = cell.value;\n cur.snapshot.pending = cell.pending;\n cur.snapshot.error = cell.error;\n holder.set(cur);\n inst.notifyUpdate?.();\n }\n });\n }\n } catch (err) {\n if (err instanceof SSRDataMissingError) throw err;\n cell.error = err as Error;\n cell.pending = false;\n const cur = holder();\n cur.snapshot.value = cell.value;\n cur.snapshot.pending = cell.pending;\n cur.snapshot.error = cell.error;\n // Do not call holder.set() here; this is still render.\n }\n }\n\n // Return the stable snapshot object owned by the cell\n return h.snapshot;\n}\n\nexport function on(\n target: EventTarget,\n event: string,\n handler: EventListener\n): void {\n const ownerIsRoot = getCurrentComponentInstance()?.isRoot ?? false;\n // Register the listener to be attached on mount. If the owner is not the\n // root app instance, fail loudly to prevent silent no-op behavior.\n registerMountOperation(() => {\n if (!ownerIsRoot) {\n throw new Error('[Askr] on() may only be used in root components');\n }\n target.addEventListener(event, handler);\n // Return cleanup function\n return () => {\n target.removeEventListener(event, handler);\n };\n });\n}\n\nexport function timer(intervalMs: number, fn: () => void): void {\n const ownerIsRoot = getCurrentComponentInstance()?.isRoot ?? false;\n // Register the timer to be started on mount. Fail loudly when used outside\n // of the root component to avoid silent no-ops.\n registerMountOperation(() => {\n if (!ownerIsRoot) {\n throw new Error('[Askr] timer() may only be used in root components');\n }\n const id = setInterval(fn, intervalMs);\n // Return cleanup function\n return () => {\n clearInterval(id);\n };\n });\n}\n\nexport function stream<T>(\n _source: unknown,\n _options?: Record<string, unknown>\n): { value: T | null; pending: boolean; error: Error | null } {\n // Stub implementation: no-op.\n return { value: null, pending: true, error: null };\n}\n\nexport function task(\n fn: () => void | (() => void) | PromiseLike<void | (() => void)>\n): void {\n const ownerIsRoot = getCurrentComponentInstance()?.isRoot ?? false;\n // Register the task to run on mount. Fail loudly when used outside the root\n // component so callers get immediate feedback rather than silent no-op.\n registerMountOperation(async () => {\n if (!ownerIsRoot) {\n throw new Error('[Askr] task() may only be used in root components');\n }\n // Execute the task (may be async) and return its cleanup\n return await fn();\n });\n}\n\n/**\n * Capture the result of a synchronous expression at call time and return a\n * thunk that returns the captured value later. This is a low-level helper for\n * cases where async continuations need to observe a snapshot of values at the\n * moment scheduling occurred.\n *\n * Usage (public API):\n * const snapshot = capture(() => someState());\n * Promise.resolve().then(() => { use(snapshot()); });\n */\nexport function capture<T>(fn: () => T): () => T {\n const value = fn();\n return () => value;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"operations.js","names":[],"sources":["../../src/runtime/operations.ts"],"sourcesContent":["import {\n claimHookIndex,\n getCurrentComponentInstance,\n registerCommitOperation,\n type ComponentInstance,\n} from './component';\nimport { getCurrentContextFrame } from './context';\nimport { ResourceCell } from './resource-cell';\nimport { state } from './state';\nimport { globalScheduler } from './scheduler';\nimport { getSSRBridge } from './ssr-bridge';\nimport { brandSnapshotSource } from './snapshot-source';\nimport { SSRDataMissingError } from '../common/ssr-errors';\nimport { isRouteActivityActive } from '../common/route-activity';\n\nexport interface ResourceResult<T> {\n value: T | null;\n pending: boolean;\n error: Error | null;\n refresh(): void;\n}\n\nexport type ActivityPredicate = () => boolean;\n\nexport interface TimerOptions {\n when?: ActivityPredicate | readonly ActivityPredicate[];\n}\n\nfunction normalizePredicates(\n predicates: TimerOptions['when']\n): readonly ActivityPredicate[] {\n if (!predicates) {\n return [];\n }\n\n return typeof predicates === 'function' ? [predicates] : predicates;\n}\n\nfunction allPredicatesPass(predicates: readonly ActivityPredicate[]): boolean {\n for (const predicate of predicates) {\n if (!predicate()) {\n return false;\n }\n }\n\n return true;\n}\n\ntype LifecycleSlotKind = 'timer' | 'listener' | 'task';\n\ntype LifecycleSlot = {\n kind: LifecycleSlotKind;\n};\n\nfunction getLifecycleSlot<TSlot extends LifecycleSlot>(\n instance: ComponentInstance,\n index: number,\n kind: TSlot['kind'],\n create: () => TSlot\n): TSlot {\n const existing = instance.lifecycleSlots[index];\n\n if (existing) {\n const slot = existing as LifecycleSlot;\n if (slot.kind !== kind) {\n throw new Error(\n `${kind}() lifecycle order violation: slot ${index} already belongs to ${slot.kind}(). ` +\n 'Keep lifecycle primitives in a stable top-level order.'\n );\n }\n\n return existing as TSlot;\n }\n\n const slot = create();\n instance.lifecycleSlots[index] = slot;\n return slot;\n}\n\nexport function routeActive(\n pathOrPaths: string | readonly string[]\n): ActivityPredicate {\n return () => isRouteActivityActive(pathOrPaths);\n}\n\nexport function documentVisible(): ActivityPredicate {\n return () =>\n typeof document === 'undefined' || document.visibilityState !== 'hidden';\n}\n\nexport function windowFocused(): ActivityPredicate {\n return () =>\n typeof document === 'undefined' ||\n typeof document.hasFocus !== 'function' ||\n document.hasFocus();\n}\n\nexport function resource<T, const TDeps extends readonly unknown[]>(\n fn: (opts: { signal: AbortSignal }) => PromiseLike<T> | T,\n deps: TDeps\n): ResourceResult<T>;\n\n/**\n * Resource primitive — simple, deterministic async primitive\n * Usage: resource(fn, deps)\n * - fn receives { signal }\n * - captures execution context once at creation (synchronous step only)\n * - executes at most once per generation; stale async results are ignored\n * - refresh() cancels in-flight execution, increments generation and re-runs\n * - exposes { value, pending, error, refresh }\n * - during SSR, async results are disallowed and will throw synchronously\n */\nexport function resource<T>(\n fn: (opts: { signal: AbortSignal }) => PromiseLike<T> | T,\n deps: readonly unknown[] = []\n): ResourceResult<T> {\n const instance = getCurrentComponentInstance();\n // Create a non-null alias early so it can be used in nested closures\n // without TypeScript complaining about possible null access.\n const inst = instance as ComponentInstance;\n\n if (!instance) {\n const ssr = getSSRBridge();\n // If we're in a synchronous SSR render that has resolved data, use it.\n const renderData = ssr.getCurrentRenderData();\n if (renderData) {\n const key = ssr.getNextKey();\n if (!(key in renderData)) {\n ssr.throwSSRDataMissing();\n }\n const val = renderData[key] as T;\n return brandSnapshotSource({\n value: val,\n pending: false,\n error: null,\n refresh: () => {},\n }) as ResourceResult<T>;\n }\n\n // If we are in an SSR render pass without supplied data, throw for clarity.\n const ssrCtx = ssr.getCurrentSSRContext();\n if (ssrCtx) {\n ssr.throwSSRDataMissing();\n }\n\n // No active component instance and not in SSR render with data.\n // Autopilot invariant: resources must be created during render within an app.\n throw new Error(\n '[Askr] resource() must be called during component render inside an app. ' +\n 'Do not create resources at module scope or outside render.'\n );\n }\n\n // Internal ResourceCell — pure state machine now moved to its own module\n // to keep component wiring separate and ensure no component access here.\n // (See ./resource-cell.ts)\n\n // If we're in a synchronous SSR render that was supplied resolved data, use it\n const ssr = getSSRBridge();\n const renderData = ssr.getCurrentRenderData();\n if (renderData) {\n // Deterministic key generation: the collection step and render step use\n // the same incremental key generation to align resources.\n const key = ssr.getNextKey();\n if (!(key in renderData)) {\n ssr.throwSSRDataMissing();\n }\n\n // Commit synchronous value from render data and return a stable snapshot\n const val = renderData[key] as T;\n\n const holder = state<{\n cell?: ResourceCell<T>;\n snapshot: ResourceResult<T>;\n }>({\n cell: undefined,\n snapshot: brandSnapshotSource({\n value: val,\n pending: false,\n error: null,\n refresh: () => {},\n }) as ResourceResult<T>,\n });\n\n const h = holder();\n h.snapshot.value = val;\n h.snapshot.pending = false;\n h.snapshot.error = null;\n return h.snapshot;\n }\n\n // Persist a holder so the snapshot identity is stable across renders.\n const holder = state<{ cell?: ResourceCell<T>; snapshot: ResourceResult<T> }>(\n {\n cell: undefined,\n snapshot: brandSnapshotSource({\n value: null,\n pending: true,\n error: null,\n refresh: () => {},\n }) as ResourceResult<T>,\n }\n );\n\n const h = holder();\n\n // Initialize cell on first call\n if (!h.cell) {\n const frame = getCurrentContextFrame();\n const cell = new ResourceCell<T>(fn, deps, frame);\n // Attach debug label (component name) for richer logs\n cell.ownerName = inst.fn?.name || '<anonymous>';\n h.cell = cell;\n h.snapshot = cell.snapshot as ResourceResult<T>;\n\n // Subscribe and schedule component updates when cell changes\n const unsubscribe = cell.subscribe(() => {\n const cur = holder();\n cur.snapshot.value = cell.snapshot.value;\n cur.snapshot.pending = cell.snapshot.pending;\n cur.snapshot.error = cell.snapshot.error;\n holder.set(cur);\n try {\n inst.notifyUpdate?.();\n } catch {\n // ignore\n }\n });\n\n // Cleanup on unmount\n (inst.cleanupFns ??= []).push(() => {\n unsubscribe();\n cell.dispose();\n });\n\n // Render invariant: do NOT start async work during render on the client.\n // SSR remains strict/synchronous and must throw immediately if async is encountered.\n if (inst.ssr) {\n // SSR: must run synchronously so missing data throws during render\n cell.start(true, false);\n if (!cell.pending) {\n const cur = holder();\n cur.snapshot.value = cell.value;\n cur.snapshot.pending = cell.pending;\n cur.snapshot.error = cell.error;\n }\n } else {\n // Client: start after render via scheduler (never inline)\n const scheduledGeneration = cell.generation;\n globalScheduler.enqueueInLane('post', () => {\n if (!inst.notifyUpdate || cell.generation !== scheduledGeneration) {\n return;\n }\n\n try {\n cell.start(false, false);\n } catch (err) {\n // Non-SSR: reflect synchronous errors into snapshot via manual update\n const cur = holder();\n cur.snapshot.value = cell.value;\n cur.snapshot.pending = cell.pending;\n cur.snapshot.error = (err as Error) ?? null;\n holder.set(cur);\n inst.notifyUpdate?.();\n return;\n }\n\n // If the resource completed synchronously, subscribers were not notified.\n // Force a re-render so the component can observe the value.\n if (!cell.pending) {\n const cur = holder();\n cur.snapshot.value = cell.value;\n cur.snapshot.pending = cell.pending;\n cur.snapshot.error = cell.error;\n holder.set(cur);\n inst.notifyUpdate?.();\n }\n });\n }\n }\n\n const cell = h.cell!;\n cell.setLoader(fn);\n\n // Detect dependency changes and refresh immediately\n const depsChanged =\n !cell.deps ||\n cell.deps.length !== deps.length ||\n cell.deps.some((d, i) => !Object.is(d, deps[i]));\n\n if (depsChanged) {\n cell.deps = deps.slice();\n cell.generation++;\n cell.pending = true;\n cell.error = null;\n\n // Synchronously reflect the pending state into the stable snapshot so the\n // render that triggered the deps change can surface a loading indicator.\n // The async start() runs with notify=false and the deps-change branch never\n // re-published the snapshot, so without this a deps-driven refetch jumped\n // straight from the old value to the new value, never exposing pending.\n // Stale-while-revalidate: the previous value is retained until the new\n // fetch resolves.\n h.snapshot.pending = true;\n h.snapshot.error = null;\n try {\n if (inst.ssr) {\n cell.start(true, false);\n if (!cell.pending) {\n const cur = holder();\n cur.snapshot.value = cell.value;\n cur.snapshot.pending = cell.pending;\n cur.snapshot.error = cell.error;\n }\n } else {\n const scheduledGeneration = cell.generation;\n globalScheduler.enqueueInLane('post', () => {\n if (!inst.notifyUpdate || cell.generation !== scheduledGeneration) {\n return;\n }\n\n cell.start(false, false);\n if (!cell.pending) {\n const cur = holder();\n cur.snapshot.value = cell.value;\n cur.snapshot.pending = cell.pending;\n cur.snapshot.error = cell.error;\n holder.set(cur);\n inst.notifyUpdate?.();\n }\n });\n }\n } catch (err) {\n if (err instanceof SSRDataMissingError) throw err;\n cell.error = err as Error;\n cell.pending = false;\n const cur = holder();\n cur.snapshot.value = cell.value;\n cur.snapshot.pending = cell.pending;\n cur.snapshot.error = cell.error;\n // Do not call holder.set() here; this is still render.\n }\n }\n\n // Return the stable snapshot object owned by the cell\n return h.snapshot;\n}\n\ntype ListenerOptions = boolean | AddEventListenerOptions | undefined;\n\ntype NormalizedListenerOptions =\n | boolean\n | {\n capture?: boolean;\n once?: boolean;\n passive?: boolean;\n signal?: AbortSignal;\n }\n | undefined;\n\ninterface ListenerSlot extends LifecycleSlot {\n kind: 'listener';\n target: EventTarget | null;\n event: string;\n handler: EventListener;\n listener: EventListener;\n options: NormalizedListenerOptions;\n pendingTarget: EventTarget;\n pendingEvent: string;\n pendingHandler: EventListener;\n pendingOptions: NormalizedListenerOptions;\n attached: boolean;\n cleanupRegistered: boolean;\n}\n\nfunction normalizeListenerOptions(\n options: ListenerOptions\n): NormalizedListenerOptions {\n if (options === undefined || typeof options === 'boolean') {\n return options;\n }\n\n return {\n ...(options.capture !== undefined ? { capture: options.capture } : {}),\n ...(options.once !== undefined ? { once: options.once } : {}),\n ...(options.passive !== undefined ? { passive: options.passive } : {}),\n ...(options.signal !== undefined ? { signal: options.signal } : {}),\n };\n}\n\nfunction listenerOptionsEqual(\n a: NormalizedListenerOptions,\n b: NormalizedListenerOptions\n): boolean {\n if (a === b) {\n return true;\n }\n if (typeof a === 'boolean' || typeof b === 'boolean') {\n return a === b;\n }\n if (!a || !b) {\n return a === b;\n }\n\n return (\n a.capture === b.capture &&\n a.once === b.once &&\n a.passive === b.passive &&\n a.signal === b.signal\n );\n}\n\nfunction detachListenerSlot(slot: ListenerSlot): void {\n if (!slot.attached || !slot.target) {\n return;\n }\n\n slot.target.removeEventListener(slot.event, slot.listener, slot.options);\n slot.attached = false;\n}\n\nfunction commitListenerSlot(\n instance: ComponentInstance,\n slot: ListenerSlot\n): void {\n slot.handler = slot.pendingHandler;\n\n const shouldReattach =\n !slot.attached ||\n slot.target !== slot.pendingTarget ||\n slot.event !== slot.pendingEvent ||\n !listenerOptionsEqual(slot.options, slot.pendingOptions);\n\n if (shouldReattach) {\n detachListenerSlot(slot);\n slot.target = slot.pendingTarget;\n slot.event = slot.pendingEvent;\n slot.options = slot.pendingOptions;\n slot.target.addEventListener(slot.event, slot.listener, slot.options);\n slot.attached = true;\n }\n\n if (!slot.cleanupRegistered) {\n slot.cleanupRegistered = true;\n instance.cleanupFns.push(() => {\n detachListenerSlot(slot);\n slot.cleanupRegistered = false;\n });\n }\n}\n\nexport function on(\n target: EventTarget,\n event: string,\n handler: EventListener,\n options?: ListenerOptions\n): void {\n const instance = getCurrentComponentInstance();\n if (!instance) {\n return;\n }\n\n const index = claimHookIndex(instance, 'on');\n const normalizedOptions = normalizeListenerOptions(options);\n const slot = getLifecycleSlot<ListenerSlot>(\n instance,\n index,\n 'listener',\n () => {\n const createdSlot = {\n kind: 'listener' as const,\n target: null,\n event,\n handler,\n listener: ((evt: Event) => {\n createdSlot.handler.call(createdSlot.target, evt);\n }) as EventListener,\n options: undefined,\n pendingTarget: target,\n pendingEvent: event,\n pendingHandler: handler,\n pendingOptions: normalizedOptions,\n attached: false,\n cleanupRegistered: false,\n };\n return createdSlot;\n }\n );\n\n slot.pendingTarget = target;\n slot.pendingEvent = event;\n slot.pendingHandler = handler;\n slot.pendingOptions = normalizedOptions;\n\n registerCommitOperation(() => {\n commitListenerSlot(instance, slot);\n });\n}\n\ninterface TimerSlot extends LifecycleSlot {\n kind: 'timer';\n id: ReturnType<typeof setInterval> | null;\n intervalMs: number | null;\n pendingIntervalMs: number;\n callback: () => void;\n predicates: readonly ActivityPredicate[];\n pendingCallback: () => void;\n pendingPredicates: readonly ActivityPredicate[];\n cleanupRegistered: boolean;\n}\n\nfunction stopTimerSlot(slot: TimerSlot): void {\n if (slot.id === null) {\n return;\n }\n\n clearInterval(slot.id);\n slot.id = null;\n}\n\nfunction startTimerSlot(slot: TimerSlot): void {\n slot.id = setInterval(() => {\n if (allPredicatesPass(slot.predicates)) {\n slot.callback();\n }\n }, slot.intervalMs ?? slot.pendingIntervalMs);\n}\n\nfunction commitTimerSlot(instance: ComponentInstance, slot: TimerSlot): void {\n const intervalChanged = slot.intervalMs !== slot.pendingIntervalMs;\n\n slot.callback = slot.pendingCallback;\n slot.predicates = slot.pendingPredicates;\n\n if (slot.id === null || intervalChanged) {\n stopTimerSlot(slot);\n slot.intervalMs = slot.pendingIntervalMs;\n startTimerSlot(slot);\n }\n\n if (!slot.cleanupRegistered) {\n slot.cleanupRegistered = true;\n instance.cleanupFns.push(() => {\n stopTimerSlot(slot);\n slot.cleanupRegistered = false;\n });\n }\n}\n\nexport function timer(\n intervalMs: number,\n fn: () => void,\n options?: TimerOptions\n): void {\n const instance = getCurrentComponentInstance();\n if (!instance) {\n return;\n }\n\n const index = claimHookIndex(instance, 'timer');\n const predicates = normalizePredicates(options?.when);\n const slot = getLifecycleSlot<TimerSlot>(instance, index, 'timer', () => ({\n kind: 'timer',\n id: null,\n intervalMs: null,\n pendingIntervalMs: intervalMs,\n callback: fn,\n predicates,\n pendingCallback: fn,\n pendingPredicates: predicates,\n cleanupRegistered: false,\n }));\n\n slot.pendingIntervalMs = intervalMs;\n slot.pendingCallback = fn;\n slot.pendingPredicates = predicates;\n\n registerCommitOperation(() => {\n commitTimerSlot(instance, slot);\n });\n}\n\nexport function stream<T>(\n _source: unknown,\n _options?: Record<string, unknown>\n): { value: T | null; pending: boolean; error: Error | null } {\n // Stub implementation: no-op.\n return { value: null, pending: true, error: null };\n}\n\nexport function task(\n fn: () => void | (() => void) | PromiseLike<void | (() => void)>\n): void {\n const instance = getCurrentComponentInstance();\n if (!instance) {\n return;\n }\n\n const index = claimHookIndex(instance, 'task');\n const slot = getLifecycleSlot<\n LifecycleSlot & {\n kind: 'task';\n started: boolean;\n task: typeof fn;\n }\n >(instance, index, 'task', () => ({\n kind: 'task',\n started: false,\n task: fn,\n }));\n\n if (!slot.started) {\n slot.task = fn;\n registerCommitOperation(async () => {\n if (slot.started) {\n return;\n }\n\n slot.started = true;\n return await slot.task();\n });\n }\n}\n\n/**\n * Capture the result of a synchronous expression at call time and return a\n * thunk that returns the captured value later. This is a low-level helper for\n * cases where async continuations need to observe a snapshot of values at the\n * moment scheduling occurred.\n *\n * Usage (public API):\n * const snapshot = capture(() => someState());\n * Promise.resolve().then(() => { use(snapshot()); });\n */\nexport function capture<T>(fn: () => T): () => T {\n const value = fn();\n return () => value;\n}\n"],"mappings":"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"}
@@ -16,6 +16,7 @@ declare function markReadableUsage(source: unknown): void;
16
16
  declare function recordReadableRead(source: ReadableSource<unknown>): void;
17
17
  declare function withFineGrainedReadTracking<T>(pendingSources: Set<ReadableSource<unknown>>, fn: () => T): T;
18
18
  declare function finalizeReadableSubscriptions(instance: ComponentInstance): void;
19
+ declare function finalizeReadableSubscriptionsFromSnapshot(instance: ComponentInstance, token: number | undefined, newSet: Set<ReadableSource<unknown>> | undefined): void;
19
20
  declare function cleanupReadableSubscriptions(instance: ComponentInstance): void;
20
21
  declare function withDerivedReadTracking<T>(subscriber: DerivedSubscriber, fn: () => T): T;
21
22
  declare function syncDerivedDependencySubscriptions(subscriber: DerivedSubscriber, prevSources: Set<ReadableSource<unknown>>, nextSources: Set<ReadableSource<unknown>>): void;
@@ -1 +1 @@
1
- {"version":3,"file":"readable.d.ts","names":[],"sources":["../../src/runtime/readable.ts"],"mappings":";;;UAGiB,iBAAA;EACf,UAAA;EACA,yBAAA,GAA4B,GAAG,CAAC,cAAA;AAAA;AAAA,UAGjB,cAAA;EAAA,IACX,CAAA;EACJ,YAAA;EACA,gBAAA;EACA,QAAA,GAAW,GAAA,CAAI,iBAAA;EACf,mBAAA,GAAsB,GAAA,CAAI,iBAAA;AAAA;AAAA,iBAGZ,iBAAA,CAAkB,MAAe;AAAA,iBAoBjC,kBAAA,CAAmB,MAA+B,EAAvB,cAAc;AAAA,iBA8BzC,2BAAA,IACd,cAAA,EAAgB,GAAA,CAAI,cAAA,YACpB,EAAA,QAAU,CAAA,GACT,CAAA;AAAA,iBAWa,6BAAA,CACd,QAA2B,EAAjB,iBAAiB;AAAA,iBAoCb,4BAAA,CACd,QAA2B,EAAjB,iBAAiB;AAAA,iBAab,uBAAA,IACd,UAAA,EAAY,iBAAA,EACZ,EAAA,QAAU,CAAA,GACT,CAAA;AAAA,iBAaa,kCAAA,CACd,UAAA,EAAY,iBAAA,EACZ,WAAA,EAAa,GAAA,CAAI,cAAA,YACjB,WAAA,EAAa,GAAA,CAAI,cAAA;AAAA,iBAkBH,mCAAA,CACd,UAAA,EAAY,iBAAA,EACZ,OAAA,EAAS,GAAA,CAAI,cAAA;AAAA,iBAQC,mCAAA,CACd,MAA+B,EAAvB,cAAc;AAAA,iBAYR,4BAAA,CACd,MAA+B,EAAvB,cAAc;AAAA,iBAaR,qBAAA,CACd,MAAA,EAAQ,cAAA,WACR,YAAA,GAAe,iBAAiB"}
1
+ {"version":3,"file":"readable.d.ts","names":[],"sources":["../../src/runtime/readable.ts"],"mappings":";;;UAGiB,iBAAA;EACf,UAAA;EACA,yBAAA,GAA4B,GAAG,CAAC,cAAA;AAAA;AAAA,UAGjB,cAAA;EAAA,IACX,CAAA;EACJ,YAAA;EACA,gBAAA;EACA,QAAA,GAAW,GAAA,CAAI,iBAAA;EACf,mBAAA,GAAsB,GAAA,CAAI,iBAAA;AAAA;AAAA,iBAGZ,iBAAA,CAAkB,MAAe;AAAA,iBAoBjC,kBAAA,CAAmB,MAA+B,EAAvB,cAAc;AAAA,iBA8BzC,2BAAA,IACd,cAAA,EAAgB,GAAA,CAAI,cAAA,YACpB,EAAA,QAAU,CAAA,GACT,CAAA;AAAA,iBAWa,6BAAA,CACd,QAA2B,EAAjB,iBAAiB;AAAA,iBAUb,yCAAA,CACd,QAAA,EAAU,iBAAA,EACV,KAAA,sBACA,MAAA,EAAQ,GAAA,CAAI,cAAA;AAAA,iBAgCE,4BAAA,CACd,QAA2B,EAAjB,iBAAiB;AAAA,iBAab,uBAAA,IACd,UAAA,EAAY,iBAAA,EACZ,EAAA,QAAU,CAAA,GACT,CAAA;AAAA,iBAaa,kCAAA,CACd,UAAA,EAAY,iBAAA,EACZ,WAAA,EAAa,GAAA,CAAI,cAAA,YACjB,WAAA,EAAa,GAAA,CAAI,cAAA;AAAA,iBAkBH,mCAAA,CACd,UAAA,EAAY,iBAAA,EACZ,OAAA,EAAS,GAAA,CAAI,cAAA;AAAA,iBAQC,mCAAA,CACd,MAA+B,EAAvB,cAAc;AAAA,iBAYR,4BAAA,CACd,MAA+B,EAAvB,cAAc;AAAA,iBAaR,qBAAA,CACd,MAAA,EAAQ,cAAA,WACR,YAAA,GAAe,iBAAiB"}
@@ -38,9 +38,14 @@ function withFineGrainedReadTracking(pendingSources, fn) {
38
38
  }
39
39
  function finalizeReadableSubscriptions(instance) {
40
40
  const newSet = instance._pendingReadSources;
41
- const oldSet = instance._lastReadSources;
42
41
  const token = instance._currentRenderToken;
42
+ finalizeReadableSubscriptionsFromSnapshot(instance, token, newSet);
43
+ instance._pendingReadSources = void 0;
44
+ instance._currentRenderToken = void 0;
45
+ }
46
+ function finalizeReadableSubscriptionsFromSnapshot(instance, token, newSet) {
43
47
  if (token === void 0) return;
48
+ const oldSet = instance._lastReadSources;
44
49
  if (oldSet) {
45
50
  for (const source of oldSet) if (!newSet?.has(source)) source._readers?.delete(instance);
46
51
  }
@@ -54,8 +59,6 @@ function finalizeReadableSubscriptions(instance) {
54
59
  readers.set(instance, instance.lastRenderToken ?? 0);
55
60
  }
56
61
  instance._lastReadSources = newSet ?? /* @__PURE__ */ new Set();
57
- instance._pendingReadSources = void 0;
58
- instance._currentRenderToken = void 0;
59
62
  }
60
63
  function cleanupReadableSubscriptions(instance) {
61
64
  const sources = instance._lastReadSources;
@@ -119,6 +122,6 @@ function notifyReadableReaders(source, skipInstance) {
119
122
  return didScheduleUpdate;
120
123
  }
121
124
  //#endregion
122
- export { cleanupReadableSubscriptions, clearDerivedDependencySubscriptions, finalizeReadableSubscriptions, markReactivePropsDirtySource, markReadableDerivedSubscribersDirty, markReadableUsage, notifyReadableReaders, recordReadableRead, syncDerivedDependencySubscriptions, withDerivedReadTracking, withFineGrainedReadTracking };
125
+ export { cleanupReadableSubscriptions, clearDerivedDependencySubscriptions, finalizeReadableSubscriptions, finalizeReadableSubscriptionsFromSnapshot, markReactivePropsDirtySource, markReadableDerivedSubscribersDirty, markReadableUsage, notifyReadableReaders, recordReadableRead, syncDerivedDependencySubscriptions, withDerivedReadTracking, withFineGrainedReadTracking };
123
126
 
124
127
  //# sourceMappingURL=readable.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"readable.js","names":[],"sources":["../../src/runtime/readable.ts"],"sourcesContent":["import { globalScheduler } from './scheduler';\nimport { getCurrentInstance, type ComponentInstance } from './component';\n\nexport interface DerivedSubscriber {\n _markDirty(): void;\n _pendingDependencySources?: Set<ReadableSource<unknown>>;\n}\n\nexport interface ReadableSource<T = unknown> {\n (): T;\n _hasBeenRead?: boolean;\n _hasEverBeenRead?: boolean;\n _readers?: Map<ComponentInstance, number>;\n _derivedSubscribers?: Set<DerivedSubscriber>;\n}\n\nexport function markReadableUsage(source: unknown): void {\n if (\n source &&\n typeof source === 'function' &&\n ('_hasBeenRead' in source || '_readers' in source)\n ) {\n const readable = source as ReadableSource<unknown>;\n readable._hasBeenRead = true;\n readable._hasEverBeenRead = true;\n }\n}\n\ntype RendererBridge = {\n markReactivePropsDirtySource?: (source: ReadableSource<unknown>) => void;\n};\n\nlet currentDerivedSubscriber: DerivedSubscriber | null = null;\nlet suppressComponentReadTrackingDepth = 0;\nlet currentFineGrainedReadSources: Set<ReadableSource<unknown>> | null = null;\n\nexport function recordReadableRead(source: ReadableSource<unknown>): void {\n source._hasEverBeenRead = true;\n\n if (currentFineGrainedReadSources) {\n currentFineGrainedReadSources.add(source);\n return;\n }\n\n if (currentDerivedSubscriber) {\n if (!currentDerivedSubscriber._pendingDependencySources) {\n currentDerivedSubscriber._pendingDependencySources = new Set();\n }\n currentDerivedSubscriber._pendingDependencySources.add(source);\n }\n\n if (suppressComponentReadTrackingDepth > 0) {\n return;\n }\n\n const inst = getCurrentInstance();\n if (!inst || inst._currentRenderToken === undefined) {\n return;\n }\n\n if (!inst._pendingReadSources) {\n inst._pendingReadSources = new Set();\n }\n inst._pendingReadSources.add(source);\n}\n\nexport function withFineGrainedReadTracking<T>(\n pendingSources: Set<ReadableSource<unknown>>,\n fn: () => T\n): T {\n const previousPendingSources = currentFineGrainedReadSources;\n currentFineGrainedReadSources = pendingSources;\n\n try {\n return fn();\n } finally {\n currentFineGrainedReadSources = previousPendingSources;\n }\n}\n\nexport function finalizeReadableSubscriptions(\n instance: ComponentInstance\n): void {\n const newSet = instance._pendingReadSources;\n const oldSet = instance._lastReadSources;\n const token = instance._currentRenderToken;\n\n if (token === undefined) {\n return;\n }\n\n if (oldSet) {\n for (const source of oldSet) {\n if (!newSet?.has(source)) {\n source._readers?.delete(instance);\n }\n }\n }\n\n instance.lastRenderToken = token;\n\n if (newSet) {\n for (const source of newSet) {\n let readers = source._readers;\n if (!readers) {\n readers = new Map();\n source._readers = readers;\n }\n readers.set(instance, instance.lastRenderToken ?? 0);\n }\n }\n\n instance._lastReadSources = newSet ?? new Set();\n instance._pendingReadSources = undefined;\n instance._currentRenderToken = undefined;\n}\n\nexport function cleanupReadableSubscriptions(\n instance: ComponentInstance\n): void {\n const sources = instance._lastReadSources;\n if (!sources || sources.size === 0) {\n return;\n }\n\n for (const source of sources) {\n source._readers?.delete(instance);\n }\n instance._lastReadSources = new Set();\n}\n\nexport function withDerivedReadTracking<T>(\n subscriber: DerivedSubscriber,\n fn: () => T\n): T {\n const prevDerivedSubscriber = currentDerivedSubscriber;\n currentDerivedSubscriber = subscriber;\n suppressComponentReadTrackingDepth += 1;\n\n try {\n return fn();\n } finally {\n suppressComponentReadTrackingDepth -= 1;\n currentDerivedSubscriber = prevDerivedSubscriber;\n }\n}\n\nexport function syncDerivedDependencySubscriptions(\n subscriber: DerivedSubscriber,\n prevSources: Set<ReadableSource<unknown>>,\n nextSources: Set<ReadableSource<unknown>>\n): void {\n for (const source of prevSources) {\n if (!nextSources.has(source)) {\n source._derivedSubscribers?.delete(subscriber);\n }\n }\n\n for (const source of nextSources) {\n let subscribers = source._derivedSubscribers;\n if (!subscribers) {\n subscribers = new Set();\n source._derivedSubscribers = subscribers;\n }\n subscribers.add(subscriber);\n }\n}\n\nexport function clearDerivedDependencySubscriptions(\n subscriber: DerivedSubscriber,\n sources: Set<ReadableSource<unknown>>\n): void {\n for (const source of sources) {\n source._derivedSubscribers?.delete(subscriber);\n }\n sources.clear();\n}\n\nexport function markReadableDerivedSubscribersDirty(\n source: ReadableSource<unknown>\n): void {\n const subscribers = source._derivedSubscribers;\n if (!subscribers || subscribers.size === 0) {\n return;\n }\n\n for (const subscriber of subscribers) {\n subscriber._markDirty();\n }\n}\n\nexport function markReactivePropsDirtySource(\n source: ReadableSource<unknown>\n): void {\n try {\n (\n globalThis as {\n __ASKR_RENDERER?: RendererBridge;\n }\n ).__ASKR_RENDERER?.markReactivePropsDirtySource?.(source);\n } catch {\n // Keep readable notifications side-effect safe.\n }\n}\n\nexport function notifyReadableReaders(\n source: ReadableSource<unknown>,\n skipInstance?: ComponentInstance | null\n): boolean {\n const readers = source._readers;\n let didScheduleUpdate = false;\n\n if (!readers || readers.size === 0) {\n return false;\n }\n\n for (const [instance, token] of readers) {\n if (skipInstance && instance === skipInstance) {\n continue;\n }\n if (instance.lastRenderToken !== token) {\n continue;\n }\n if (instance.hasPendingUpdate) {\n continue;\n }\n\n instance.hasPendingUpdate = true;\n const task = instance._pendingFlushTask;\n if (task) {\n globalScheduler.enqueue(task);\n } else {\n globalScheduler.enqueue(() => {\n instance.hasPendingUpdate = false;\n instance.notifyUpdate?.();\n });\n }\n didScheduleUpdate = true;\n }\n\n return didScheduleUpdate;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"readable.js","names":[],"sources":["../../src/runtime/readable.ts"],"sourcesContent":["import { globalScheduler } from './scheduler';\nimport { getCurrentInstance, type ComponentInstance } from './component';\n\nexport interface DerivedSubscriber {\n _markDirty(): void;\n _pendingDependencySources?: Set<ReadableSource<unknown>>;\n}\n\nexport interface ReadableSource<T = unknown> {\n (): T;\n _hasBeenRead?: boolean;\n _hasEverBeenRead?: boolean;\n _readers?: Map<ComponentInstance, number>;\n _derivedSubscribers?: Set<DerivedSubscriber>;\n}\n\nexport function markReadableUsage(source: unknown): void {\n if (\n source &&\n typeof source === 'function' &&\n ('_hasBeenRead' in source || '_readers' in source)\n ) {\n const readable = source as ReadableSource<unknown>;\n readable._hasBeenRead = true;\n readable._hasEverBeenRead = true;\n }\n}\n\ntype RendererBridge = {\n markReactivePropsDirtySource?: (source: ReadableSource<unknown>) => void;\n};\n\nlet currentDerivedSubscriber: DerivedSubscriber | null = null;\nlet suppressComponentReadTrackingDepth = 0;\nlet currentFineGrainedReadSources: Set<ReadableSource<unknown>> | null = null;\n\nexport function recordReadableRead(source: ReadableSource<unknown>): void {\n source._hasEverBeenRead = true;\n\n if (currentFineGrainedReadSources) {\n currentFineGrainedReadSources.add(source);\n return;\n }\n\n if (currentDerivedSubscriber) {\n if (!currentDerivedSubscriber._pendingDependencySources) {\n currentDerivedSubscriber._pendingDependencySources = new Set();\n }\n currentDerivedSubscriber._pendingDependencySources.add(source);\n }\n\n if (suppressComponentReadTrackingDepth > 0) {\n return;\n }\n\n const inst = getCurrentInstance();\n if (!inst || inst._currentRenderToken === undefined) {\n return;\n }\n\n if (!inst._pendingReadSources) {\n inst._pendingReadSources = new Set();\n }\n inst._pendingReadSources.add(source);\n}\n\nexport function withFineGrainedReadTracking<T>(\n pendingSources: Set<ReadableSource<unknown>>,\n fn: () => T\n): T {\n const previousPendingSources = currentFineGrainedReadSources;\n currentFineGrainedReadSources = pendingSources;\n\n try {\n return fn();\n } finally {\n currentFineGrainedReadSources = previousPendingSources;\n }\n}\n\nexport function finalizeReadableSubscriptions(\n instance: ComponentInstance\n): void {\n const newSet = instance._pendingReadSources;\n const token = instance._currentRenderToken;\n\n finalizeReadableSubscriptionsFromSnapshot(instance, token, newSet);\n instance._pendingReadSources = undefined;\n instance._currentRenderToken = undefined;\n}\n\nexport function finalizeReadableSubscriptionsFromSnapshot(\n instance: ComponentInstance,\n token: number | undefined,\n newSet: Set<ReadableSource<unknown>> | undefined\n): void {\n if (token === undefined) {\n return;\n }\n\n const oldSet = instance._lastReadSources;\n\n if (oldSet) {\n for (const source of oldSet) {\n if (!newSet?.has(source)) {\n source._readers?.delete(instance);\n }\n }\n }\n\n instance.lastRenderToken = token;\n\n if (newSet) {\n for (const source of newSet) {\n let readers = source._readers;\n if (!readers) {\n readers = new Map();\n source._readers = readers;\n }\n readers.set(instance, instance.lastRenderToken ?? 0);\n }\n }\n\n instance._lastReadSources = newSet ?? new Set();\n}\n\nexport function cleanupReadableSubscriptions(\n instance: ComponentInstance\n): void {\n const sources = instance._lastReadSources;\n if (!sources || sources.size === 0) {\n return;\n }\n\n for (const source of sources) {\n source._readers?.delete(instance);\n }\n instance._lastReadSources = new Set();\n}\n\nexport function withDerivedReadTracking<T>(\n subscriber: DerivedSubscriber,\n fn: () => T\n): T {\n const prevDerivedSubscriber = currentDerivedSubscriber;\n currentDerivedSubscriber = subscriber;\n suppressComponentReadTrackingDepth += 1;\n\n try {\n return fn();\n } finally {\n suppressComponentReadTrackingDepth -= 1;\n currentDerivedSubscriber = prevDerivedSubscriber;\n }\n}\n\nexport function syncDerivedDependencySubscriptions(\n subscriber: DerivedSubscriber,\n prevSources: Set<ReadableSource<unknown>>,\n nextSources: Set<ReadableSource<unknown>>\n): void {\n for (const source of prevSources) {\n if (!nextSources.has(source)) {\n source._derivedSubscribers?.delete(subscriber);\n }\n }\n\n for (const source of nextSources) {\n let subscribers = source._derivedSubscribers;\n if (!subscribers) {\n subscribers = new Set();\n source._derivedSubscribers = subscribers;\n }\n subscribers.add(subscriber);\n }\n}\n\nexport function clearDerivedDependencySubscriptions(\n subscriber: DerivedSubscriber,\n sources: Set<ReadableSource<unknown>>\n): void {\n for (const source of sources) {\n source._derivedSubscribers?.delete(subscriber);\n }\n sources.clear();\n}\n\nexport function markReadableDerivedSubscribersDirty(\n source: ReadableSource<unknown>\n): void {\n const subscribers = source._derivedSubscribers;\n if (!subscribers || subscribers.size === 0) {\n return;\n }\n\n for (const subscriber of subscribers) {\n subscriber._markDirty();\n }\n}\n\nexport function markReactivePropsDirtySource(\n source: ReadableSource<unknown>\n): void {\n try {\n (\n globalThis as {\n __ASKR_RENDERER?: RendererBridge;\n }\n ).__ASKR_RENDERER?.markReactivePropsDirtySource?.(source);\n } catch {\n // Keep readable notifications side-effect safe.\n }\n}\n\nexport function notifyReadableReaders(\n source: ReadableSource<unknown>,\n skipInstance?: ComponentInstance | null\n): boolean {\n const readers = source._readers;\n let didScheduleUpdate = false;\n\n if (!readers || readers.size === 0) {\n return false;\n }\n\n for (const [instance, token] of readers) {\n if (skipInstance && instance === skipInstance) {\n continue;\n }\n if (instance.lastRenderToken !== token) {\n continue;\n }\n if (instance.hasPendingUpdate) {\n continue;\n }\n\n instance.hasPendingUpdate = true;\n const task = instance._pendingFlushTask;\n if (task) {\n globalScheduler.enqueue(task);\n } else {\n globalScheduler.enqueue(() => {\n instance.hasPendingUpdate = false;\n instance.notifyUpdate?.();\n });\n }\n didScheduleUpdate = true;\n }\n\n return didScheduleUpdate;\n}\n"],"mappings":"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"}
@@ -0,0 +1,53 @@
1
+ import { Route, RouteManifest, RouteMatch } from "../common/router.js";
2
+ import { Query, QueryStaleReason } from "../data/index.js";
3
+
4
+ //#region src/testing/index.d.ts
5
+ type MockRefresh = () => void | Promise<void>;
6
+ interface MockQueryOptions {
7
+ refresh?: MockRefresh;
8
+ }
9
+ interface InvalidationRecord {
10
+ prefix: string;
11
+ markPendingWrite: boolean;
12
+ }
13
+ interface InvalidationRecorder {
14
+ readonly calls: readonly InvalidationRecord[];
15
+ readonly prefixes: readonly string[];
16
+ clear(): void;
17
+ stop(): void;
18
+ }
19
+ interface MatchRouteOptions {
20
+ manifest?: RouteManifest;
21
+ routes?: readonly Route[];
22
+ }
23
+ interface RoutePatternWarning {
24
+ kind: 'route-collision';
25
+ path: string;
26
+ conflictingPath: string;
27
+ segment: string;
28
+ namespace: string | undefined;
29
+ message: string;
30
+ }
31
+ type StaleValueReason = Exclude<QueryStaleReason, 'error'>;
32
+ declare function createFreshQuery<T extends {}>(data: T, options?: MockQueryOptions): Query<T>;
33
+ declare const mockQuery: typeof createFreshQuery & {
34
+ loading<T extends {} = {}>(options?: MockQueryOptions): Query<T>;
35
+ error<T extends {} = {}>(error: {}, previousData?: T, options?: MockQueryOptions): Query<T>;
36
+ refreshing<T extends {}>(data: T, options?: MockQueryOptions): Query<T>;
37
+ stale<T extends {}>(data: T, reason?: StaleValueReason, options?: MockQueryOptions): Query<T>;
38
+ pendingWrite<T extends {}>(data: T, options?: MockQueryOptions): Query<T>;
39
+ };
40
+ declare const queryState: {
41
+ fresh: typeof createFreshQuery;
42
+ loading: <T extends {} = {}>(options?: MockQueryOptions) => Query<T>;
43
+ error: <T extends {} = {}>(error: {}, previousData?: T, options?: MockQueryOptions) => Query<T>;
44
+ refreshing: <T extends {}>(data: T, options?: MockQueryOptions) => Query<T>;
45
+ stale: <T extends {}>(data: T, reason?: StaleValueReason, options?: MockQueryOptions) => Query<T>;
46
+ pendingWrite: <T extends {}>(data: T, options?: MockQueryOptions) => Query<T>;
47
+ };
48
+ declare function createInvalidationRecorder(): InvalidationRecorder;
49
+ declare function matchRoute(path: string, options?: MatchRouteOptions): RouteMatch | null;
50
+ declare function getRouteWarnings(options?: MatchRouteOptions): RoutePatternWarning[];
51
+ //#endregion
52
+ export { InvalidationRecord, InvalidationRecorder, MockQueryOptions, MockRefresh, RoutePatternWarning, createInvalidationRecorder, getRouteWarnings, matchRoute, mockQuery, queryState };
53
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../../src/testing/index.ts"],"mappings":";;;;KAeY,WAAA,gBAA2B,OAAO;AAAA,UAE7B,gBAAA;EACf,OAAA,GAAU,WAAW;AAAA;AAAA,UAGN,kBAAA;EACf,MAAA;EACA,gBAAgB;AAAA;AAAA,UAGD,oBAAA;EAAA,SACN,KAAA,WAAgB,kBAAkB;EAAA,SAClC,QAAA;EACT,KAAA;EACA,IAAA;AAAA;AAAA,UAGQ,iBAAA;EACR,QAAA,GAAW,aAAA;EACX,MAAA,YAAkB,KAAK;AAAA;AAAA,UAGR,mBAAA;EACf,IAAA;EACA,IAAA;EACA,eAAA;EACA,OAAA;EACA,SAAA;EACA,OAAA;AAAA;AAAA,KAGG,gBAAA,GAAmB,OAAO,CAAC,gBAAA;AAAA,iBAkBvB,gBAAA,eACP,IAAA,EAAM,CAAA,EACN,OAAA,GAAU,gBAAA,GACT,KAAA,CAAM,CAAA;AAAA,cAeI,SAAA,SAAS,gBAAA;6BACA,OAAA,GAAiB,gBAAA,GAAmB,KAAA,CAAM,CAAA;2BAe5C,KAAA,MACP,YAAA,GACM,CAAA,EAAC,OAAA,GACN,gBAAA,GACT,KAAA,CAAM,CAAA;2BAec,IAAA,EAAQ,CAAA,EAAC,OAAA,GAAY,gBAAA,GAAmB,KAAA,CAAM,CAAA;sBAenD,IAAA,EACV,CAAA,EAAC,MAAA,GACC,gBAAA,EAAgB,OAAA,GACd,gBAAA,GACT,KAAA,CAAM,CAAA;6BAegB,IAAA,EAAQ,CAAA,EAAC,OAAA,GAAY,gBAAA,GAAmB,KAAA,CAAM,CAAA;AAAA;AAAA,cAgB5D,UAAA;;+BApFS,OAAA,GAAiB,gBAAA,KAAmB,KAAA,CAAM,CAAA;6BAe5C,KAAA,MACP,YAAA,GACM,CAAA,EAAC,OAAA,GACN,gBAAA,KACT,KAAA,CAAM,CAAA;6BAec,IAAA,EAAQ,CAAA,EAAC,OAAA,GAAY,gBAAA,KAAmB,KAAA,CAAM,CAAA;wBAenD,IAAA,EACV,CAAA,EAAC,MAAA,GACC,gBAAA,EAAgB,OAAA,GACd,gBAAA,KACT,KAAA,CAAM,CAAA;+BAegB,IAAA,EAAQ,CAAA,EAAC,OAAA,GAAY,gBAAA,KAAmB,KAAA,CAAM,CAAA;AAAA;AAAA,iBAyBzD,0BAAA,IAA8B,oBAAoB;AAAA,iBAmClD,UAAA,CACd,IAAA,UACA,OAAA,GAAS,iBAAA,GACR,UAAU;AAAA,iBAoDG,gBAAA,CACd,OAAA,GAAS,iBAAA,GACR,mBAAmB"}
@@ -0,0 +1,172 @@
1
+ import { parseSegments } from "../router/match.js";
2
+ import { computeRouteActivityMatches } from "../router/route.js";
3
+ import { addInvalidationListener } from "../data/invalidation-listeners.js";
4
+ //#region src/testing/index.ts
5
+ function normalizeRefresh(options) {
6
+ return async () => {
7
+ await options?.refresh?.();
8
+ };
9
+ }
10
+ function makeQuery(state, options) {
11
+ return {
12
+ ...state,
13
+ refresh: normalizeRefresh(options)
14
+ };
15
+ }
16
+ function createFreshQuery(data, options) {
17
+ return makeQuery({
18
+ data,
19
+ error: null,
20
+ loading: false,
21
+ refreshing: false,
22
+ stale: false,
23
+ consistency: "fresh",
24
+ staleReason: null
25
+ }, options);
26
+ }
27
+ const mockQuery = Object.assign(createFreshQuery, {
28
+ loading(options) {
29
+ return makeQuery({
30
+ data: null,
31
+ error: null,
32
+ loading: true,
33
+ refreshing: false,
34
+ stale: false,
35
+ consistency: "fresh",
36
+ staleReason: null
37
+ }, options);
38
+ },
39
+ error(error, previousData, options) {
40
+ return makeQuery({
41
+ data: previousData ?? null,
42
+ error,
43
+ loading: false,
44
+ refreshing: false,
45
+ stale: true,
46
+ consistency: "stale",
47
+ staleReason: "error"
48
+ }, options);
49
+ },
50
+ refreshing(data, options) {
51
+ return makeQuery({
52
+ data,
53
+ error: null,
54
+ loading: false,
55
+ refreshing: true,
56
+ stale: true,
57
+ consistency: "refreshing",
58
+ staleReason: null
59
+ }, options);
60
+ },
61
+ stale(data, reason = "inconsistent", options) {
62
+ return makeQuery({
63
+ data,
64
+ error: null,
65
+ loading: false,
66
+ refreshing: false,
67
+ stale: true,
68
+ consistency: "stale",
69
+ staleReason: reason
70
+ }, options);
71
+ },
72
+ pendingWrite(data, options) {
73
+ return makeQuery({
74
+ data,
75
+ error: null,
76
+ loading: false,
77
+ refreshing: true,
78
+ stale: true,
79
+ consistency: "pending-write",
80
+ staleReason: null
81
+ }, options);
82
+ }
83
+ });
84
+ const queryState = {
85
+ fresh: createFreshQuery,
86
+ loading: mockQuery.loading,
87
+ error: mockQuery.error,
88
+ refreshing: mockQuery.refreshing,
89
+ stale: mockQuery.stale,
90
+ pendingWrite: mockQuery.pendingWrite
91
+ };
92
+ function createInvalidationRecorder() {
93
+ const records = [];
94
+ let active = true;
95
+ const unsubscribe = addInvalidationListener((event) => {
96
+ records.push({
97
+ prefix: event.prefix,
98
+ markPendingWrite: event.markPendingWrite
99
+ });
100
+ });
101
+ return {
102
+ get calls() {
103
+ return records.slice();
104
+ },
105
+ get prefixes() {
106
+ return records.map((record) => record.prefix);
107
+ },
108
+ clear() {
109
+ records.length = 0;
110
+ },
111
+ stop() {
112
+ if (!active) return;
113
+ active = false;
114
+ unsubscribe();
115
+ }
116
+ };
117
+ }
118
+ function matchRoute(path, options = {}) {
119
+ return computeRouteActivityMatches(path, options)[0] ?? null;
120
+ }
121
+ function getRoutePatternRecords(options) {
122
+ if (options.manifest) return options.manifest.records.map((record) => ({
123
+ path: record.path,
124
+ segments: record.segments,
125
+ namespace: record.options.namespace
126
+ }));
127
+ return (options.routes ?? []).map((route) => ({
128
+ path: route.path,
129
+ segments: parseSegments(route.path),
130
+ namespace: route.namespace
131
+ }));
132
+ }
133
+ function routePrefixMatches(splatPrefix, routeSegments) {
134
+ if (routeSegments.length <= splatPrefix.length) return false;
135
+ for (let index = 0; index < splatPrefix.length; index++) {
136
+ const splatSegment = splatPrefix[index];
137
+ const routeSegment = routeSegments[index];
138
+ if (splatSegment.kind === "static" && routeSegment.kind === "static" && splatSegment.value !== routeSegment.value) return false;
139
+ }
140
+ return true;
141
+ }
142
+ function getRouteWarnings(options = {}) {
143
+ const records = getRoutePatternRecords(options);
144
+ const warnings = [];
145
+ for (const record of records) {
146
+ const splatIndex = record.segments.findIndex((segment) => segment.kind === "splat");
147
+ if (splatIndex === -1) continue;
148
+ const splatPrefix = record.segments.slice(0, splatIndex);
149
+ for (const candidate of records) {
150
+ if (candidate === record || candidate.namespace !== record.namespace || !routePrefixMatches(splatPrefix, candidate.segments)) continue;
151
+ const reservedSegment = candidate.segments[splatIndex];
152
+ if (reservedSegment?.kind !== "static") continue;
153
+ warnings.push({
154
+ kind: "route-collision",
155
+ path: record.path,
156
+ conflictingPath: candidate.path,
157
+ segment: reservedSegment.value,
158
+ namespace: record.namespace,
159
+ message: `Route "${candidate.path}" reserves segment "${reservedSegment.value}" under named splat route "${record.path}".`
160
+ });
161
+ }
162
+ }
163
+ return warnings.sort((left, right) => {
164
+ const pathOrder = left.path.localeCompare(right.path);
165
+ if (pathOrder !== 0) return pathOrder;
166
+ return left.conflictingPath.localeCompare(right.conflictingPath);
167
+ });
168
+ }
169
+ //#endregion
170
+ export { createInvalidationRecorder, getRouteWarnings, matchRoute, mockQuery, queryState };
171
+
172
+ //# sourceMappingURL=index.js.map