@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
@@ -0,0 +1,242 @@
1
+ import { r as currentAppRuntime } from "./hooks-BimKStr4.js";
2
+ import { n as getActiveRenderContext } from "./render-context-DGHnEveC.js";
3
+ //#region src/data/invalidation-listeners.ts
4
+ const invalidationListeners = /* @__PURE__ */ new Set();
5
+ const activeInvalidationPrefixes = [];
6
+ const MAX_INVALIDATION_CASCADE_DEPTH = 100;
7
+ function addInvalidationListener(listener) {
8
+ invalidationListeners.add(listener);
9
+ return () => {
10
+ invalidationListeners.delete(listener);
11
+ };
12
+ }
13
+ /**
14
+ * Whether any listener is registered. Listeners come only from the testing
15
+ * entry (`createInvalidationRecorder()`), so production invalidations check
16
+ * this and skip dispatch and cascade bookkeeping entirely.
17
+ */
18
+ function hasInvalidationListeners() {
19
+ return invalidationListeners.size > 0;
20
+ }
21
+ function emitInvalidation(event) {
22
+ if (activeInvalidationPrefixes.includes(event.prefix)) throw new Error(`[Askr] Cyclic invalidation cascade detected for prefix ${JSON.stringify(event.prefix)}.`);
23
+ if (activeInvalidationPrefixes.length >= MAX_INVALIDATION_CASCADE_DEPTH) throw new Error(`[Askr] Invalidation cascade exceeded ${MAX_INVALIDATION_CASCADE_DEPTH} nested events at prefix ${JSON.stringify(event.prefix)}.`);
24
+ activeInvalidationPrefixes.push(event.prefix);
25
+ try {
26
+ for (const listener of invalidationListeners) listener(event);
27
+ } finally {
28
+ activeInvalidationPrefixes.pop();
29
+ }
30
+ }
31
+ //#endregion
32
+ //#region src/data/collection-invalidation.ts
33
+ const owners = /* @__PURE__ */ new WeakMap();
34
+ function registerCollectionCell(cell, owner) {
35
+ let registrations = owners.get(cell);
36
+ if (!registrations) {
37
+ registrations = /* @__PURE__ */ new Set();
38
+ owners.set(cell, registrations);
39
+ }
40
+ registrations.add(owner);
41
+ }
42
+ function unregisterCollectionCell(cell, owner) {
43
+ const registrations = owners.get(cell);
44
+ registrations?.delete(owner);
45
+ if (registrations?.size === 0) owners.delete(cell);
46
+ }
47
+ /** A shared query uses the smallest cap among its live collection readers. */
48
+ function invalidateCollectionCell(cell) {
49
+ const registrations = owners.get(cell);
50
+ if (!registrations?.size) return false;
51
+ let selected;
52
+ for (const owner of registrations) if (!selected || owner.invalidationConcurrency < selected.invalidationConcurrency) selected = owner;
53
+ selected.invalidateCell(cell);
54
+ return true;
55
+ }
56
+ //#endregion
57
+ //#region src/data/data-runtime.ts
58
+ const dataRuntimeStates = /* @__PURE__ */ new WeakMap();
59
+ const dataRuntimeByQueryCache = /* @__PURE__ */ new WeakMap();
60
+ function createDataRuntimeState(queryCache, queryData) {
61
+ return {
62
+ queryCache,
63
+ queryData,
64
+ unreadPrefetches: /* @__PURE__ */ new Map(),
65
+ prefetches: /* @__PURE__ */ new Map(),
66
+ querySlotsByGeneration: /* @__PURE__ */ new WeakMap(),
67
+ mutationSlotsByGeneration: /* @__PURE__ */ new WeakMap(),
68
+ queryCleanupRegistered: /* @__PURE__ */ new WeakSet(),
69
+ mutationCleanupRegistered: /* @__PURE__ */ new WeakSet(),
70
+ queryTestOverrides: /* @__PURE__ */ new Map(),
71
+ mutationTestOverrides: /* @__PURE__ */ new Map()
72
+ };
73
+ }
74
+ /** Create a new, isolated {@link DataRuntime} with its own query/mutation caches. */
75
+ function createDataRuntime(options = {}) {
76
+ const runtime = Object.freeze({
77
+ queryCache: options.queryCache ?? /* @__PURE__ */ new Map(),
78
+ queryData: options.queryData ?? /* @__PURE__ */ new Map()
79
+ });
80
+ dataRuntimeStates.set(runtime, createDataRuntimeState(runtime.queryCache, runtime.queryData));
81
+ dataRuntimeByQueryCache.set(runtime.queryCache, runtime);
82
+ return runtime;
83
+ }
84
+ const defaultDataRuntime = createDataRuntime();
85
+ /** Get the process-wide default {@link DataRuntime} used when none is provided explicitly. */
86
+ function getDefaultDataRuntime() {
87
+ return defaultDataRuntime;
88
+ }
89
+ function getDataRuntimeState(runtime) {
90
+ const state = dataRuntimeStates.get(runtime);
91
+ if (!state) throw new Error("[Askr] data runtime was not created by createDataRuntime().");
92
+ return state;
93
+ }
94
+ function isDataRuntime(value) {
95
+ return typeof value === "object" && value !== null && dataRuntimeStates.has(value);
96
+ }
97
+ function getDataRuntimeForQueryCache(queryCache) {
98
+ let runtime = dataRuntimeByQueryCache.get(queryCache);
99
+ if (!runtime) {
100
+ runtime = createDataRuntime({ queryCache });
101
+ dataRuntimeByQueryCache.set(queryCache, runtime);
102
+ }
103
+ return runtime;
104
+ }
105
+ function getActiveDataRuntime() {
106
+ const ctx = getActiveRenderContext();
107
+ if (isDataRuntime(ctx?.dataRuntime)) return ctx.dataRuntime;
108
+ if (ctx?.queryCache) return getDataRuntimeForQueryCache(ctx.queryCache);
109
+ const appRuntime = currentAppRuntime();
110
+ if (appRuntime?.dataRuntime) return appRuntime.dataRuntime;
111
+ return defaultDataRuntime;
112
+ }
113
+ function getActiveDataRuntimeState() {
114
+ return getDataRuntimeState(getActiveDataRuntime());
115
+ }
116
+ function resolveDataRuntimeState(runtime) {
117
+ return runtime ? getDataRuntimeState(runtime) : getActiveDataRuntimeState();
118
+ }
119
+ /** State for a runtime from createDataRuntime(); undefined for hand-built ones. */
120
+ function findDataRuntimeState(runtime) {
121
+ return dataRuntimeStates.get(runtime);
122
+ }
123
+ /**
124
+ * Read hydrated or prefetched data for a new query cell. Client readers
125
+ * consume it: the cell owns the value from then on, so keeping the entry would
126
+ * revive it as fresh after the cell is gone.
127
+ */
128
+ function readQueryData(runtimeState, key, consume) {
129
+ const value = runtimeState.queryData.get(key);
130
+ if (consume) {
131
+ runtimeState.queryData.delete(key);
132
+ runtimeState.unreadPrefetches.delete(key);
133
+ }
134
+ return value;
135
+ }
136
+ /**
137
+ * Store browser-prefetched data. Unread entries are capped: the oldest one is
138
+ * evicted once more than {@link PREFETCHED_QUERY_DATA_LIMIT} are waiting.
139
+ * Entries replaced or removed through `queryData` directly are no longer
140
+ * tracked and are never evicted by the cap.
141
+ */
142
+ function writePrefetchedQueryData(runtimeState, key, value) {
143
+ const { queryData, unreadPrefetches } = runtimeState;
144
+ queryData.set(key, value);
145
+ unreadPrefetches.delete(key);
146
+ unreadPrefetches.set(key, value);
147
+ for (const [oldestKey, oldestValue] of unreadPrefetches) {
148
+ if (unreadPrefetches.size <= 50) return;
149
+ unreadPrefetches.delete(oldestKey);
150
+ if (queryData.get(oldestKey) === oldestValue) queryData.delete(oldestKey);
151
+ }
152
+ }
153
+ /** Run `fn` for every entry even when some throw; rethrow the failures. */
154
+ function drain(entries, fn) {
155
+ const errors = [];
156
+ for (const entry of Array.from(entries)) try {
157
+ fn(entry);
158
+ } catch (error) {
159
+ errors.push(error);
160
+ }
161
+ if (errors.length === 1) throw errors[0];
162
+ if (errors.length > 1) throw new AggregateError(errors, "Data cleanup failed");
163
+ }
164
+ function getQuerySlotStore(runtimeState, instance) {
165
+ const generation = instance;
166
+ let store = runtimeState.querySlotsByGeneration.get(generation);
167
+ if (!store) {
168
+ store = /* @__PURE__ */ new Map();
169
+ runtimeState.querySlotsByGeneration.set(generation, store);
170
+ }
171
+ return store;
172
+ }
173
+ function getMutationSlotStore(runtimeState, instance) {
174
+ const generation = instance;
175
+ let store = runtimeState.mutationSlotsByGeneration.get(generation);
176
+ if (!store) {
177
+ store = /* @__PURE__ */ new Map();
178
+ runtimeState.mutationSlotsByGeneration.set(generation, store);
179
+ }
180
+ return store;
181
+ }
182
+ function ensureQueryCleanup(runtimeState, instance) {
183
+ const generation = instance;
184
+ if (runtimeState.queryCleanupRegistered.has(generation)) return;
185
+ runtimeState.queryCleanupRegistered.add(generation);
186
+ const slots = getQuerySlotStore(runtimeState, instance);
187
+ instance.onCleanup(() => {
188
+ try {
189
+ drain(slots, ([hookIndex, slot]) => slot.cell.detach(generation, hookIndex));
190
+ } finally {
191
+ slots.clear();
192
+ runtimeState.querySlotsByGeneration.delete(generation);
193
+ runtimeState.queryCleanupRegistered.delete(generation);
194
+ }
195
+ });
196
+ }
197
+ function ensureMutationCleanup(runtimeState, instance) {
198
+ const generation = instance;
199
+ if (runtimeState.mutationCleanupRegistered.has(generation)) return;
200
+ runtimeState.mutationCleanupRegistered.add(generation);
201
+ const slots = getMutationSlotStore(runtimeState, instance);
202
+ instance.onCleanup(() => {
203
+ try {
204
+ drain(slots.values(), (slot) => slot.cell.abort());
205
+ } finally {
206
+ slots.clear();
207
+ runtimeState.mutationSlotsByGeneration.delete(generation);
208
+ runtimeState.mutationCleanupRegistered.delete(generation);
209
+ }
210
+ });
211
+ }
212
+ /**
213
+ * Whether `key` falls under the invalidation `prefix`, matching whole
214
+ * `:`-delimited segments: `user:1` covers `user:1` and `user:1:posts` but not
215
+ * `user:10`. A prefix that already ends in `:` (every `queryScope()` prefix
216
+ * does) covers everything below it, and the empty prefix covers every key.
217
+ */
218
+ function matchesInvalidationPrefix(key, prefix) {
219
+ return key.startsWith(prefix) && (key.length === prefix.length || prefix.length === 0 || prefix.endsWith(":") || key[prefix.length] === ":");
220
+ }
221
+ function invalidateQueriesForRuntime(runtimeState, prefix, markPendingWrite) {
222
+ if (hasInvalidationListeners()) emitInvalidation({
223
+ prefix,
224
+ markPendingWrite
225
+ });
226
+ for (const [key, prefetch] of runtimeState.prefetches) if (matchesInvalidationPrefix(key, prefix)) {
227
+ prefetch.invalidated = true;
228
+ runtimeState.prefetches.delete(key);
229
+ }
230
+ for (const key of runtimeState.queryData.keys()) if (matchesInvalidationPrefix(key, prefix)) {
231
+ runtimeState.queryData.delete(key);
232
+ runtimeState.unreadPrefetches.delete(key);
233
+ }
234
+ const cache = runtimeState.queryCache;
235
+ for (const [key, query] of cache) {
236
+ if (!matchesInvalidationPrefix(key, prefix)) continue;
237
+ if (markPendingWrite) query.markPendingWrite();
238
+ if (!invalidateCollectionCell(query)) query.invalidate();
239
+ }
240
+ }
241
+ //#endregion
242
+ export { getDefaultDataRuntime as a, invalidateQueriesForRuntime as c, writePrefetchedQueryData as d, invalidateCollectionCell as f, addInvalidationListener as h, findDataRuntimeState as i, readQueryData as l, unregisterCollectionCell as m, ensureMutationCleanup as n, getMutationSlotStore as o, registerCollectionCell as p, ensureQueryCleanup as r, getQuerySlotStore as s, createDataRuntime as t, resolveDataRuntimeState as u };
@@ -1,10 +1,9 @@
1
- import { d as getCurrentAppRenderRuntime, f as getCurrentComponentInstance } from "./scope-Cq_iYbTR.js";
2
- import { i as getExecutionModel } from "./manifest-D1P3UNWV.js";
3
- import { n as getActiveRenderContext, r as getCurrentRenderData } from "./render-context-B7ohlh6t.js";
4
- import { t as resource } from "./resource-18-sFRl9.js";
5
- import { S as isDeferred, b as DEFERRED_BOUNDARY, g as guardHydratedRouteData, l as createRouteHandler } from "./resolution-3DskUkjG.js";
6
- import { C as getCurrentInheritedMeta, D as getCurrentPageScope, E as getCurrentPageChain, H as computeRank, I as insertRecordSorted, O as getCurrentPathPrefix, P as hasActivePageScope, R as pushRegistrationScope, S as getCurrentInheritedAuthRequirements, T as getCurrentLayoutChain, U as normalizeRouteSegmentName, W as parseSegments, g as assertRouteRegistrationUnlocked, h as addRouteToStores, j as getDefaultRouteBasePath, k as getCurrentScopeKind, w as getCurrentInheritedPolicies } from "./route-matching-CUzZ31gT.js";
7
- import { allOf } from "@askrjs/auth";
1
+ import { i as currentComponent, r as currentAppRuntime } from "./hooks-BimKStr4.js";
2
+ import { c as registerDeferredBoundary, n as getActiveRenderContext, r as getCurrentRenderData } from "./render-context-DGHnEveC.js";
3
+ import { d as DEFERRED_BOUNDARY, p as isDeferred } from "./query-registry-WMCN0BJL.js";
4
+ import { B as getCurrentPathPrefix, F as getCurrentInheritedMeta, G as getRouteRecords, I as getCurrentInheritedPolicies, J as insertRecordSorted, K as hasActivePageScope, L as getCurrentLayoutChain, O as addRouteToStores, P as getCurrentInheritedAuthRequirements, R as getCurrentPageChain, U as getDefaultRouteBasePath, V as getCurrentScopeKind, X as pushRegistrationScope, dt as routeMatchKey, k as assertRouteRegistrationUnlocked, lt as normalizeRouteSegmentName, pt as guardHydratedRouteData, st as computeRank, tt as createRouteHandler, ut as parseSegments, z as getCurrentPageScope } from "./resolution-Cx82-xjW.js";
5
+ import { i as getExecutionModel } from "./manifest-CW9gyNBY.js";
6
+ import { t as resource } from "./resource-CLjB7RBQ.js";
8
7
  //#region src/router/access.ts
