@askrjs/askr 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (123) hide show
  1. package/CHANGELOG.md +874 -0
  2. package/README.md +61 -29
  3. package/capabilities.json +19 -9
  4. package/dist/actions/index.js +56 -12
  5. package/dist/benchmark.js +10 -15
  6. package/dist/boot/index.d.ts +3 -2
  7. package/dist/boot/index.js +1 -1
  8. package/dist/boot-DaoTYoCw.js +3989 -0
  9. package/dist/browser-activity-CB8e-V4f.js +220 -0
  10. package/dist/components/index.d.ts +8 -1
  11. package/dist/components/index.js +1 -43
  12. package/dist/{aria-hKjThHcp.js → compose-ref-DWzdbxsD.js} +41 -4
  13. package/dist/control/index.d.ts +2 -0
  14. package/dist/control/index.js +1 -1
  15. package/dist/control-nfpT2fbK.js +209 -0
  16. package/dist/core.d.ts +5 -9
  17. package/dist/csp-nonce-DwGw1A6Q.js +35 -0
  18. package/dist/data/index.js +3 -3
  19. package/dist/{data-2edW0jhF.js → data-BCwClJiF.js} +283 -94
  20. package/dist/data-runtime-C1bFAlba.js +242 -0
  21. package/dist/{deferred-CbgNhf67.js → deferred-bEEHHvic.js} +42 -20
  22. package/dist/domains/actions.d.ts +8 -3
  23. package/dist/domains/component.d.ts +1 -107
  24. package/dist/domains/context.d.ts +0 -15
  25. package/dist/domains/control.d.ts +36 -249
  26. package/dist/domains/data.d.ts +31 -13
  27. package/dist/domains/diagnostics.d.ts +1 -2
  28. package/dist/domains/lifecycle.d.ts +2 -5
  29. package/dist/domains/refs.d.ts +0 -2
  30. package/dist/domains/routing.d.ts +43 -8
  31. package/dist/domains/scheduler.d.ts +1 -76
  32. package/dist/domains/server.d.ts +5 -2
  33. package/dist/domains/state.d.ts +2 -5
  34. package/dist/domains/telemetry.d.ts +0 -2
  35. package/dist/elements.d.ts +214 -51
  36. package/dist/error-boundary-Bpwofwps.js +293 -0
  37. package/dist/foundations/icon/index.d.ts +0 -58
  38. package/dist/foundations/icon/index.js +2 -2
  39. package/dist/foundations/index.js +2 -1
  40. package/dist/foundations/interactions/index.js +1 -1
  41. package/dist/foundations/state/index.d.ts +3 -3
  42. package/dist/foundations/state/index.js +5 -3
  43. package/dist/foundations/structures/index.js +2 -1
  44. package/dist/foundations/utilities/index.d.ts +2 -0
  45. package/dist/foundations/utilities/index.js +1 -2
  46. package/dist/fx/index.d.ts +31 -58
  47. package/dist/fx/index.js +313 -195
  48. package/dist/hooks-BimKStr4.js +202 -0
  49. package/dist/index.d.ts +11 -74
  50. package/dist/index.js +8 -447
  51. package/dist/instance-BAnfmXOR.js +988 -0
  52. package/dist/{interactions-CTeACdT_.js → interactions-D2BoREb-.js} +3 -2
  53. package/dist/jsx-dev-runtime.d.ts +41 -18
  54. package/dist/jsx-dev-runtime.js +1 -6
  55. package/dist/{jsx-runtime-CPJXAOBF.js → jsx-runtime-C4idqHY0.js} +18 -7
  56. package/dist/jsx-runtime.js +1 -1
  57. package/dist/jsx.d.ts +61 -37
  58. package/dist/{manifest-D1P3UNWV.js → manifest-CW9gyNBY.js} +3 -3
  59. package/dist/{navigate-prC5V8XV.js → navigate-CP_0Zy5U.js} +454 -87
  60. package/dist/navigation.d.ts +12 -3
  61. package/dist/peer-types.d.ts +8 -0
  62. package/dist/portal-DuOKfnLa.js +349 -0
  63. package/dist/promise-Dz8iIHAm.js +16 -0
  64. package/dist/query-registry-WMCN0BJL.js +320 -0
  65. package/dist/readable-DFWOsTac.js +21 -0
  66. package/dist/{render-context-B7ohlh6t.js → render-context-DGHnEveC.js} +27 -11
  67. package/dist/render-sync-IC5lMFpq.js +1240 -0
  68. package/dist/resolution-Cx82-xjW.js +1244 -0
  69. package/dist/resource-CLjB7RBQ.js +377 -0
  70. package/dist/resources/index.d.ts +21 -11
  71. package/dist/resources/index.js +45 -244
  72. package/dist/router/index.d.ts +14 -5
  73. package/dist/router/index.js +22 -53
  74. package/dist/scope-CwpVSHVf.js +55 -0
  75. package/dist/{shared-kU8yvryp.js → shared-O2ZnmYAf.js} +1 -7
  76. package/dist/{snapshot-source-An4iKajG.js → snapshot-Cjx4N988.js} +6 -1
  77. package/dist/ssg/index.d.ts +1 -2
  78. package/dist/ssg/index.js +5 -13
  79. package/dist/ssr/index.d.ts +26 -28
  80. package/dist/ssr/index.js +4 -4
  81. package/dist/{ssr-CiNm6Vzl.js → ssr-BJ8ug7zH.js} +133 -122
  82. package/dist/ssr-DoV-vJ-5.js +229 -0
  83. package/dist/state-DWO1KzkX.js +232 -0
  84. package/dist/structures-D4dDMqHX.js +231 -0
  85. package/dist/testing/index.d.ts +8 -4
  86. package/dist/testing/index.js +29 -34
  87. package/dist/url-CnbfpX0K.js +185 -0
  88. package/dist/utilities.d.ts +48 -2
  89. package/dist/verify-hydration-DpVaDnqz.js +152 -0
  90. package/package.json +19 -9
  91. package/dist/access-CEA2pOry.js +0 -1046
  92. package/dist/activity-oy2YFTFF.js +0 -201
  93. package/dist/boot-BMBMWUGI.js +0 -1084
  94. package/dist/capabilities-Bbf0mlp9.js +0 -42
  95. package/dist/compose-ref-v0qGlHoN.js +0 -26
  96. package/dist/control-BR_mrX__.js +0 -173
  97. package/dist/control-C-ApnFs1.js +0 -20
  98. package/dist/csp-nonce-BfFWcd0w.js +0 -173
  99. package/dist/data-runtime-CxoY98lX.js +0 -149
  100. package/dist/default-portal-runtime-DvMvWJVp.js +0 -20
  101. package/dist/dom-renderer.d.ts +0 -77
  102. package/dist/domains/renderer.d.ts +0 -127
  103. package/dist/eager-control.d.ts +0 -8
  104. package/dist/effect-C3Zsc1k6.js +0 -292
  105. package/dist/for-state-BDykeOJL.js +0 -1753
  106. package/dist/instance-VAFYVJOa.js +0 -1027
  107. package/dist/notify-DxxuDPeY.js +0 -266
  108. package/dist/portal-w3oQ6oQU.js +0 -29
  109. package/dist/query-registry-LrYpQsDJ.js +0 -83
  110. package/dist/render-resolved-BC0W7_5b.js +0 -1123
  111. package/dist/renderer-CT0HeY58.js +0 -8623
  112. package/dist/resolution-3DskUkjG.js +0 -577
  113. package/dist/resource-18-sFRl9.js +0 -344
  114. package/dist/route-matching-CUzZ31gT.js +0 -551
  115. package/dist/router-internal-7nuNEHfG.js +0 -36
  116. package/dist/scope-Cq_iYbTR.js +0 -403
  117. package/dist/selector-BSPVkjKP.js +0 -311
  118. package/dist/ssr-EPjwJH5R.js +0 -29
  119. package/dist/state-BXJfudq1.js +0 -91
  120. package/dist/structures-CjEzfj4h.js +0 -654
  121. package/dist/timer-C7uqnJVt.js +0 -85
  122. package/dist/verify-hydration-ikaxotf7.js +0 -27
  123. package/dist/vnode-D24p6IW2.js +0 -7