9
8
  function compileNodePolicies(node) {
10
9
  return node.policies ? [...node.policies] : [];
@@ -43,7 +42,11 @@ function normalizeAbsoluteRoutePath(path) {
43
42
  }
44
43
  function joinRoutePaths(prefix, path) {
45
44
  const normalizedPrefix = normalizeAbsoluteRoutePath(prefix || "/");
46
- const normalizedPath = path.replace(/^\/+|\/+$/g, "");
45
+ let start = 0;
46
+ let end = path.length;
47
+ while (start < end && path[start] === "/") start++;
48
+ while (end > start && path[end - 1] === "/") end--;
49
+ const normalizedPath = path.slice(start, end);
47
50
  if (!normalizedPath) return normalizedPrefix;
48
51
  return normalizedPrefix === "/" ? `/${normalizedPath}` : `${normalizedPrefix}/${normalizedPath}`;
49
52
  }
@@ -98,12 +101,13 @@ function normalizeRouteOptions(options) {
98
101
  const dehydrate = options.dehydrate;
99
102
  const preload = options.preload;
100
103
  const policies = compileNodePolicies(options);
101
- if (!loader && !dehydrate && !preload && !options.entries && policies.length === 0 && !options.title && !options.namespace && !options.search && !options.meta && !options.actions && options.auth === void 0) return;
104
+ if (!loader && !dehydrate && !preload && !options.entries && !options.invalidationKeys && policies.length === 0 && !options.title && !options.namespace && !options.search && !options.meta && !options.actions && options.auth === void 0) return;
102
105
  return {
103
106
  ...loader ? { loader } : {},
104
107
  ...dehydrate ? { dehydrate } : {},
105
108
  ...preload ? { preload } : {},
106
109
  ...options.entries ? { entries: options.entries } : {},
110
+ ...options.invalidationKeys ? { invalidationKeys: options.invalidationKeys } : {},
107
111
  ...options.auth !== void 0 ? { auth: options.auth } : {},
108
112
  ...policies.length > 0 ? { policies } : {},
109
113
  ...options.title ? { title: options.title } : {},
@@ -113,18 +117,37 @@ function normalizeRouteOptions(options) {
113
117
  ...options.actions ? { actions: options.actions } : {}
114
118
  };
115
119
  }
120
+ function assertRouteNotDuplicated(path, segments, fallbackPrefix) {
121
+ const key = routeMatchKey(segments, fallbackPrefix);
122
+ const existing = getRouteRecords().find((record) => routeMatchKey(record.segments, record.fallbackPrefix) === key);
123
+ if (existing) {
124
+ const hint = existing.path === path ? " To generate several pages from one route template, declare it once and return each page's params from entries()." : "";
125
+ throw new Error(`Duplicate route path "${path}": it matches the same URLs as "${existing.path}", which is already registered.${hint}`);
126
+ }
127
+ }
128
+ const authAllowed = Object.freeze({ allowed: true });
129
+ function requireAll(requirements) {
130
+ return async (context) => {
131
+ for (const requirement of requirements) {
132
+ const decision = await requirement(context);
133
+ if (!decision.allowed) return decision;
134
+ }
135
+ return authAllowed;
136
+ };
137
+ }
116
138
  function registerRouteAtResolvedPath(path, Component, options, metadata) {
117
139
  validateRoutePath(path);
140
+ const segments = parseSegments(path);
141
+ assertRouteNotDuplicated(path, segments, metadata?.fallbackPrefix);
118
142
  const chain = getCurrentLayoutChain();
119
143
  const pageChain = getCurrentPageChain();
120
- const segments = parseSegments(path);
121
144
  const rank = computeRank(segments);
122
145
  const isFallback = metadata?.isFallback ?? path === "/*";
123
146
  const comp = Component;
124
147
  const normalizedOptions = normalizeRouteOptions(options);
125
148
  const policies = [...getCurrentInheritedPolicies(), ...normalizedOptions?.policies ?? []];
126
149
  const authRequirements = [...getCurrentInheritedAuthRequirements(), ...normalizedOptions?.auth ? [normalizedOptions.auth] : []];
127
- const auth = authRequirements.length === 0 ? void 0 : authRequirements.length === 1 ? authRequirements[0] : allOf(...authRequirements);
150
+ const auth = authRequirements.length === 0 ? void 0 : authRequirements.length === 1 ? authRequirements[0] : requireAll(authRequirements);
128
151
  const metaChain = [...getCurrentInheritedMeta(), ...normalizedOptions?.meta ? [normalizedOptions.meta] : []];
129
152
  const handler = createRouteHandler(comp, pageChain, chain);
130
153
  const renderHandler = createRouteHandler(comp, pageChain, chain, true);
@@ -192,9 +215,9 @@ function fallback(Component) {
192
215
  }
193
216
  function route(path, Component, options) {
194
217
  if (typeof path === "undefined") throw new Error("route() is only for route registration. Use currentRoute() inside components.");
195
- if (getExecutionModel() === "islands") throw new Error("Routes are not supported with islands. Use createSPA (client) or createSSR (server) instead.");
196
- const currentInst = getCurrentComponentInstance();
197
- if (currentInst && currentInst.ssr) throw new Error("route() cannot be called during SSR rendering. Register routes at module load time instead.");
218
+ if (getExecutionModel() === "islands") throw new Error("Routes are not supported with islands. Use createSPA or hydrateSPA with a route registry instead.");
219
+ const currentInst = currentComponent();
220
+ if (currentInst && currentInst.server) throw new Error("route() cannot be called during SSR rendering. Register routes at module load time instead.");
198
221
  assertRouteRegistrationUnlocked();
199
222
  if (typeof Component !== "function") throw new Error("route(path, Component) requires a component function as the second argument. Passing JSX elements or VNodes directly is not supported.");
200
223
  if (options?.search && options.search.kind !== "object") throw new Error("route search must be an object schema.");
@@ -249,7 +272,7 @@ async function resolveDeferredValues(input, signal) {
249
272
  function routeData() {
250
273
  const envelope = getCurrentRenderData();
251
274
  if (envelope) return guardHydratedRouteData(envelope.route, envelope.framework);
252
- const runtime = getCurrentAppRenderRuntime();
275
+ const runtime = currentAppRuntime();
253
276
  if (!runtime?.hasRoute) throw new Error("routeData() can only be read while rendering a route with loader data.");
254
277
  return guardHydratedRouteData(runtime.route, runtime.framework);
255
278
  }
@@ -258,13 +281,12 @@ function rejectedChild(props, error) {
258
281
  }
259
282
  /** Render a {@link Deferred} value's fulfilled state, a pending placeholder, or a rejected fallback. */
260
283
  function Resolve(props) {
284
+ const renderContext = getActiveRenderContext();
285
+ const result = renderContext ? null : resource(() => props.value.state === "pending" ? props.value.promise : null, [props.value]);
261
286
  if (props.value.state === "fulfilled") return props.children(props.value.value);
262
287
  if (props.value.state === "rejected") return rejectedChild(props, props.value.error);
263
- const renderContext = getActiveRenderContext();
264
288
  if (renderContext) {
265
- const id = `d:${renderContext.deferredBoundaries.length}`;
266
- renderContext.deferredBoundaries.push({
267
- id,
289
+ const id = registerDeferredBoundary(renderContext, {
268
290
  promise: props.value.promise,
269
291
  fulfilled: (value) => props.children(value),
270
292
  rejected: (error) => rejectedChild(props, error)
@@ -277,7 +299,7 @@ function Resolve(props) {
277
299
  }
278
300
  };
279
301
  }
280
- const result = resource(() => props.value.promise, [props.value]);
302
+ if (!result) return props.pending ?? null;
281
303
  if (result.error) return rejectedChild(props, result.error);
282
304
  if (result.pending || result.value === null) return props.pending ?? null;
283
305
  return props.children(result.value);
@@ -1,7 +1,6 @@
1
1
  import { JSXElementType, JSXElement, Props } from '../elements.js';
2
2
  import '../jsx-globals.js';
3
- import { AuthContext, AuthRequirement } from '@askrjs/auth';
4
- import { InferSchema, ObjectSchema } from '@askrjs/schema';
3
+ import { InferSchema, ObjectSchema } from '../peer-types.js';
5
4
  import { StateTuple, state } from './state.js';
6
5
  import { RenderableChild } from './context.js';
7
6
  import { on } from './lifecycle.js';
@@ -50,7 +49,13 @@ interface ActionStatus<TResult = unknown> {
50
49
  readonly error?: unknown;
51
50
  }
52
51
 
53
- /** Returns a command handle, rather than a hook. */
52
+ /**
53
+ * Bind an action descriptor to the calling component and return its
54
+ * `{ state, submit }` handle. `action()` allocates its status with `state()`,
55
+ * so it follows the same rule: call it during component render, at the top
56
+ * level and in the same order on every render. `submit()` itself may be
57
+ * called from event handlers or other async work.
58
+ */
54
59
  declare function action<
55
60
  TInput extends Record<string, unknown>,
56
61
  TResult = unknown,
@@ -1,84 +1,4 @@
1
- import { JSXElementType, JSXElement, Props } from '../elements.js';
2
1
  import '../jsx-globals.js';
3
- import { AuthContext, AuthRequirement } from '@askrjs/auth';
4
- import { InferSchema, ObjectSchema } from '@askrjs/schema';
5
- import { State, state } from './state.js';
6
- import { ComponentFunction, ContextFrame, OwnedChildScope } from './context.js';
7
- import { DataRuntime } from './data.js';
8
- import { RouteAuthOptions, RouteRegistry } from './routing.js';
9
-
10
- interface AppRenderRuntime {
11
- framework: Readonly<Record<string, unknown>>;
12
- route: unknown;
13
- hasRoute: boolean;
14
- dataRuntime?: DataRuntime;
15
- routeRegistry?: RouteRegistry;
16
- routeAuth?: RouteAuthOptions;
17
- }
18
-
19
- interface ComponentInstance {
20
- id: string;
21
- fn: ComponentFunction;
22
- props: Props;
23
- target: Element | null;
24
- parentInstance: ComponentInstance | null;
25
- portalScope: object | null;
26
- mounted: boolean;
27
- abortController: AbortController | null;
28
- ssr?: boolean;
29
- cleanupStrict?: boolean;
30
- /** @internal Private resource-ownership identity for the active mount. */
31
- _ownershipGeneration: object;
32
- stateValues?: State<unknown>[];
33
- evaluationGeneration: number;
34
- notifyUpdate: (() => void) | null;
35
- _pendingFlushTask?: () => void;
36
- _pendingRunTask?: () => void;
37
- _enqueueRun?: () => void;
38
- stateIndexCheck: number;
39
- expectedStateIndices?: number[];
40
- firstRenderComplete: boolean;
41
- mountOperations?: Array<
42
- () => void | (() => void) | PromiseLike<void | (() => void)>
43
- >;
44
- commitOperations?: Array<
45
- () => void | (() => void) | PromiseLike<void | (() => void)>
46
- >;
47
- cleanupFns?: Array<() => void>;
48
- lifecycleSlots?: unknown[];
49
- lifecycleGeneration: number;
50
- hasPendingUpdate: boolean;
51
- ownerFrame: ContextFrame | null;
52
- isRoot?: boolean;
53
- _rootComponentFn?: ComponentFunction;
54
- /** @internal Browser-owned hydration and route state for this app root. */
55
- _appRenderRuntime?: AppRenderRuntime;
56
- /** @internal CSP nonce retained across browser route navigation. */
57
- _cspNonce?: string;
58
- _vnodeOwner?: object;
59
- _vnodeParent?: ComponentInstance | null;
60
- _vnodeParentGeneration?: object;
61
- _vnodeKey?: string | number;
62
- _vnodePosition?: number;
63
- _wrapperDepth?: number;
64
- _currentRenderToken?: number;
65
- lastRenderToken?: number;
66
- _pendingReadSources?: Set<ReadableSource<unknown>>;
67
- _pendingReadSourceVersions?: Map<ReadableSource<unknown>, number>;
68
- _lastReadSources?: Set<ReadableSource<unknown>>;
69
- devWarningsEmitted?: Set<string>;
70
- _placeholder?: Comment;
71
- _ownedChildScopes?: Set<OwnedChildScope>;
72
- errorBoundaryState?: {
73
- error: unknown | null;
74
- resetKey: unknown;
75
- notified: boolean;
76
- };
77
- /** @internal Logical error ancestry for content materialized by a portal host. */
78
- _portalErrorParent?: ComponentInstance | null;
79
- /** @internal Ownership identity paired with `_portalErrorParent`. */
80
- _portalErrorParentGeneration?: object;
81
- }
82
2
 
83
3
  /**
84
4
  * Get the abort signal for the current component.
@@ -90,30 +10,4 @@ interface ComponentInstance {
90
10
  */
91
11
  declare function getSignal(): AbortSignal;
92
12
 
93
- interface DerivedSubscriber {
94
- _markDirty(): void;
95
- _pendingDependencySources?: Set<ReadableSource<unknown>>;
96
- }
97
-
98
- interface ReadableSource<T = unknown> {
99
- (): T;
100
- _hasBeenRead?: boolean;
101
- _hasEverBeenRead?: boolean;
102
- _unusedStateDiagnosticEligible?: boolean;
103
- _readers?: Map<
104
- ComponentInstance,
105
- {
106
- token: number;
107
- generation: object;
108
- }
109
- >;
110
- _derivedSubscribers?: Set<DerivedSubscriber>;
111
- _version?: number;
112
- }
113
- export {
114
- AppRenderRuntime,
115
- ComponentInstance,
116
- getSignal,
117
- DerivedSubscriber,
118
- ReadableSource,
119
- };
13
+ export { getSignal };
@@ -1,9 +1,6 @@
1
1
  import { JSXElementType, JSXElement, Props } from '../elements.js';
2
2
  import '../jsx-globals.js';
3
- import { AuthContext, AuthRequirement } from '@askrjs/auth';
4
- import { InferSchema, ObjectSchema } from '@askrjs/schema';
5
3
  import { SSRContext } from './server.js';
6
- import { ControlBoundaryState } from './control.js';
7
4
 
8
5
  interface DOMElement {
9
6
  type: JSXElementType;
@@ -11,7 +8,6 @@ interface DOMElement {
11
8
  children?: VNode[];
12
9
  key?: string | number | null;
13
10
  [Symbol.iterator]?: never;
14
- _controlState?: ControlBoundaryState;
15
11
  }
16
12
 
17
13
  type VNode = DOMElement | string | number | boolean | null | undefined;
@@ -41,21 +37,12 @@ interface Scope<T> {
41
37
  readonly defaultValue: T;
42
38
  }
43
39
 
44
- interface ContextFrame {
45
- parent: ContextFrame | null;
46
- values: Map<ContextKey, unknown> | null;
47
- }
48
-
49
40
  /** Create a new lexical {@link Scope} with `defaultValue`, readable via {@link readScope}. */
50
41
  declare function defineScope<T>(defaultValue: T): Scope<T>;
51
42
 
52
43
  /** Read the current value of a {@link Scope} during component render or an async resource. */
53
44
  declare function readScope<T>(context: Scope<T>): T;
54
45
 
55
- type OwnedChildScope = {
56
- key: string | number;
57
- dispose(): void;
58
- };
59
46
  export {
60
47
  DOMElement,
61
48
  VNode,
@@ -66,8 +53,6 @@ export {
66
53
  Renderable,
67
54
  ContextScopeChildren,
68
55
  Scope,
69
- ContextFrame,
70
56
  defineScope,
71
57
  readScope,
72
- OwnedChildScope,
73
58
  };