@@ -1,1084 +0,0 @@
1
- import { T as isProductionEnvironment, i as getRuntimeCleanup, l as installRuntimeRenderer, r as flushRuntimeScheduler, v as logger, w as isDevelopmentEnvironment } from "./access-CEA2pOry.js";
2
- import { D as createComponentOwnership, E as cleanupComponentGeneration, F as captureGenerationExecution, N as assertSyncComponentResult, T as cleanupComponent, k as resetComponentWork, n as createComponentInstance, r as executeComponent } from "./instance-VAFYVJOa.js";
3
- import { a as registerDeferredHydrationBoundary, c as beginHydrationDirectListenerMode, d as commitHydrationListenerTransaction, f as discardHydrationListenerTransaction, g as teardownNodeSubtree, h as removeAllListeners, i as clearDeferredHydrationBoundaries, l as endHydrationDirectListenerMode, n as activateHydrationBoundary$1, o as setStaticChildSlotsCacheEnabled, s as withIntrinsicHydrationAdoption, t as createRendererCapabilities, u as beginHydrationListenerTransaction } from "./renderer-CT0HeY58.js";
4
- import { b as attachOwnership } from "./scope-Cq_iYbTR.js";
5
- import { m as cleanupReadableSubscriptionSources } from "./notify-DxxuDPeY.js";
6
- import { n as Fragment, t as ELEMENT_TYPE } from "./jsx-DRS-qjiN.js";
7
- import { r as assertExecutionModel, t as _applyManifest } from "./manifest-D1P3UNWV.js";
8
- import { d as stopHydrationRenderPhase, l as startHydrationRenderPhase, y as pageRenderEnvelope } from "./render-context-B7ohlh6t.js";
9
- import { r as validateCspNonce, t as CspNonceScope } from "./csp-nonce-BfFWcd0w.js";
10
- import { i as getDefaultDataRuntime } from "./data-runtime-CxoY98lX.js";
11
- import { a as hydrateDataRuntime } from "./query-registry-LrYpQsDJ.js";
12
- import { _ as configureRootUpdateHost, f as registerAppInstance, g as unregisterAppInstance, i as resolveRouteMeta, o as configureScrollRestoration, r as reconcileRouteMeta, t as initializeNavigation } from "./navigate-prC5V8XV.js";
13
- import { n as disposeRegisteredDefaultPortalScope, r as getDefaultPortalHost, t as clearRegisteredDefaultPortalForInstance } from "./default-portal-runtime-DvMvWJVp.js";
14
- import { a as clearStagedAppRenderRouteLocation, o as createAppRenderRuntime, r as setServerLocation, s as stageAppRenderRouteLocation } from "./activity-oy2YFTFF.js";
15
- import { C as reviveDeferredValue, a as resolveRouteRequest, i as hasRouteRenderData, n as getRouteRenderContext, r as getRouteRenderData, y as withoutRouteHydrationMetadata } from "./resolution-3DskUkjG.js";
16
- import { F as hasRegisteredRoutes, L as lockRouteRegistration, _ as clearRouteState, m as _setActiveRouteAuthOptions } from "./route-matching-CUzZ31gT.js";
17
- import "./ssr-EPjwJH5R.js";
18
- //#region src/runtime/component/generation.ts
19
- /** Reuse an execution record after boot has retired its previous lifetime. */
20
- function restartComponentGeneration(instance, fn, preserveState) {
21
- instance.owner = createComponentOwnership(instance);
22
- instance.fn = fn;
23
- instance.evaluationGeneration++;
24
- instance.expectedStateIndices = [];
25
- instance.firstRenderComplete = false;
26
- if (!preserveState) {
27
- resetComponentWork(instance);
28
- instance.stateValues = [];
29
- instance.stateIndexCheck = -1;
30
- instance._currentRenderToken = void 0;
31
- instance.lastRenderToken = 0;
32
- instance.errorBoundaryState = void 0;
33
- instance._portalErrorParent = void 0;
34
- instance._portalErrorParentGeneration = void 0;
35
- instance.devWarningsEmitted = void 0;
36
- }
37
- }
38
- /** Runtime-owned preparation and retirement. A host may restore its own state
39
- * between disposing provisional work and restoring the previous execution. */
40
- function captureComponentGeneration(instance) {
41
- const restoreHostIndex = getRuntimeCleanup().captureComponentHost(instance);
42
- const snapshot = captureGenerationExecution(instance);
43
- const reads = instance.owner.reads;
44
- const readerEntries = Array.from(reads ?? [], (source) => ({
45
- source,
46
- reader: source._readers?.get(instance)
47
- }));
48
- let settled = false;
49
- return {
50
- prepare(fn, props) {
51
- if (settled || instance.owner !== snapshot.owner) throw new Error("[Askr] component generation is no longer available for preparation");
52
- const owner = createComponentOwnership(instance);
53
- owner.mounted = snapshot.owner.mounted;
54
- instance.owner = owner;
55
- attachOwnership(owner, snapshot.owner.parent);
56
- instance.fn = fn;
57
- instance.props = props;
58
- resetComponentWork(instance);
59
- instance.stateValues = [];
60
- instance.expectedStateIndices = [];
61
- instance.firstRenderComplete = false;
62
- instance.stateIndexCheck = -1;
63
- instance.evaluationGeneration++;
64
- instance.lifecycleGeneration++;
65
- instance.mountOperations = [];
66
- instance.commitOperations = [];
67
- instance.lifecycleSlots = [];
68
- instance._currentRenderToken = void 0;
69
- instance.lastRenderToken = 0;
70
- },
71
- rollback(restoreHost) {
72
- if (settled) return [];
73
- settled = true;
74
- const errors = [];
75
- const provisional = instance.owner;
76
- if (provisional !== snapshot.owner) {
77
- try {
78
- cleanupReadableSubscriptionSources(instance, provisional.reads, provisional.identity);
79
- } catch (error) {
80
- errors.push(error);
81
- }
82
- provisional.reads = void 0;
83
- try {
84
- cleanupComponent(instance);
85
- } catch (error) {
86
- errors.push(error);
87
- }
88
- }
89
- try {
90
- errors.push(...restoreHost());
91
- } catch (error) {
92
- errors.push(error);
93
- }
94
- Object.assign(instance, snapshot);
95
- try {
96
- restoreHostIndex?.();
97
- } catch (error) {
98
- errors.push(error);
99
- }
100
- snapshot.owner.reads = reads;
101
- for (const { source, reader } of readerEntries) {
102
- if (!reader) {
103
- if (source._readers?.delete(instance) && false);
104
- continue;
105
- }
106
- const readers = source._readers ??= /* @__PURE__ */ new Map();
107
- readers.has(instance);
108
- readers.set(instance, reader);
109
- }
110
- return errors;
111
- },
112
- retire() {
113
- if (settled) return;
114
- settled = true;
115
- cleanupComponentGeneration(instance, snapshot.owner);
116
- }
117
- };
118
- }
119
- //#endregion
120
- //#region src/renderer/ownership/root-snapshot.ts
121
- function captureRootTree(root) {
122
- if (!root) return null;
123
- const nodes = [];
124
- const visit = (node) => {
125
- nodes.push({
126
- node,
127
- children: Array.from(node.childNodes),
128
- attributes: node instanceof Element ? Array.from(node.attributes).map((attribute) => [attribute.name, attribute.value]) : null,
129
- nodeValue: node.nodeValue
130
- });
131
- for (const child of Array.from(node.childNodes)) visit(child);
132
- };
133
- visit(root);
134
- return {
135
- root,
136
- nodes
137
- };
138
- }
139
- function collectProvisionalRootNodes(snapshot) {
140
- const originalNodes = new Set(snapshot.nodes.map((entry) => entry.node));
141
- const provisional = [];
142
- const visit = (node) => {
143
- if (!originalNodes.has(node)) {
144
- provisional.push(node);
145
- return;
146
- }
147
- for (const child of Array.from(node.childNodes)) visit(child);
148
- };
149
- for (const child of Array.from(snapshot.root.childNodes)) visit(child);
150
- return provisional;
151
- }
152
- function restoreRootTree(snapshot) {
153
- if (!snapshot) return [];
154
- const errors = [];
155
- for (const node of collectProvisionalRootNodes(snapshot)) try {
156
- teardownNodeSubtree(node, { strict: true });
157
- } catch (error) {
158
- errors.push(error);
159
- }
160
- for (const entry of snapshot.nodes) {
161
- const { node, attributes } = entry;
162
- try {
163
- if (attributes && node instanceof Element) {
164
- const expected = new Map(attributes);
165
- for (const attribute of Array.from(node.attributes)) if (!expected.has(attribute.name)) node.removeAttribute(attribute.name);
166
- for (const [name, value] of attributes) if (node.getAttribute(name) !== value) node.setAttribute(name, value);
167
- } else if (!(node instanceof Element)) node.nodeValue = entry.nodeValue;
168
- if (node instanceof Element || node instanceof DocumentFragment) node.replaceChildren(...entry.children);
169
- } catch (error) {
170
- errors.push(error);
171
- }
172
- }
173
- return errors;
174
- }
175
- function captureRootHost(root) {
176
- const snapshot = captureRootTree(root);
177
- return { restore: () => restoreRootTree(snapshot) };
178
- }
179
- //#endregion
180
- //#region src/boot/root-handler.ts
181
- function wrapRootRouteHandler(componentFn, cspNonce) {
182
- const wrappedFn = (props, ctx) => {
183
- const out = componentFn(props, ctx);
184
- assertSyncComponentResult(out);
185
- const portalVNode = {
186
- $$typeof: ELEMENT_TYPE,
187
- type: getDefaultPortalHost(),
188
- props: { __askrAutoDefaultPortal: true },
189
- key: "__default_portal"
190
- };
191
- const root = {
192
- $$typeof: ELEMENT_TYPE,
193
- type: Fragment,
194
- props: { children: out === void 0 || out === null ? [portalVNode] : [out, portalVNode] }
195
- };
196
- return cspNonce === void 0 ? root : CspNonceScope({
197
- value: cspNonce,
198
- children: root
199
- });
200
- };
201
- Object.defineProperty(wrappedFn, "name", { value: componentFn.name || "Component" });
202
- return wrappedFn;
203
- }
204
- //#endregion
205
- //#region src/boot/root-update.ts
206
- function prepare(root, input) {
207
- const instance = root;
208
- const generation = captureComponentGeneration(instance);
209
- const host = captureRootHost(instance.target);
210
- const current = instance._appRenderRuntime;
211
- const runtime = createAppRenderRuntime({
212
- framework: withoutRouteHydrationMetadata(current?.framework),
213
- dataRuntime: current?.dataRuntime,
214
- routeRegistry: current?.routeRegistry,
215
- routeAuth: current?.routeAuth,
216
- route: input.routeData,
217
- hasRoute: input.hasRouteData
218
- });
219
- let applied = false;
220
- let settled = false;
221
- return {
222
- apply() {
223
- if (applied) return;
224
- applied = true;
225
- stageAppRenderRouteLocation(runtime, input.href);
226
- instance._appRenderRuntime = runtime;
227
- if (input.replaceLifetime) {
228
- clearRegisteredDefaultPortalForInstance(instance);
229
- generation.prepare(wrapRootRouteHandler(input.handler, instance._cspNonce), {});
230
- executeComponent(instance);
231
- } else instance._enqueueRun?.();
232
- },
233
- publish() {
234
- clearStagedAppRenderRouteLocation(runtime);
235
- },
236
- rollback() {
237
- if (settled) return [];
238
- settled = true;
239
- clearStagedAppRenderRouteLocation(runtime);
240
- return applied ? generation.rollback(() => host.restore()) : [];
241
- },
242
- retire() {
243
- if (settled) return [];
244
- settled = true;
245
- const errors = [];
246
- if (applied && input.replaceLifetime) try {
247
- generation.retire();
248
- } catch (error) {
249
- errors.push(error);
250
- }
251
- return errors;
252
- }
253
- };
254
- }
255
- function installRootUpdateHost() {
256
- configureRootUpdateHost({ prepare });
257
- }
258
- //#endregion
259
- //#region src/boot/runtime-wiring.ts
260
- let bridged = false;
261
- /**
262
- * Browser composition owns the connection between execution and rendering.
263
- *
264
- * Installs unconditionally: importing the boot entry means native boot owns the
265
- * renderer, replacing whatever a consumer had configured before that point.
266
- */
267
- function installRendererBridge() {
268
- bridged = true;
269
- installRuntimeRenderer(createRendererCapabilities());
270
- return true;
271
- }
272
- /**
273
- * Connect execution to the DOM renderer only if nothing has yet.
274
- *
275
- * Unlike the install above this leaves an already-installed host alone, so a
276
- * renderer a consumer configured after composing boot survives. Kept in this
277
- * module rather than the composition root so a caller that only needs
278
- * rendering — driving the runtime directly, as the jsdom test environment does
279
- * — does not pull root-lifetime wiring into its module graph.
280
- */
281
- function ensureRendererBridge() {
282
- if (bridged) return;
283
- installRendererBridge();
284
- }
285
- //#endregion
286
- //#region src/boot/composition.ts
287
- let rootUpdateHosted = false;
288
- function ensureRootUpdateHost() {
289
- if (rootUpdateHosted) return;
290
- rootUpdateHosted = true;
291
- installRootUpdateHost();
292
- }
293
- /**
294
- * Browser composition root, owned by the boot entry.
295
- *
296
- * Wiring a host is composition, not an import side effect of an implementation
297
- * module: when these calls lived at the top of root-lifecycle.ts, the import
298
- * order of unrelated modules decided whether a host existed. An entry barrel is
299
- * where composition belongs, so importing `@askrjs/askr/boot` installs the
300
- * native renderer — that is the contract consumers rely on.
301
- */
302
- function composeBrowserRuntime() {
303
- installRendererBridge();
304
- ensureRootUpdateHost();
305
- }
306
- /**
307
- * Compose whatever is still missing, for a mount reached without the boot entry.
308
- *
309
- * Non-clobbering: a renderer the consumer configured after composing boot stays
310
- * installed across mounts.
311
- */
312
- function ensureBrowserRuntime() {
313
- ensureRendererBridge();
314
- ensureRootUpdateHost();
315
- }
316
- //#endregion
317
- //#region src/boot/root-element.ts
318
- function resolveRootElement(root) {
319
- if (typeof root !== "string") return root;
320
- const id = root.startsWith("#") ? root.slice(1) : root;
321
- return document.getElementById(id);
322
- }
323
- //#endregion
324
- //#region src/boot/root-lifecycle.ts
325
- let componentIdCounter = 0;
326
- const instancesByRoot = /* @__PURE__ */ new WeakMap();
327
- const routedRoots = /* @__PURE__ */ new Set();
328
- const CLEANUP_SYMBOL = Symbol.for("__askrCleanup__");
329
- const ROOT_CLEANUP_CALLBACKS_SYMBOL = Symbol.for("__askrRootCleanupCallbacks__");
330
- function clearRootCleanupCallbacks(rootElement) {
331
- if (!Reflect.deleteProperty(rootElement, ROOT_CLEANUP_CALLBACKS_SYMBOL)) rootElement[ROOT_CLEANUP_CALLBACKS_SYMBOL]?.clear();
332
- }
333
- function registerRootCleanupCallback(rootElement, callback) {
334
- const elementWithCleanup = rootElement;
335
- const callbacks = elementWithCleanup[ROOT_CLEANUP_CALLBACKS_SYMBOL] ?? /* @__PURE__ */ new Set();
336
- callbacks.add(callback);
337
- elementWithCleanup[ROOT_CLEANUP_CALLBACKS_SYMBOL] = callbacks;
338
- return () => {
339
- callbacks.delete(callback);
340
- if (callbacks.size === 0 && elementWithCleanup[ROOT_CLEANUP_CALLBACKS_SYMBOL] === callbacks) clearRootCleanupCallbacks(rootElement);
341
- };
342
- }
343
- function cleanupRootInstance(rootElement, instance, options) {
344
- if (instancesByRoot.get(rootElement) !== instance) return;
345
- const element = rootElement;
346
- const cleanup = element[CLEANUP_SYMBOL];
347
- const callbacks = element[ROOT_CLEANUP_CALLBACKS_SYMBOL];
348
- const wasRoutedRoot = routedRoots.delete(rootElement);
349
- instancesByRoot.delete(rootElement);
350
- clearRootCleanupCallbacks(rootElement);
351
- Reflect.deleteProperty(element, CLEANUP_SYMBOL);
352
- clearDeferredHydrationBoundaries(rootElement);
353
- const errors = [];
354
- for (const phase of [[() => teardownNodeSubtree(rootElement), () => cleanupComponent(instance)], callbacks ?? []]) for (const callback of phase) try {
355
- callback();
356
- } catch (e) {
357
- errors.push(e);
358
- }
359
- if (options?.preserveInstance && !instancesByRoot.has(rootElement)) {
360
- instancesByRoot.set(rootElement, instance);
361
- element[CLEANUP_SYMBOL] = cleanup;
362
- if (wasRoutedRoot) routedRoots.add(rootElement);
363
- } else unregisterAppInstance(instance);
364
- if (wasRoutedRoot && routedRoots.size === 0) clearRouteState();
365
- if (errors.length > 0) {
366
- if (instance.cleanupStrict) throw new AggregateError(errors, `cleanup failed for app root`);
367
- else if (isDevelopmentEnvironment()) for (const err of errors) logger.warn("[Askr] cleanup error:", err);
368
- }
369
- }
370
- function attachCleanupForRoot(rootElement, instance) {
371
- rootElement[CLEANUP_SYMBOL] = (options) => {
372
- cleanupRootInstance(rootElement, instance, options);
373
- };
374
- registerRootCleanupCallback(rootElement, () => {
375
- disposeRegisteredDefaultPortalScope(instance.portalScope ?? instance);
376
- });
377
- try {
378
- const descriptor = Object.getOwnPropertyDescriptor(rootElement, "innerHTML") || Object.getOwnPropertyDescriptor(Object.getPrototypeOf(rootElement), "innerHTML") || Object.getOwnPropertyDescriptor(Element.prototype, "innerHTML");
379
- if (descriptor && (descriptor.get || descriptor.set)) Object.defineProperty(rootElement, "innerHTML", {
380
- get: function() {
381
- return descriptor.get?.call(this);
382
- },
383
- set: function(value) {
384
- if (value === "" && instancesByRoot.get(this) === instance) {
385
- cleanupRootInstance(rootElement, instance);
386
- if (instancesByRoot.has(this)) return;
387
- }
388
- return descriptor.set?.call(this, value);
389
- },
390
- configurable: true
391
- });
392
- } catch {}
393
- }
394
- function mountOrUpdate(rootElement, componentFn, options) {
395
- ensureBrowserRuntime();
396
- const nonce = validateCspNonce(options?.cspNonce);
397
- const wrappedFn = wrapRootRouteHandler(componentFn, nonce);
398
- const existingCleanup = rootElement[CLEANUP_SYMBOL];
399
- const reusedExistingInstance = typeof existingCleanup === "function";
400
- if (reusedExistingInstance) {
401
- existingCleanup({ preserveInstance: true });
402
- if (rootElement[CLEANUP_SYMBOL] !== existingCleanup) return;
403
- }
404
- let instance = instancesByRoot.get(rootElement);
405
- if (instance) {
406
- const shouldResetHookState = instance._rootComponentFn !== componentFn;
407
- if (!reusedExistingInstance) {
408
- removeAllListeners(rootElement);
409
- try {
410
- cleanupComponent(instance);
411
- } catch (e) {
412
- if (isDevelopmentEnvironment()) logger.warn("[Askr] prior cleanup threw:", e);
413
- }
414
- }
415
- restartComponentGeneration(instance, wrappedFn, !shouldResetHookState);
416
- } else {
417
- const componentId = String(++componentIdCounter);
418
- instance = createComponentInstance(componentId, wrappedFn, {}, rootElement, null);
419
- instancesByRoot.set(rootElement, instance);
420
- }
421
- instance.isRoot = true;
422
- instance._rootComponentFn = componentFn;
423
- instance.portalScope = instance;
424
- instance._appRenderRuntime = options?.appRuntime;
425
- instance._cspNonce = nonce;
426
- if (options && typeof options.cleanupStrict === "boolean") instance.cleanupStrict = options.cleanupStrict;
427
- attachCleanupForRoot(rootElement, instance);
428
- executeComponent(instance);
429
- flushRuntimeScheduler();
430
- }
431
- /** @internal Activate one deferred boundary without rerunning its root. */
432
- function activateHydrationBoundary(rootElement, boundary) {
433
- if (!instancesByRoot.has(rootElement)) return false;
434
- const activated = activateHydrationBoundary$1(boundary);
435
- if (activated) flushRuntimeScheduler();
436
- return activated;
437
- }
438
- async function registerAppNavigation(rootElement, path, source) {
439
- ensureBrowserRuntime();
440
- const instance = instancesByRoot.get(rootElement);
441
- if (!instance) throw new Error("Internal error: app instance missing");
442
- routedRoots.add(rootElement);
443
- registerAppInstance(instance, path, source);
444
- initializeNavigation();
445
- }
446
- /**
447
- * Tear down the app mounted at `root`: runs its cleanup callbacks, clears
448
- * route state if it was the last routed root, and removes bookkeeping for
449
- * the root element.
450
- */
451
- function cleanupApp(root) {
452
- const rootElement = resolveRootElement(root);
453
- if (!rootElement) return;
454
- const cleanupFn = rootElement[CLEANUP_SYMBOL];
455
- if (typeof cleanupFn === "function") cleanupFn();
456
- else clearRootCleanupCallbacks(rootElement);
457
- }
458
- /** Check whether an app instance is currently mounted at `root`. */
459
- function hasApp(root) {
460
- const rootElement = resolveRootElement(root);
461
- if (!rootElement) return false;
462
- return instancesByRoot.has(rootElement);
463
- }
464
- //#endregion
465
- //#region src/boot/islands.ts
466
- /**
467
- * createIsland: Enhances existing DOM (no router, mounts once)
468
- */
469
- function createIsland(config) {
470
- assertExecutionModel("islands");
471
- if (!config || typeof config !== "object") throw new Error("createIsland requires a config object");
472
- if (typeof config.component !== "function") throw new Error("createIsland: component must be a function");
473
- validateCspNonce(config.cspNonce);
474
- const rootElement = resolveRootElement(config.root);
475
- if (!rootElement) throw new Error(`Root element not found: ${config.root}`);
476
- if ("routes" in config) throw new Error("createIsland does not accept routes; use createSPA for routed apps");
477
- if (hasRegisteredRoutes()) throw new Error("Routes are not supported with islands. Use createSPA (client) or createSSR (server) instead.");
478
- mountOrUpdate(rootElement, config.component, {
479
- cleanupStrict: config.cleanupStrict,
480
- cspNonce: config.cspNonce,
481
- appRuntime: createAppRenderRuntime({ dataRuntime: config.dataRuntime ?? getDefaultDataRuntime() })
482
- });
483
- }
484
- /**
485
- * createIslands: Enhances one or more existing DOM roots (no router).
486
- * The only public islands constructor.
487
- */
488
- function createIslands(config) {
489
- assertExecutionModel("islands");
490
- if (!config || typeof config !== "object") throw new Error("createIslands requires a config object");
491
- validateCspNonce(config.cspNonce);
492
- if (!Array.isArray(config.islands) || config.islands.length === 0) throw new Error("createIslands requires a non-empty islands array");
493
- for (const island of config.islands) createIsland({
494
- ...island,
495
- cspNonce: island.cspNonce ?? config.cspNonce
496
- });
497
- }
498
- //#endregion
499
- //#region src/boot/route-startup.ts
500
- const MAX_INITIAL_ROUTE_REDIRECTS = 20;
501
- function bindResolvedRouteHandler(resolved) {
502
- return () => resolved.handler(resolved.params);
503
- }
504
- function createDeniedStatusNode(status) {
505
- return {
506
- type: "div",
507
- props: { "data-route-denied": String(status) },
508
- children: [String(status)]
509
- };
510
- }
511
- function bindDeniedStatus(status) {
512
- return () => createDeniedStatusNode(status);
513
- }
514
- function bindDeniedRouteHandler(status) {
515
- return () => createDeniedStatusNode(status);
516
- }
517
- async function reconcileInitialRouteMetadata(resolved) {
518
- if (resolved?.kind !== "render" || !resolved.record) return;
519
- if (!resolved.record.options.title && !resolved.record.options.meta && !resolved.record.metaChain?.length) return;
520
- const context = getRouteRenderContext(resolved);
521
- if (!context) return;
522
- reconcileRouteMeta(await resolveRouteMeta(resolved.record, context));
523
- }
524
- async function resolveInitialRoute(auth, source) {
525
- let path = typeof window !== "undefined" ? window.location.pathname : "/";
526
- let href = typeof window !== "undefined" ? `${window.location.pathname}${window.location.search}${window.location.hash}` : path;
527
- const visited = /* @__PURE__ */ new Set();
528
- for (let redirects = 0; redirects <= MAX_INITIAL_ROUTE_REDIRECTS; redirects++) {
529
- if (visited.has(href)) throw new Error(`[Askr] Route redirect cycle detected at ${href}.`);
530
- visited.add(href);
531
- if (!source?.registry) throw new TypeError("resolveInitialRoute requires a route registry.");
532
- const resolved = await resolveRouteRequest(href, {
533
- registry: source.registry,
534
- auth,
535
- load: source.load
536
- });
537
- if (typeof window === "undefined" || !resolved || resolved.kind !== "redirect") return {
538
- path,
539
- href,
540
- resolved
541
- };
542
- const redirectTarget = new URL(resolved.to, window.location.href);
543
- const redirectHref = `${redirectTarget.pathname}${redirectTarget.search}${redirectTarget.hash}`;
544
- window.history.replaceState({ path: redirectHref }, "", redirectHref);
545
- path = redirectTarget.pathname;
546
- href = redirectHref;
547
- }
548
- throw new Error(`[Askr] Route redirect limit exceeded (${MAX_INITIAL_ROUTE_REDIRECTS}).`);
549
- }
550
- //#endregion
551
- //#region src/boot/spa.ts
552
- /**
553
- * App bootstrap and mount
554
- */
555
- /**
556
- * createSPA: Initializes the router and mounts the app with an explicit route registry.
557
- * ```ts
558
- * import { createRouteRegistry } from '@askrjs/askr/router';
559
- * const registry = createRouteRegistry(() => { ... });
560
- * await createSPA({ root: '#app', registry });
561
- * ```
562
- *
563
- */
564
- async function createSPA(config) {
565
- assertExecutionModel("spa");
566
- if (!config || typeof config !== "object") throw new Error("createSPA requires a config object");
567
- validateCspNonce(config.cspNonce);
568
- if (!config.registry) throw new Error("createSPA requires a route registry.");
569
- const { manifest } = config.registry;
570
- if (manifest.records.length === 0) throw new Error("createSPA requires a route registry with at least one route. If you are enhancing existing HTML, use createIsland instead.");
571
- const rootElement = resolveRootElement(config.root);
572
- if (!rootElement) throw new Error(`Root element not found: ${config.root}`);
573
- configureScrollRestoration(config.scrollRestoration);
574
- clearRouteState();
575
- _applyManifest(manifest);
576
- const routeAuth = config.auth ?? manifest.auth;
577
- const appRouteSource = {
578
- registry: config.registry,
579
- auth: routeAuth
580
- };
581
- _setActiveRouteAuthOptions(routeAuth);
582
- if (isProductionEnvironment()) lockRouteRegistration();
583
- const { path, href: currentUrl, resolved } = await resolveInitialRoute(routeAuth, { registry: config.registry });
584
- setServerLocation(currentUrl);
585
- const appRuntime = createAppRenderRuntime({
586
- ...resolved?.kind === "render" && hasRouteRenderData(resolved) ? {
587
- route: getRouteRenderData(resolved),
588
- hasRoute: true
589
- } : {},
590
- dataRuntime: config.dataRuntime ?? getDefaultDataRuntime(),
591
- routeRegistry: config.registry,
592
- routeAuth
593
- });
594
- if (!resolved) {
595
- mountOrUpdate(rootElement, () => ({
596
- type: "div",
597
- children: []
598
- }), {
599
- cleanupStrict: config.cleanupStrict,
600
- appRuntime,
601
- cspNonce: config.cspNonce
602
- });
603
- await registerAppNavigation(rootElement, path, {
604
- ...appRouteSource,
605
- runtime: appRuntime
606
- });
607
- return;
608
- }
609
- if (resolved.kind === "redirect") {
610
- mountOrUpdate(rootElement, () => ({
611
- type: "div",
612
- children: []
613
- }), {
614
- cleanupStrict: config.cleanupStrict,
615
- appRuntime,
616
- cspNonce: config.cspNonce
617
- });
618
- await registerAppNavigation(rootElement, path, {
619
- ...appRouteSource,
620
- runtime: appRuntime
621
- });
622
- return;
623
- }
624
- await reconcileInitialRouteMetadata(resolved);
625
- mountOrUpdate(rootElement, resolved.kind === "deny" ? bindDeniedStatus(resolved.status) : bindResolvedRouteHandler({
626
- handler: resolved.handler,
627
- params: resolved.params
628
- }), {
629
- cleanupStrict: config.cleanupStrict,
630
- appRuntime,
631
- cspNonce: config.cspNonce
632
- });
633
- await registerAppNavigation(rootElement, path, {
634
- ...appRouteSource,
635
- runtime: appRuntime
636
- });
637
- }
638
- //#endregion
639
- //#region src/boot/hydration.ts
640
- const DEFERRED_PAYLOAD = "__askr_deferred__";
641
- function applyDeferredStreamPatches(rootElement) {
642
- const patches = Array.from(rootElement.querySelectorAll("template[data-askr-deferred-patch]"));
643
- for (const template of patches) {
644
- const id = template.getAttribute("data-askr-deferred-patch");
645
- if (!id) continue;
646
- const boundary = Array.from(rootElement.querySelectorAll("askr-resolve[data-askr-deferred]")).find((candidate) => candidate.getAttribute("data-askr-deferred") === id);
647
- if (boundary) boundary.replaceWith(template.content.cloneNode(true));
648
- template.remove();
649
- for (const script of Array.from(rootElement.querySelectorAll("script[data-askr-deferred-apply]"))) if (script.getAttribute("data-askr-deferred-apply") === id) script.remove();
650
- }
651
- }
652
- function hydrationReviver(_key, value) {
653
- if (!value || typeof value !== "object") return value;
654
- const payload = value;
655
- if (payload[DEFERRED_PAYLOAD] === "fulfilled") return reviveDeferredValue("fulfilled", payload.value, void 0);
656
- if (payload[DEFERRED_PAYLOAD] === "rejected") return reviveDeferredValue("rejected", void 0, payload.error);
657
- return value;
658
- }
659
- function takeHydrationRenderData(rootElement) {
660
- for (const child of Array.from(rootElement.children)) if (child instanceof HTMLScriptElement && child.getAttribute("data-askr-render-data") === "true") {
661
- const raw = child.textContent ?? "";
662
- child.remove();
663
- if (!raw) return pageRenderEnvelope(void 0);
664
- try {
665
- return pageRenderEnvelope(JSON.parse(raw, hydrationReviver));
666
- } catch (err) {
667
- const error = /* @__PURE__ */ new Error("[Askr] Failed to parse embedded SSR render data during hydration.");
668
- error.cause = err;
669
- throw error;
670
- }
671
- }
672
- return null;
673
- }
674
- function markSkippedElements(root, skipSelectors) {
675
- if (skipSelectors.length === 0) return;
676
- const selectorList = Array.from(new Set(skipSelectors)).join(", ");
677
- root.querySelectorAll(selectorList).forEach((el) => el.setAttribute("data-skip-hydrate", "true"));
678
- }
679
- function collectDeferredBelowFoldBoundaries(root, foldY) {
680
- const boundaries = [];
681
- const stack = [];
682
- for (let index = root.children.length - 1; index >= 0; index -= 1) stack.push(root.children[index]);
683
- while (stack.length > 0) {
684
- const element = stack.pop();
685
- if (element.hasAttribute("data-skip-hydrate")) continue;
686
- if (element.getBoundingClientRect().top >= foldY) {
687
- element.setAttribute("data-skip-hydrate", "true");
688
- registerDeferredHydrationBoundary(root, element);
689
- boundaries.push(element);
690
- continue;
691
- }
692
- for (let index = element.children.length - 1; index >= 0; index -= 1) stack.push(element.children[index]);
693
- }
694
- return boundaries;
695
- }
696
- function activateVisibleDeferredBoundaries(root, boundaries, foldY, activate) {
697
- let activated = false;
698
- let remaining = 0;
699
- for (const element of boundaries) {
700
- if (!element.hasAttribute("data-skip-hydrate")) continue;
701
- if (element.getBoundingClientRect().top < foldY) try {
702
- if (activate(root, element)) activated = true;
703
- } catch {}
704
- if (element.hasAttribute("data-skip-hydrate")) remaining += 1;
705
- }
706
- return {
707
- activated,
708
- remaining
709
- };
710
- }
711
- function queueIdleWork(work) {
712
- return new Promise((resolve) => {
713
- if (typeof requestIdleCallback !== "undefined") {
714
- requestIdleCallback(() => {
715
- work();
716
- resolve();
717
- }, { timeout: 2e3 });
718
- return;
719
- }
720
- setTimeout(() => {
721
- work();
722
- resolve();
723
- }, 0);
724
- });
725
- }
726
- function shouldVerifyHydrationMarkup(config) {
727
- const explicit = config.hydrate?.verifyMarkup;
728
- if (typeof explicit === "boolean") return explicit;
729
- return !isProductionEnvironment();
730
- }
731
- async function applySelectiveHydration(rootElement, resolved, path, cleanupStrict, hydrateOptions, source, hooks, interactionReplay) {
732
- const hasBelowFoldDeferral = !!hydrateOptions.deferBelowFold;
733
- let staticChildSlotsCacheSuspended = false;
734
- let releaseSelectiveHydrationResources = () => {};
735
- const restoreStaticChildSlotsCache = () => {
736
- if (!staticChildSlotsCacheSuspended) return;
737
- setStaticChildSlotsCacheEnabled(true);
738
- staticChildSlotsCacheSuspended = false;
739
- };
740
- if (hydrateOptions.skipSelectors?.length) markSkippedElements(rootElement, hydrateOptions.skipSelectors);
741
- let deferredBoundaries = [];
742
- if (hydrateOptions.deferBelowFold) {
743
- setStaticChildSlotsCacheEnabled(false);
744
- staticChildSlotsCacheSuspended = true;
745
- const foldY = hydrateOptions.foldThreshold ?? window.innerHeight;
746
- deferredBoundaries = collectDeferredBelowFoldBoundaries(rootElement, foldY);
747
- interactionReplay.registerDeferredBoundaries(deferredBoundaries);
748
- let selectiveHydrationResourcesReleased = false;
749
- let unregisterRootCleanupCallback = () => {};
750
- function handleScroll() {
751
- const { activated, remaining } = activateVisibleDeferredBoundaries(rootElement, deferredBoundaries, foldY, hooks.activateHydrationBoundary);
752
- if (!activated) return;
753
- if (remaining === 0) releaseSelectiveHydrationResources();
754
- }
755
- releaseSelectiveHydrationResources = () => {
756
- if (selectiveHydrationResourcesReleased) return;
757
- selectiveHydrationResourcesReleased = true;
758
- unregisterRootCleanupCallback();
759
- window.removeEventListener("scroll", handleScroll);
760
- restoreStaticChildSlotsCache();
761
- interactionReplay.clearDeferredBoundaries();
762
- };
763
- interactionReplay.setOnDeferredBoundariesDrained(releaseSelectiveHydrationResources);
764
- unregisterRootCleanupCallback = hooks.registerRootCleanupCallback(rootElement, releaseSelectiveHydrationResources);
765
- window.addEventListener("scroll", handleScroll, { passive: true });
766
- }
767
- if (hydrateOptions.deferUntilIdle && !hasBelowFoldDeferral) {
768
- await queueIdleWork(() => {
769
- hooks.mountOrUpdate(rootElement, (() => resolved.handler(resolved.params)), {
770
- cleanupStrict,
771
- appRuntime: source?.runtime
772
- });
773
- });
774
- await hooks.registerAppNavigation(rootElement, path, source);
775
- return;
776
- }
777
- try {
778
- hooks.mountOrUpdate(rootElement, (() => resolved.handler(resolved.params)), {
779
- cleanupStrict,
780
- appRuntime: source?.runtime
781
- });
782
- await hooks.registerAppNavigation(rootElement, path, source);
783
- } catch (error) {
784
- releaseSelectiveHydrationResources();
785
- throw error;
786
- }
787
- if (hydrateOptions.deferUntilIdle && deferredBoundaries.length > 0) await queueIdleWork(() => {
788
- try {
789
- activateVisibleDeferredBoundaries(rootElement, deferredBoundaries, Number.POSITIVE_INFINITY, hooks.activateHydrationBoundary);
790
- } finally {
791
- releaseSelectiveHydrationResources();
792
- }
793
- });
794
- if (deferredBoundaries.length === 0) releaseSelectiveHydrationResources();
795
- }
796
- //#endregion
797
- //#region src/boot/hydration-interaction-replay.ts
798
- const HYDRATION_INTERACTION_TYPES = [
799
- "click",
800
- "dblclick",
801
- "pointerdown",
802
- "pointerup",
803
- "keydown",
804
- "keyup",
805
- "input",
806
- "change",
807
- "submit"
808
- ];
809
- function cloneInteractionEvent(event) {
810
- const EventConstructor = event.constructor;
811
- const eventWithDetails = event;
812
- const init = {
813
- bubbles: event.bubbles,
814
- cancelable: event.cancelable,
815
- composed: event.composed,
816
- detail: eventWithDetails.detail,
817
- view: eventWithDetails.view,
818
- key: eventWithDetails.key,
819
- code: eventWithDetails.code,
820
- location: eventWithDetails.location,
821
- repeat: eventWithDetails.repeat,
822
- isComposing: eventWithDetails.isComposing,
823
- ctrlKey: eventWithDetails.ctrlKey,
824
- shiftKey: eventWithDetails.shiftKey,
825
- altKey: eventWithDetails.altKey,
826
- metaKey: eventWithDetails.metaKey,
827
- button: eventWithDetails.button,
828
- buttons: eventWithDetails.buttons,
829
- clientX: eventWithDetails.clientX,
830
- clientY: eventWithDetails.clientY,
831
- screenX: eventWithDetails.screenX,
832
- screenY: eventWithDetails.screenY,
833
- pointerId: eventWithDetails.pointerId,
834
- pointerType: eventWithDetails.pointerType,
835
- isPrimary: eventWithDetails.isPrimary,
836
- data: eventWithDetails.data,
837
- inputType: eventWithDetails.inputType
838
- };
839
- try {
840
- return new EventConstructor(event.type, init);
841
- } catch {
842
- return new Event(event.type, {
843
- bubbles: event.bubbles,
844
- cancelable: event.cancelable,
845
- composed: event.composed
846
- });
847
- }
848
- }
849
- /**
850
- * Capture discrete interactions before hydrated handlers exist, then replay
851
- * them after the listener transaction commits. Deferred boundaries remain
852
- * interaction-activatable until they are revealed or the root is cleaned up.
853
- */
854
- function beginHydrationInteractionReplay(root, activateBoundary, permanentSkipSelectors = []) {
855
- const queued = [];
856
- const deferredBoundaries = /* @__PURE__ */ new Set();
857
- let active = true;
858
- let hydratingRoot = true;
859
- let replaying = false;
860
- let hadDeferredBoundaries = false;
861
- let onDeferredBoundariesDrained;
862
- function isPermanentlySkipped(target) {
863
- const element = target instanceof Element ? target : target instanceof Node ? target.parentElement : null;
864
- if (!element) return false;
865
- return permanentSkipSelectors.some((selector) => {
866
- try {
867
- return element.closest(selector) !== null;
868
- } catch {
869
- return false;
870
- }
871
- });
872
- }
873
- function findDeferredBoundary(target) {
874
- if (!(target instanceof Node)) return;
875
- for (const boundary of deferredBoundaries) if (boundary.hasAttribute("data-skip-hydrate") && boundary.contains(target)) return boundary;
876
- }
877
- function removeCaptureListeners() {
878
- if (!active) return;
879
- active = false;
880
- for (const type of HYDRATION_INTERACTION_TYPES) root.removeEventListener(type, captureInteraction, true);
881
- }
882
- function releaseIfFinished() {
883
- if (!hydratingRoot && deferredBoundaries.size === 0) removeCaptureListeners();
884
- }
885
- function notifyIfDeferredBoundariesDrained() {
886
- if (hadDeferredBoundaries && deferredBoundaries.size === 0) onDeferredBoundariesDrained?.();
887
- }
888
- function replayInteraction(interaction) {
889
- if (!(interaction.target instanceof Node) || !root.contains(interaction.target)) return;
890
- const boundary = findDeferredBoundary(interaction.target);
891
- if (boundary) {
892
- try {
893
- if (!activateBoundary(boundary)) return;
894
- } catch {
895
- return;
896
- }
897
- deferredBoundaries.delete(boundary);
898
- notifyIfDeferredBoundariesDrained();
899
- }
900
- replaying = true;
901
- try {
902
- interaction.target.dispatchEvent(interaction.event);
903
- } finally {
904
- replaying = false;
905
- }
906
- releaseIfFinished();
907
- }
908
- function captureInteraction(event) {
909
- if (!active || replaying) return;
910
- const target = event.target;
911
- if (!target || isPermanentlySkipped(target)) return;
912
- const boundary = findDeferredBoundary(target);
913
- if (!hydratingRoot && !boundary) return;
914
- event.preventDefault();
915
- event.stopImmediatePropagation();
916
- const interaction = {
917
- target,
918
- event: cloneInteractionEvent(event)
919
- };
920
- if (hydratingRoot) {
921
- queued.push(interaction);
922
- return;
923
- }
924
- replayInteraction(interaction);
925
- }
926
- const installedTypes = [];
927
- try {
928
- for (const type of HYDRATION_INTERACTION_TYPES) {
929
- root.addEventListener(type, captureInteraction, true);
930
- installedTypes.push(type);
931
- }
932
- } catch (error) {
933
- active = false;
934
- for (const type of installedTypes) root.removeEventListener(type, captureInteraction, true);
935
- throw error;
936
- }
937
- return {
938
- registerDeferredBoundaries(boundaries) {
939
- for (const boundary of boundaries) deferredBoundaries.add(boundary);
940
- hadDeferredBoundaries ||= boundaries.length > 0;
941
- },
942
- setOnDeferredBoundariesDrained(callback) {
943
- onDeferredBoundariesDrained = callback;
944
- },
945
- clearDeferredBoundaries() {
946
- deferredBoundaries.clear();
947
- releaseIfFinished();
948
- },
949
- complete() {
950
- if (!active || !hydratingRoot) return;
951
- hydratingRoot = false;
952
- const pending = queued.splice(0);
953
- for (const interaction of pending) replayInteraction(interaction);
954
- releaseIfFinished();
955
- },
956
- abort() {
957
- queued.length = 0;
958
- deferredBoundaries.clear();
959
- onDeferredBoundariesDrained = void 0;
960
- hydratingRoot = false;
961
- removeCaptureListeners();
962
- }
963
- };
964
- }
965
- //#endregion
966
- //#region src/boot/hydrate-spa.ts
967
- /**
968
- * App bootstrap and mount
969
- */
970
- /**
971
- * hydrateSPA: Hydrate server-rendered HTML with an explicit route registry.
972
- */
973
- async function hydrateSPA(config) {
974
- assertExecutionModel("spa");
975
- if (!config || typeof config !== "object") throw new Error("hydrateSPA requires a config object");
976
- validateCspNonce(config.cspNonce);
977
- if (!config.registry) throw new Error("hydrateSPA requires a route registry.");
978
- const { manifest } = config.registry;
979
- if (manifest.records.length === 0) throw new Error("hydrateSPA requires a route registry with at least one route. If you are enhancing existing HTML, use createIsland instead.");
980
- const rootElement = resolveRootElement(config.root);
981
- if (!rootElement) throw new Error(`Root element not found: ${config.root}`);
982
- const interactionReplay = beginHydrationInteractionReplay(rootElement, (boundary) => activateHydrationBoundary(rootElement, boundary), config.hydrate?.skipSelectors);
983
- try {
984
- applyDeferredStreamPatches(rootElement);
985
- const hydrationRenderData = takeHydrationRenderData(rootElement);
986
- const hydrationQueryCache = hydrationRenderData?.resources;
987
- if (hydrationQueryCache) hydrateDataRuntime(config.dataRuntime ?? getDefaultDataRuntime(), hydrationQueryCache);
988
- const hydrationRenderDataForApp = hydrationRenderData;
989
- configureScrollRestoration(config.scrollRestoration);
990
- clearRouteState();
991
- _applyManifest(manifest);
992
- const routeAuth = config.auth ?? manifest.auth;
993
- const appRouteSource = {
994
- registry: config.registry,
995
- auth: routeAuth,
996
- runtime: createAppRenderRuntime({
997
- framework: hydrationRenderData?.framework,
998
- route: hydrationRenderData?.route,
999
- hasRoute: hydrationRenderData !== null,
1000
- routeRegistry: config.registry,
1001
- routeAuth
1002
- })
1003
- };
1004
- _setActiveRouteAuthOptions(routeAuth);
1005
- const { path, href: currentUrl, resolved } = await resolveInitialRoute(routeAuth, {
1006
- registry: config.registry,
1007
- load: false
1008
- });
1009
- setServerLocation(currentUrl);
1010
- if (isProductionEnvironment()) lockRouteRegistration();
1011
- if (!resolved) throw new Error(`hydrateSPA: no route found for current path (${path}).`);
1012
- if (resolved.kind === "redirect") throw new Error(`hydrateSPA: unresolved redirect for current path (${path}).`);
1013
- await reconcileInitialRouteMetadata(resolved);
1014
- const hydrationResolved = resolved.kind === "deny" ? {
1015
- handler: bindDeniedRouteHandler(resolved.status),
1016
- params: {}
1017
- } : resolved;
1018
- const mountHydratedRoot = (...args) => withIntrinsicHydrationAdoption(() => mountOrUpdate(args[0], args[1], {
1019
- ...args[2],
1020
- cspNonce: config.cspNonce
1021
- }));
1022
- if (shouldVerifyHydrationMarkup(config)) {
1023
- const { verifyHydrationSyncForUrl } = await import("./verify-hydration-ikaxotf7.js");
1024
- if (!verifyHydrationSyncForUrl({
1025
- root: rootElement,
1026
- url: currentUrl,
1027
- registry: config.registry,
1028
- resolved: hydrationResolved,
1029
- options: {
1030
- data: hydrationRenderDataForApp?.resources,
1031
- dataRuntime: config.dataRuntime ?? getDefaultDataRuntime(),
1032
- envelope: hydrationRenderDataForApp ?? void 0,
1033
- cspNonce: config.cspNonce
1034
- }
1035
- })) throw new Error("[Askr] Hydration mismatch detected. Server HTML does not match expected server-render output.");
1036
- }
1037
- const hydrateOptions = config.hydrate;
1038
- if (hydrateOptions) {
1039
- if (hydrateOptions.deferUntilIdle || hydrateOptions.deferBelowFold) {
1040
- if (hydrationRenderDataForApp) startHydrationRenderPhase(hydrationRenderDataForApp);
1041
- try {
1042
- await applySelectiveHydration(rootElement, hydrationResolved, path, config.cleanupStrict, hydrateOptions, appRouteSource, {
1043
- mountOrUpdate: mountHydratedRoot,
1044
- registerAppNavigation,
1045
- registerRootCleanupCallback,
1046
- activateHydrationBoundary
1047
- }, interactionReplay);
1048
- } finally {
1049
- if (hydrationRenderDataForApp) stopHydrationRenderPhase();
1050
- }
1051
- interactionReplay.complete();
1052
- return;
1053
- }
1054
- if (hydrateOptions.skipSelectors?.length) markSkippedElements(rootElement, hydrateOptions.skipSelectors);
1055
- }
1056
- if (hydrationRenderDataForApp) startHydrationRenderPhase(hydrationRenderDataForApp);
1057
- const listenerTransaction = beginHydrationListenerTransaction();
1058
- beginHydrationDirectListenerMode();
1059
- try {
1060
- mountHydratedRoot(rootElement, resolved.kind === "deny" ? bindDeniedStatus(resolved.status) : bindResolvedRouteHandler(hydrationResolved), {
1061
- cleanupStrict: config.cleanupStrict,
1062
- appRuntime: appRouteSource.runtime
1063
- });
1064
- commitHydrationListenerTransaction(listenerTransaction);
1065
- interactionReplay.complete();
1066
- } catch (error) {
1067
- discardHydrationListenerTransaction(listenerTransaction);
1068
- throw error;
1069
- } finally {
1070
- endHydrationDirectListenerMode();
1071
- if (hydrationRenderDataForApp) stopHydrationRenderPhase();
1072
- }
1073
- await registerAppNavigation(rootElement, path, { ...appRouteSource });
1074
- } catch (error) {
1075
- interactionReplay.abort();
1076
- throw error;
1077
- }
1078
- }
1079
- //#endregion
1080
- //#region src/boot/index.ts
1081
- /** Stable bootstrap exports; each mounting mode owns its orchestration. */
1082
- composeBrowserRuntime();
1083
- //#endregion
1084
- export { cleanupApp as a, createIslands as i, createSPA as n, hasApp as o, createIsland as r, installRendererBridge as s, hydrateSPA as t };