@askrjs/askr 0.0.39 → 0.0.40

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 (250) hide show
  1. package/dist/bench/components/benchmark-row.d.ts.map +1 -1
  2. package/dist/bench/components/benchmark-row.js.map +1 -1
  3. package/dist/bench/components/benchmark-table.js.map +1 -1
  4. package/dist/benchmark.d.ts.map +1 -1
  5. package/dist/benchmark.js +2 -2
  6. package/dist/benchmark.js.map +1 -1
  7. package/dist/bin/askr-ssg.d.ts.map +1 -1
  8. package/dist/bin/askr-ssg.js.map +1 -1
  9. package/dist/boot/index.d.ts +9 -6
  10. package/dist/boot/index.d.ts.map +1 -1
  11. package/dist/boot/index.js +210 -75
  12. package/dist/boot/index.js.map +1 -1
  13. package/dist/common/attr-names.js +17 -0
  14. package/dist/common/attr-names.js.map +1 -0
  15. package/dist/common/component.d.ts.map +1 -1
  16. package/dist/common/control.d.ts.map +1 -1
  17. package/dist/common/control.js.map +1 -1
  18. package/dist/common/env.js.map +1 -1
  19. package/dist/common/jsx.d.ts.map +1 -1
  20. package/dist/common/jsx.js.map +1 -1
  21. package/dist/common/promise.js +8 -0
  22. package/dist/common/promise.js.map +1 -0
  23. package/dist/common/props.d.ts +286 -1
  24. package/dist/common/props.d.ts.map +1 -1
  25. package/dist/common/router-internal.js +6 -0
  26. package/dist/common/router-internal.js.map +1 -0
  27. package/dist/common/router.d.ts +45 -36
  28. package/dist/common/router.d.ts.map +1 -1
  29. package/dist/common/ssr-errors.d.ts.map +1 -1
  30. package/dist/common/ssr-errors.js.map +1 -1
  31. package/dist/common/ssr.d.ts +1 -0
  32. package/dist/common/ssr.d.ts.map +1 -1
  33. package/dist/common/ssr.js +6 -0
  34. package/dist/common/ssr.js.map +1 -0
  35. package/dist/common/vnode.d.ts +2 -1
  36. package/dist/common/vnode.d.ts.map +1 -1
  37. package/dist/common/vnode.js.map +1 -1
  38. package/dist/components/error-boundary.d.ts +7 -4
  39. package/dist/components/error-boundary.d.ts.map +1 -1
  40. package/dist/components/error-boundary.js.map +1 -1
  41. package/dist/components/link.d.ts +2 -1
  42. package/dist/components/link.d.ts.map +1 -1
  43. package/dist/components/link.js +10 -1
  44. package/dist/components/link.js.map +1 -1
  45. package/dist/control/case.d.ts +4 -2
  46. package/dist/control/case.d.ts.map +1 -1
  47. package/dist/control/case.js.map +1 -1
  48. package/dist/control/for.d.ts +2 -1
  49. package/dist/control/for.d.ts.map +1 -1
  50. package/dist/control/for.js.map +1 -1
  51. package/dist/control/shared.d.ts +9 -0
  52. package/dist/control/shared.d.ts.map +1 -0
  53. package/dist/control/shared.js.map +1 -1
  54. package/dist/control/show.d.ts +4 -3
  55. package/dist/control/show.d.ts.map +1 -1
  56. package/dist/control/show.js.map +1 -1
  57. package/dist/data/index.d.ts +94 -14
  58. package/dist/data/index.d.ts.map +1 -1
  59. package/dist/data/index.js +65 -14
  60. package/dist/data/index.js.map +1 -1
  61. package/dist/dev/invariant.js.map +1 -1
  62. package/dist/dev/logger.js.map +1 -1
  63. package/dist/foundations/icon/icon.d.ts.map +1 -1
  64. package/dist/foundations/icon/icon.js.map +1 -1
  65. package/dist/foundations/icon/icon.types.d.ts.map +1 -1
  66. package/dist/foundations/interactions/dismissable.d.ts +9 -3
  67. package/dist/foundations/interactions/dismissable.d.ts.map +1 -1
  68. package/dist/foundations/interactions/dismissable.js.map +1 -1
  69. package/dist/foundations/interactions/focusable.d.ts.map +1 -1
  70. package/dist/foundations/interactions/focusable.js.map +1 -1
  71. package/dist/foundations/interactions/hoverable.d.ts.map +1 -1
  72. package/dist/foundations/interactions/hoverable.js.map +1 -1
  73. package/dist/foundations/interactions/interaction-policy.d.ts +1 -1
  74. package/dist/foundations/interactions/interaction-policy.d.ts.map +1 -1
  75. package/dist/foundations/interactions/interaction-policy.js +3 -3
  76. package/dist/foundations/interactions/interaction-policy.js.map +1 -1
  77. package/dist/foundations/interactions/pressable.d.ts.map +1 -1
  78. package/dist/foundations/interactions/pressable.js.map +1 -1
  79. package/dist/foundations/interactions/roving-focus.d.ts.map +1 -1
  80. package/dist/foundations/interactions/roving-focus.js +8 -8
  81. package/dist/foundations/interactions/roving-focus.js.map +1 -1
  82. package/dist/foundations/state/controllable.d.ts.map +1 -1
  83. package/dist/foundations/state/controllable.js.map +1 -1
  84. package/dist/foundations/structures/collection.d.ts.map +1 -1
  85. package/dist/foundations/structures/collection.js.map +1 -1
  86. package/dist/foundations/structures/layer.d.ts.map +1 -1
  87. package/dist/foundations/structures/layer.js.map +1 -1
  88. package/dist/foundations/structures/layout.d.ts +4 -2
  89. package/dist/foundations/structures/layout.d.ts.map +1 -1
  90. package/dist/foundations/structures/layout.js.map +1 -1
  91. package/dist/foundations/structures/portal.d.ts +9 -25
  92. package/dist/foundations/structures/portal.d.ts.map +1 -1
  93. package/dist/foundations/structures/portal.js +98 -17
  94. package/dist/foundations/structures/portal.js.map +1 -1
  95. package/dist/foundations/structures/presence.d.ts +4 -3
  96. package/dist/foundations/structures/presence.d.ts.map +1 -1
  97. package/dist/foundations/structures/presence.js +2 -2
  98. package/dist/foundations/structures/presence.js.map +1 -1
  99. package/dist/foundations/structures/slot.d.ts +2 -1
  100. package/dist/foundations/structures/slot.d.ts.map +1 -1
  101. package/dist/foundations/structures/slot.js.map +1 -1
  102. package/dist/foundations/utilities/aria.d.ts.map +1 -1
  103. package/dist/foundations/utilities/aria.js.map +1 -1
  104. package/dist/foundations/utilities/compose-handlers.d.ts.map +1 -1
  105. package/dist/foundations/utilities/compose-handlers.js.map +1 -1
  106. package/dist/foundations/utilities/compose-ref.d.ts.map +1 -1
  107. package/dist/foundations/utilities/compose-ref.js.map +1 -1
  108. package/dist/foundations/utilities/event-types.d.ts.map +1 -1
  109. package/dist/foundations/utilities/merge-props.js.map +1 -1
  110. package/dist/foundations/utilities/use-id.d.ts.map +1 -1
  111. package/dist/foundations/utilities/use-id.js.map +1 -1
  112. package/dist/fx/fx.d.ts.map +1 -1
  113. package/dist/fx/fx.js.map +1 -1
  114. package/dist/fx/noop.js.map +1 -1
  115. package/dist/fx/timing.d.ts +5 -4
  116. package/dist/fx/timing.d.ts.map +1 -1
  117. package/dist/fx/timing.js +7 -4
  118. package/dist/fx/timing.js.map +1 -1
  119. package/dist/index.js.map +1 -1
  120. package/dist/jsx/types.d.ts +3 -3
  121. package/dist/jsx/types.d.ts.map +1 -1
  122. package/dist/jsx/utils.d.ts.map +1 -1
  123. package/dist/jsx/utils.js.map +1 -1
  124. package/dist/jsx-dev-runtime.d.ts +6 -2
  125. package/dist/jsx-dev-runtime.d.ts.map +1 -1
  126. package/dist/jsx-dev-runtime.js.map +1 -1
  127. package/dist/jsx-runtime.d.ts +16 -6
  128. package/dist/jsx-runtime.d.ts.map +1 -1
  129. package/dist/jsx-runtime.js.map +1 -1
  130. package/dist/renderer/children.js.map +1 -1
  131. package/dist/renderer/cleanup.js +40 -13
  132. package/dist/renderer/cleanup.js.map +1 -1
  133. package/dist/renderer/dom.js +163 -63
  134. package/dist/renderer/dom.js.map +1 -1
  135. package/dist/renderer/evaluate.js +9 -6
  136. package/dist/renderer/evaluate.js.map +1 -1
  137. package/dist/renderer/fastpath.js.map +1 -1
  138. package/dist/renderer/for-commit.js +1 -0
  139. package/dist/renderer/for-commit.js.map +1 -1
  140. package/dist/renderer/index.js.map +1 -1
  141. package/dist/renderer/keyed.js.map +1 -1
  142. package/dist/renderer/reconcile.js +34 -0
  143. package/dist/renderer/reconcile.js.map +1 -1
  144. package/dist/renderer/utils.js +34 -3
  145. package/dist/renderer/utils.js.map +1 -1
  146. package/dist/router/index.d.ts +2 -2
  147. package/dist/router/match.js +16 -2
  148. package/dist/router/match.js.map +1 -1
  149. package/dist/router/navigate.d.ts +9 -1
  150. package/dist/router/navigate.d.ts.map +1 -1
  151. package/dist/router/navigate.js +235 -81
  152. package/dist/router/navigate.js.map +1 -1
  153. package/dist/router/policy.d.ts +7 -7
  154. package/dist/router/policy.d.ts.map +1 -1
  155. package/dist/router/policy.js +5 -7
  156. package/dist/router/policy.js.map +1 -1
  157. package/dist/router/route-context.js.map +1 -1
  158. package/dist/router/route.d.ts +20 -9
  159. package/dist/router/route.d.ts.map +1 -1
  160. package/dist/router/route.js +95 -29
  161. package/dist/router/route.js.map +1 -1
  162. package/dist/runtime/child-scope.d.ts.map +1 -1
  163. package/dist/runtime/child-scope.js +5 -1
  164. package/dist/runtime/child-scope.js.map +1 -1
  165. package/dist/runtime/component.d.ts +5 -2
  166. package/dist/runtime/component.d.ts.map +1 -1
  167. package/dist/runtime/component.js +85 -25
  168. package/dist/runtime/component.js.map +1 -1
  169. package/dist/runtime/context.d.ts +5 -2
  170. package/dist/runtime/context.d.ts.map +1 -1
  171. package/dist/runtime/context.js +5 -2
  172. package/dist/runtime/context.js.map +1 -1
  173. package/dist/runtime/control.d.ts.map +1 -1
  174. package/dist/runtime/control.js.map +1 -1
  175. package/dist/runtime/derive.d.ts.map +1 -1
  176. package/dist/runtime/derive.js +3 -1
  177. package/dist/runtime/derive.js.map +1 -1
  178. package/dist/runtime/dev-namespace.js.map +1 -1
  179. package/dist/runtime/effect.d.ts.map +1 -1
  180. package/dist/runtime/effect.js +17 -11
  181. package/dist/runtime/effect.js.map +1 -1
  182. package/dist/runtime/events.js +43 -1
  183. package/dist/runtime/events.js.map +1 -1
  184. package/dist/runtime/execution-model.js.map +1 -1
  185. package/dist/runtime/fastlane.js +2 -23
  186. package/dist/runtime/fastlane.js.map +1 -1
  187. package/dist/runtime/for-bench.d.ts.map +1 -1
  188. package/dist/runtime/for-bench.js.map +1 -1
  189. package/dist/runtime/for.d.ts +1 -1
  190. package/dist/runtime/for.d.ts.map +1 -1
  191. package/dist/runtime/for.js +36 -12
  192. package/dist/runtime/for.js.map +1 -1
  193. package/dist/runtime/operations.d.ts +3 -13
  194. package/dist/runtime/operations.d.ts.map +1 -1
  195. package/dist/runtime/operations.js +47 -39
  196. package/dist/runtime/operations.js.map +1 -1
  197. package/dist/runtime/perf-metrics.js.map +1 -1
  198. package/dist/runtime/readable.d.ts +1 -0
  199. package/dist/runtime/readable.d.ts.map +1 -1
  200. package/dist/runtime/readable.js +6 -1
  201. package/dist/runtime/readable.js.map +1 -1
  202. package/dist/runtime/resource-cell.js +13 -2
  203. package/dist/runtime/resource-cell.js.map +1 -1
  204. package/dist/runtime/scheduler.d.ts.map +1 -1
  205. package/dist/runtime/scheduler.js +8 -4
  206. package/dist/runtime/scheduler.js.map +1 -1
  207. package/dist/runtime/selector.d.ts.map +1 -1
  208. package/dist/runtime/selector.js +8 -2
  209. package/dist/runtime/selector.js.map +1 -1
  210. package/dist/runtime/snapshot-source.d.ts.map +1 -1
  211. package/dist/runtime/snapshot-source.js.map +1 -1
  212. package/dist/runtime/ssr-bridge.js.map +1 -1
  213. package/dist/runtime/state.d.ts +4 -3
  214. package/dist/runtime/state.d.ts.map +1 -1
  215. package/dist/runtime/state.js.map +1 -1
  216. package/dist/ssg/batch-render.js +4 -8
  217. package/dist/ssg/batch-render.js.map +1 -1
  218. package/dist/ssg/create-static-gen.d.ts +10 -2
  219. package/dist/ssg/create-static-gen.d.ts.map +1 -1
  220. package/dist/ssg/create-static-gen.js +5 -4
  221. package/dist/ssg/create-static-gen.js.map +1 -1
  222. package/dist/ssg/generate-metadata.js.map +1 -1
  223. package/dist/ssg/incremental-manifest.js.map +1 -1
  224. package/dist/ssg/resolve-ssg-data.js +54 -12
  225. package/dist/ssg/resolve-ssg-data.js.map +1 -1
  226. package/dist/ssg/route-utils.js.map +1 -1
  227. package/dist/ssg/types.d.ts +8 -7
  228. package/dist/ssg/types.d.ts.map +1 -1
  229. package/dist/ssg/write-static-files.js.map +1 -1
  230. package/dist/ssr/attrs.js +3 -2
  231. package/dist/ssr/attrs.js.map +1 -1
  232. package/dist/ssr/context.d.ts +1 -0
  233. package/dist/ssr/context.d.ts.map +1 -1
  234. package/dist/ssr/context.js +1 -0
  235. package/dist/ssr/context.js.map +1 -1
  236. package/dist/ssr/errors.d.ts.map +1 -1
  237. package/dist/ssr/escape.js.map +1 -1
  238. package/dist/ssr/index.d.ts +8 -12
  239. package/dist/ssr/index.d.ts.map +1 -1
  240. package/dist/ssr/index.js +55 -30
  241. package/dist/ssr/index.js.map +1 -1
  242. package/dist/ssr/render-keys.js +15 -4
  243. package/dist/ssr/render-keys.js.map +1 -1
  244. package/dist/ssr/render-resolved.js +33 -0
  245. package/dist/ssr/render-resolved.js.map +1 -0
  246. package/dist/ssr/sink.js.map +1 -1
  247. package/dist/ssr/types.d.ts.map +1 -1
  248. package/dist/ssr/verify-hydration.js +5 -6
  249. package/dist/ssr/verify-hydration.js.map +1 -1
  250. package/package.json +12 -4
@@ -1,10 +1,12 @@
1
1
  import { isDevelopmentEnvironment, isProductionEnvironment } from "../common/env.js";
2
2
  import { logger } from "../dev/logger.js";
3
3
  import { ELEMENT_TYPE, Fragment } from "../common/jsx.js";
4
- import { mountComponent } from "../runtime/component.js";
5
- import { lockRouteRegistration, resolveRoute, resolveRouteRequest, syncCurrentRouteSnapshot } from "./route.js";
4
+ import { isPromiseLike } from "../common/promise.js";
5
+ import { cleanupComponent, mountComponent } from "../runtime/component.js";
6
+ import { cleanupInstancesUnder } from "../renderer/cleanup.js";
7
+ import { lockRouteRegistration, resolveRouteFromRoutes, resolveRouteRequest, syncCurrentRouteSnapshot } from "./route.js";
6
8
  import "../jsx/index.js";
7
- import { DefaultPortal } from "../foundations/structures/portal.js";
9
+ import { DefaultPortal, clearDefaultPortalForInstance } from "../foundations/structures/portal.js";
8
10
  //#region src/router/navigate.ts
9
11
  /**
10
12
  * Client-side navigation with History API
@@ -13,6 +15,14 @@ let currentInstance = null;
13
15
  let currentPathname = "/";
14
16
  let currentHref = "/";
15
17
  let navigationInitialized = false;
18
+ let activeRouteRequestId = 0;
19
+ let activeRouteRequestController = null;
20
+ const MAX_NAVIGATION_REDIRECTS = 20;
21
+ const registeredApps = [];
22
+ function syncAppRegistrationLocation(app, pathname, href) {
23
+ app.pathname = pathname;
24
+ app.href = href;
25
+ }
16
26
  const DEFAULT_SCROLL_RESTORATION = {
17
27
  enabled: true,
18
28
  navigation: "top",
@@ -111,8 +121,24 @@ function parseTargetUrl(path) {
111
121
  function isRenderResult(result) {
112
122
  return result !== null && result.kind === "render";
113
123
  }
114
- function isPromise(value) {
115
- return value instanceof Promise;
124
+ function getRedirectHistoryMode(replace) {
125
+ return replace === false ? "push" : "replace";
126
+ }
127
+ function getNavigationHistoryMode(options) {
128
+ if (options.history) return options.history;
129
+ return options.replace ? "replace" : "push";
130
+ }
131
+ function beginRouteRequest() {
132
+ activeRouteRequestId += 1;
133
+ activeRouteRequestController?.abort();
134
+ activeRouteRequestController = new AbortController();
135
+ return {
136
+ id: activeRouteRequestId,
137
+ signal: activeRouteRequestController.signal
138
+ };
139
+ }
140
+ function isStaleRouteRequest(requestId) {
141
+ return requestId !== activeRouteRequestId;
116
142
  }
117
143
  function createDeniedResolvedRoute(status) {
118
144
  return {
@@ -130,6 +156,7 @@ function bindResolvedRouteHandler(resolved) {
130
156
  function wrapRootRouteHandler(componentFn) {
131
157
  const wrappedFn = (props, ctx) => {
132
158
  const out = componentFn(props, ctx);
159
+ if (isPromiseLike(out)) throw new Error("Async components are not supported. Components must return synchronously.");
133
160
  const portalVNode = {
134
161
  $$typeof: ELEMENT_TYPE,
135
162
  type: DefaultPortal,
@@ -145,139 +172,266 @@ function wrapRootRouteHandler(componentFn) {
145
172
  Object.defineProperty(wrappedFn, "name", { value: componentFn.name || "Component" });
146
173
  return wrappedFn;
147
174
  }
148
- function remountResolvedRoute(resolved, pathname, href) {
149
- if (!currentInstance) return false;
150
- currentInstance.fn = wrapRootRouteHandler(bindResolvedRouteHandler(resolved));
151
- currentInstance.props = {};
152
- currentInstance.stateValues = [];
153
- currentInstance.expectedStateIndices = [];
154
- currentInstance.firstRenderComplete = false;
155
- currentInstance.stateIndexCheck = -1;
156
- currentInstance.evaluationGeneration++;
157
- currentInstance.notifyUpdate = null;
158
- currentInstance.mountOperations = [];
159
- currentInstance._placeholder = void 0;
160
- currentInstance.hasPendingUpdate = false;
161
- currentInstance.abortController = null;
162
- mountComponent(currentInstance);
175
+ function cleanupRouteOwnership(instance) {
176
+ const cleanupErrors = [];
177
+ const children = instance.target ? Array.from(instance.target.childNodes) : [];
178
+ for (const child of children) try {
179
+ cleanupInstancesUnder(child, { strict: instance.cleanupStrict });
180
+ } catch (error) {
181
+ cleanupErrors.push(error);
182
+ }
183
+ try {
184
+ cleanupComponent(instance);
185
+ } catch (error) {
186
+ cleanupErrors.push(error);
187
+ }
188
+ if (cleanupErrors.length > 0) throw new AggregateError(cleanupErrors, "Route cleanup failed");
189
+ }
190
+ function remountResolvedRoute(instance, resolved, pathname, href) {
191
+ clearDefaultPortalForInstance(instance);
192
+ cleanupRouteOwnership(instance);
193
+ instance.fn = wrapRootRouteHandler(bindResolvedRouteHandler(resolved));
194
+ instance.props = {};
195
+ instance.stateValues = [];
196
+ instance.expectedStateIndices = [];
197
+ instance.firstRenderComplete = false;
198
+ instance.stateIndexCheck = -1;
199
+ instance.evaluationGeneration++;
200
+ instance.notifyUpdate = null;
201
+ instance.mountOperations = [];
202
+ instance.cleanupFns = [];
203
+ instance._placeholder = void 0;
204
+ instance.hasPendingUpdate = false;
205
+ instance._currentRenderToken = void 0;
206
+ instance.lastRenderToken = 0;
207
+ instance._pendingReadSources = void 0;
208
+ instance._lastReadSources = void 0;
209
+ instance.abortController = null;
210
+ mountComponent(instance);
163
211
  currentPathname = pathname;
164
212
  currentHref = href;
165
213
  return true;
166
214
  }
167
- function rerenderResolvedRoute(pathname, href) {
168
- if (!currentInstance) return false;
169
- if (!resolveRoute(pathname)) return false;
215
+ function rerenderResolvedRoute(instance, pathname, href) {
170
216
  currentPathname = pathname;
171
217
  currentHref = href;
172
- currentInstance._enqueueRun?.();
218
+ instance._enqueueRun?.();
173
219
  return true;
174
220
  }
175
- function resolveNavigationTarget(path) {
176
- const target = parseTargetUrl(path);
177
- const pathname = target.pathname;
178
- const href = `${target.pathname}${target.search}${target.hash}`;
179
- const resolved = resolveRouteRequest(href);
180
- if (isPromise(resolved)) return resolved.then((next) => ({
181
- href,
182
- pathname,
183
- resolved: next
184
- }));
185
- return {
186
- href,
187
- pathname,
188
- resolved
189
- };
221
+ function resolveAppRouteRequest(app, pathname, href, signal) {
222
+ if (app.routes) {
223
+ const resolved = resolveRouteFromRoutes(pathname, app.routes);
224
+ if (!resolved) return null;
225
+ return {
226
+ kind: "render",
227
+ handler: resolved.handler,
228
+ params: resolved.params
229
+ };
230
+ }
231
+ return resolveRouteRequest(href, {
232
+ manifest: app.manifest,
233
+ auth: app.auth,
234
+ signal
235
+ });
190
236
  }
191
- function applyNavigationTarget(path, options, target) {
192
- const { href, pathname, resolved } = target;
193
- const previousPathname = currentPathname;
194
- saveScrollPosition(currentHref);
195
- if (!resolved) {
196
- if (isDevelopmentEnvironment()) logger.warn(`No route found for path: ${path}`);
197
- return;
237
+ function resolveNavigationTargetsForApps(pathname, href, signal) {
238
+ const apps = [...registeredApps];
239
+ if (apps.length === 1) {
240
+ const app = apps[0];
241
+ const resolved = resolveAppRouteRequest(app, pathname, href, signal);
242
+ if (isPromiseLike(resolved)) return Promise.resolve(resolved).then((next) => [{
243
+ app,
244
+ resolved: next
245
+ }]);
246
+ return [{
247
+ app,
248
+ resolved
249
+ }];
250
+ }
251
+ const syncTargets = [];
252
+ const pendingTargets = [];
253
+ for (const app of apps) {
254
+ const resolved = resolveAppRouteRequest(app, pathname, href, signal);
255
+ if (isPromiseLike(resolved)) {
256
+ pendingTargets.push(Promise.resolve(resolved).then((next) => ({
257
+ app,
258
+ resolved: next
259
+ })));
260
+ continue;
261
+ }
262
+ syncTargets.push({
263
+ app,
264
+ resolved
265
+ });
198
266
  }
199
- if (resolved.kind === "redirect") {
267
+ if (pendingTargets.length === 0) return syncTargets;
268
+ return Promise.all([...syncTargets.map((target) => Promise.resolve(target)), ...pendingTargets]);
269
+ }
270
+ function applyNavigationTargets(path, options, redirectState, pathname, href, targets) {
271
+ const previousPathname = currentPathname;
272
+ for (const target of targets) {
273
+ const resolved = target.resolved;
274
+ if (!resolved || resolved.kind !== "redirect") continue;
200
275
  const redirectTarget = parseTargetUrl(resolved.to);
201
276
  const redirectHref = `${redirectTarget.pathname}${redirectTarget.search}${redirectTarget.hash}`;
202
277
  if (redirectHref === href) {
203
278
  if (isDevelopmentEnvironment()) logger.warn(`Navigation guard redirected to the same path: ${redirectHref}`);
204
279
  return;
205
280
  }
206
- navigate(redirectHref, { history: "replace" });
281
+ if (redirectState.visited.has(redirectHref)) throw new Error(`[Askr] Navigation redirect cycle detected at ${redirectHref}.`);
282
+ if (redirectState.redirects >= MAX_NAVIGATION_REDIRECTS) throw new Error(`[Askr] Navigation redirect limit exceeded (${MAX_NAVIGATION_REDIRECTS}).`);
283
+ redirectState.visited.add(redirectHref);
284
+ redirectState.redirects++;
285
+ navigateWithRedirectState(redirectHref, { history: getRedirectHistoryMode(resolved.replace) }, redirectState);
207
286
  return;
208
287
  }
209
- const nextResolved = resolved.kind === "deny" ? createDeniedResolvedRoute(resolved.status) : {
210
- handler: resolved.handler,
211
- params: resolved.params
212
- };
213
- const historyMethod = options.history === "replace" ? "replaceState" : "pushState";
288
+ const matchedTargets = targets.filter((target) => target.resolved !== null);
289
+ if (matchedTargets.length === 0) {
290
+ if (isDevelopmentEnvironment()) logger.warn(`No route found for path: ${path}`);
291
+ return;
292
+ }
293
+ saveScrollPosition(currentHref);
294
+ const historyMethod = getNavigationHistoryMode(options) === "replace" ? "replaceState" : "pushState";
214
295
  window.history[historyMethod]({ path: href }, "", href);
215
296
  syncCurrentRouteSnapshot(window.location.pathname, window.location.search, window.location.hash);
216
- if (currentInstance) {
217
- if (pathname === currentPathname && isRenderResult(resolved)) {
218
- rerenderResolvedRoute(pathname, href);
219
- return;
297
+ for (const target of matchedTargets) {
298
+ const resolved = target.resolved;
299
+ if (resolved.kind === "redirect") continue;
300
+ if (pathname === target.app.pathname && isRenderResult(resolved)) {
301
+ rerenderResolvedRoute(target.app.instance, pathname, href);
302
+ syncAppRegistrationLocation(target.app, pathname, href);
303
+ continue;
220
304
  }
221
- remountResolvedRoute(nextResolved, pathname, href);
222
- if (pathname !== previousPathname) applyNavigationScroll(options.scroll ?? scrollRestorationOptions.navigation);
305
+ remountResolvedRoute(target.app.instance, resolved.kind === "deny" ? createDeniedResolvedRoute(resolved.status) : {
306
+ handler: resolved.handler,
307
+ params: resolved.params
308
+ }, pathname, href);
309
+ syncAppRegistrationLocation(target.app, pathname, href);
223
310
  }
311
+ if (pathname !== previousPathname) applyNavigationScroll(options.scroll ?? scrollRestorationOptions.navigation);
224
312
  }
225
313
  /** Register the current app instance (called by createSPA/hydrateSPA). */
226
- function registerAppInstance(instance, path) {
314
+ function registerAppInstance(instance, path, source = {}) {
315
+ const existingIndex = registeredApps.findIndex((app) => app.instance === instance);
316
+ const registration = {
317
+ instance,
318
+ pathname: path,
319
+ href: getWindowHref(),
320
+ ...source
321
+ };
322
+ if (existingIndex >= 0) registeredApps[existingIndex] = registration;
323
+ else registeredApps.push(registration);
227
324
  currentInstance = instance;
228
325
  currentPathname = path;
229
326
  currentHref = getWindowHref();
230
327
  syncCurrentRouteSnapshot(window.location.pathname, window.location.search, window.location.hash);
231
328
  if (isProductionEnvironment()) lockRouteRegistration();
232
329
  }
330
+ function unregisterAppInstance(instance) {
331
+ const existingIndex = registeredApps.findIndex((app) => app.instance === instance);
332
+ if (existingIndex >= 0) registeredApps.splice(existingIndex, 1);
333
+ if (currentInstance !== instance) return;
334
+ const nextApp = registeredApps.length > 0 ? registeredApps[registeredApps.length - 1] : null;
335
+ currentInstance = nextApp?.instance ?? null;
336
+ if (nextApp) {
337
+ currentPathname = nextApp.pathname;
338
+ currentHref = nextApp.href;
339
+ return;
340
+ }
341
+ activeRouteRequestId += 1;
342
+ activeRouteRequestController?.abort();
343
+ activeRouteRequestController = null;
344
+ currentPathname = "/";
345
+ currentHref = "/";
346
+ }
233
347
  /**
234
348
  * Navigate to a new path
235
349
  * Updates URL, resolves route, and re-mounts app with new handler
236
350
  */
237
351
  function navigate(path, options = {}) {
238
352
  if (typeof window === "undefined") return;
239
- const target = resolveNavigationTarget(path);
240
- if (isPromise(target)) {
241
- target.then((resolvedTarget) => applyNavigationTarget(path, options, resolvedTarget));
353
+ const initialTarget = parseTargetUrl(path);
354
+ navigateWithRedirectState(path, options, {
355
+ redirects: 0,
356
+ visited: new Set([`${initialTarget.pathname}${initialTarget.search}${initialTarget.hash}`])
357
+ });
358
+ }
359
+ function navigateWithRedirectState(path, options, redirectState) {
360
+ if (typeof window === "undefined") return;
361
+ const request = beginRouteRequest();
362
+ const target = parseTargetUrl(path);
363
+ const pathname = target.pathname;
364
+ const href = `${target.pathname}${target.search}${target.hash}`;
365
+ const resolvedTargets = resolveNavigationTargetsForApps(pathname, href, request.signal);
366
+ if (isPromiseLike(resolvedTargets)) {
367
+ Promise.resolve(resolvedTargets).then((targets) => {
368
+ if (isStaleRouteRequest(request.id)) return;
369
+ try {
370
+ applyNavigationTargets(path, options, redirectState, pathname, href, targets);
371
+ } catch (error) {
372
+ logger.error("[Askr] navigation failed:", error);
373
+ }
374
+ }, (error) => {
375
+ logger.error("[Askr] navigation failed:", error);
376
+ });
242
377
  return;
243
378
  }
244
- applyNavigationTarget(path, options, target);
379
+ applyNavigationTargets(path, options, redirectState, pathname, href, resolvedTargets);
245
380
  }
246
381
  /**
247
382
  * Handle browser back/forward buttons
248
383
  */
249
384
  function handlePopState(_event) {
385
+ const request = beginRouteRequest();
250
386
  const previousHref = currentHref;
251
387
  const pathname = window.location.pathname;
252
388
  const href = `${window.location.pathname}${window.location.search}${window.location.hash}`;
253
389
  saveScrollPosition(previousHref);
254
- if (!currentInstance) return;
255
- const resolved = resolveRouteRequest(href);
256
- const applyResolved = (resolved) => {
257
- if (!resolved) {
390
+ if (registeredApps.length === 0) return;
391
+ const applyResolved = (targets) => {
392
+ if (isStaleRouteRequest(request.id)) return;
393
+ const matchedTargets = targets.filter((target) => target.resolved !== null);
394
+ if (matchedTargets.length === 0) {
258
395
  if (isDevelopmentEnvironment()) logger.warn(`No route found for path: ${pathname}`);
259
396
  return;
260
397
  }
261
- if (resolved.kind === "redirect") {
262
- navigate(resolved.to, { history: "replace" });
398
+ for (const target of matchedTargets) {
399
+ const resolved = target.resolved;
400
+ if (resolved.kind !== "redirect") continue;
401
+ navigate(resolved.to, { history: getRedirectHistoryMode(resolved.replace) });
263
402
  return;
264
403
  }
265
404
  syncCurrentRouteSnapshot(window.location.pathname, window.location.search, window.location.hash);
266
- if (pathname === currentPathname && isRenderResult(resolved)) {
267
- rerenderResolvedRoute(pathname, href);
268
- return;
405
+ for (const target of matchedTargets) {
406
+ const resolved = target.resolved;
407
+ if (pathname === target.app.pathname && isRenderResult(resolved)) {
408
+ rerenderResolvedRoute(target.app.instance, pathname, href);
409
+ syncAppRegistrationLocation(target.app, pathname, href);
410
+ continue;
411
+ }
412
+ if (resolved.kind === "redirect") continue;
413
+ remountResolvedRoute(target.app.instance, resolved.kind === "deny" ? createDeniedResolvedRoute(resolved.status) : {
414
+ handler: resolved.handler,
415
+ params: resolved.params
416
+ }, pathname, href);
417
+ syncAppRegistrationLocation(target.app, pathname, href);
269
418
  }
270
- remountResolvedRoute(resolved.kind === "deny" ? createDeniedResolvedRoute(resolved.status) : {
271
- handler: resolved.handler,
272
- params: resolved.params
273
- }, pathname, href);
274
419
  applyHistoryScroll(href, _event.state);
275
420
  };
276
- if (isPromise(resolved)) {
277
- resolved.then((next) => applyResolved(next));
421
+ const resolvedTargets = resolveNavigationTargetsForApps(pathname, href, request.signal);
422
+ if (isPromiseLike(resolvedTargets)) {
423
+ Promise.resolve(resolvedTargets).then((next) => {
424
+ try {
425
+ applyResolved(next);
426
+ } catch (error) {
427
+ logger.error("[Askr] popstate navigation failed:", error);
428
+ }
429
+ }, (error) => {
430
+ logger.error("[Askr] popstate navigation failed:", error);
431
+ });
278
432
  return;
279
433
  }
280
- applyResolved(resolved);
434
+ applyResolved(resolvedTargets);
281
435
  }
282
436
  /**
283
437
  * Setup popstate listener for browser navigation
@@ -288,6 +442,6 @@ function initializeNavigation() {
288
442
  window.addEventListener("popstate", handlePopState);
289
443
  }
290
444
  //#endregion
291
- export { configureScrollRestoration, initializeNavigation, navigate, registerAppInstance };
445
+ export { configureScrollRestoration, initializeNavigation, navigate, registerAppInstance, unregisterAppInstance };
292
446
 
293
447
  //# sourceMappingURL=navigate.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"navigate.js","names":[],"sources":["../../src/router/navigate.ts"],"sourcesContent":["/**\n * Client-side navigation with History API\n */\n\nimport {\n resolveRoute,\n resolveRouteRequest,\n lockRouteRegistration,\n syncCurrentRouteSnapshot,\n type ResolvedRoute,\n} from './route';\nimport { mountComponent, type ComponentInstance } from '../runtime/component';\nimport {\n isDevelopmentEnvironment,\n isProductionEnvironment,\n} from '../common/env';\nimport { logger } from '../dev/logger';\nimport type { RouteRenderResult, RouteRequestResult } from '../common/router';\nimport { Fragment, ELEMENT_TYPE } from '../jsx';\nimport { DefaultPortal } from '../foundations/structures/portal';\n\n// Global app state for navigation\nlet currentInstance: ComponentInstance | null = null;\nlet currentPathname = '/';\nlet currentHref = '/';\nlet navigationInitialized = false;\n\nexport type NavigationScrollBehavior = 'top' | 'preserve';\nexport type HistoryScrollBehavior = 'restore' | 'top' | 'preserve';\n\nexport type ScrollRestorationOptions = {\n navigation?: NavigationScrollBehavior;\n history?: HistoryScrollBehavior;\n};\n\ntype NormalizedScrollRestorationOptions = {\n enabled: boolean;\n navigation: NavigationScrollBehavior;\n history: HistoryScrollBehavior;\n};\n\nconst DEFAULT_SCROLL_RESTORATION: NormalizedScrollRestorationOptions = {\n enabled: true,\n navigation: 'top',\n history: 'restore',\n};\n\nlet scrollRestorationOptions: NormalizedScrollRestorationOptions = {\n ...DEFAULT_SCROLL_RESTORATION,\n};\n\nconst scrollPositions = new Map<string, { x: number; y: number }>();\n\nexport type NavigateOptions = {\n history?: 'push' | 'replace';\n scroll?: NavigationScrollBehavior;\n};\n\nfunction getWindowHref(): string {\n if (typeof window === 'undefined') {\n return currentHref;\n }\n\n return `${window.location.pathname}${window.location.search}${window.location.hash}`;\n}\n\nfunction normalizeScrollRestorationOptions(\n options?: boolean | ScrollRestorationOptions\n): NormalizedScrollRestorationOptions {\n if (options === false) {\n return {\n enabled: false,\n navigation: DEFAULT_SCROLL_RESTORATION.navigation,\n history: DEFAULT_SCROLL_RESTORATION.history,\n };\n }\n\n if (options === true || options === undefined) {\n return { ...DEFAULT_SCROLL_RESTORATION };\n }\n\n return {\n enabled: true,\n navigation: options.navigation ?? DEFAULT_SCROLL_RESTORATION.navigation,\n history: options.history ?? DEFAULT_SCROLL_RESTORATION.history,\n };\n}\n\nfunction readScrollPosition(): { x: number; y: number } {\n if (typeof window === 'undefined') {\n return { x: 0, y: 0 };\n }\n\n const x =\n typeof window.scrollX === 'number'\n ? window.scrollX\n : typeof window.pageXOffset === 'number'\n ? window.pageXOffset\n : 0;\n const y =\n typeof window.scrollY === 'number'\n ? window.scrollY\n : typeof window.pageYOffset === 'number'\n ? window.pageYOffset\n : 0;\n\n return { x, y };\n}\n\nfunction writeHistoryScrollPosition(\n href: string,\n position: { x: number; y: number }\n): void {\n if (typeof window === 'undefined' || getWindowHref() !== href) {\n return;\n }\n if (typeof window.history?.replaceState !== 'function') {\n return;\n }\n const state =\n window.history.state && typeof window.history.state === 'object'\n ? window.history.state\n : {};\n\n window.history.replaceState(\n {\n ...state,\n path: href,\n scroll: position,\n },\n '',\n href\n );\n}\n\nfunction saveScrollPosition(href: string): void {\n if (!scrollRestorationOptions.enabled || typeof window === 'undefined') {\n return;\n }\n\n const position = readScrollPosition();\n scrollPositions.set(href, position);\n writeHistoryScrollPosition(href, position);\n}\n\nfunction scrollToPosition(position: { x: number; y: number }): void {\n if (typeof window === 'undefined' || typeof window.scrollTo !== 'function') {\n return;\n }\n\n window.scrollTo(position.x, position.y);\n}\n\nfunction applyNavigationScroll(behavior: NavigationScrollBehavior): void {\n if (!scrollRestorationOptions.enabled || behavior === 'preserve') {\n return;\n }\n\n scrollToPosition({ x: 0, y: 0 });\n}\n\nfunction applyHistoryScroll(href: string, state: PopStateEvent['state']): void {\n if (!scrollRestorationOptions.enabled) {\n return;\n }\n\n if (scrollRestorationOptions.history === 'preserve') {\n return;\n }\n\n if (scrollRestorationOptions.history === 'top') {\n scrollToPosition({ x: 0, y: 0 });\n return;\n }\n\n const fromState =\n state && typeof state === 'object' && 'scroll' in state\n ? (state.scroll as { x?: unknown; y?: unknown })\n : undefined;\n const saved =\n fromState &&\n typeof fromState.x === 'number' &&\n typeof fromState.y === 'number'\n ? { x: fromState.x, y: fromState.y }\n : scrollPositions.get(href);\n\n scrollToPosition(saved ?? { x: 0, y: 0 });\n}\n\nexport function configureScrollRestoration(\n options?: boolean | ScrollRestorationOptions\n): void {\n scrollRestorationOptions = normalizeScrollRestorationOptions(options);\n\n if (typeof window === 'undefined') {\n return;\n }\n\n if ('scrollRestoration' in window.history) {\n try {\n window.history.scrollRestoration = scrollRestorationOptions.enabled\n ? 'manual'\n : 'auto';\n } catch {\n // Ignore environments that expose but do not allow setting scrollRestoration.\n }\n }\n}\n\nfunction parseTargetUrl(path: string): URL {\n const pathname = window.location.pathname || '/';\n const search = window.location.search || '';\n const hash = window.location.hash || '';\n const href =\n typeof window.location.href === 'string' ? window.location.href : '';\n const base =\n href &&\n href !== 'about:blank' &&\n !href.startsWith('about:') &&\n !href.startsWith('data:')\n ? href\n : `http://localhost${pathname}${search}${hash}`;\n\n return new URL(path, base);\n}\n\nfunction isRenderResult(\n result: RouteRequestResult\n): result is RouteRenderResult {\n return result !== null && result.kind === 'render';\n}\n\nfunction isPromise<T>(value: T | Promise<T>): value is Promise<T> {\n return value instanceof Promise;\n}\n\nfunction createDeniedResolvedRoute(status: number): ResolvedRoute {\n return {\n handler: () => ({\n type: 'div',\n props: {\n 'data-route-denied': String(status),\n },\n children: [String(status)],\n }),\n params: {},\n };\n}\n\nfunction bindResolvedRouteHandler(\n resolved: ResolvedRoute\n): ComponentInstance['fn'] {\n return () =>\n resolved.handler(resolved.params) as ReturnType<ComponentInstance['fn']>;\n}\n\nfunction wrapRootRouteHandler(\n componentFn: ComponentInstance['fn']\n): ComponentInstance['fn'] {\n const wrappedFn: ComponentInstance['fn'] = (props, ctx) => {\n const out = componentFn(props, ctx);\n const portalVNode = {\n $$typeof: ELEMENT_TYPE,\n type: DefaultPortal,\n props: {},\n key: '__default_portal',\n } as unknown;\n\n return {\n $$typeof: ELEMENT_TYPE,\n type: Fragment,\n props: {\n children:\n out === undefined || out === null\n ? [portalVNode]\n : [out, portalVNode],\n },\n } as ReturnType<ComponentInstance['fn']>;\n };\n\n Object.defineProperty(wrappedFn, 'name', {\n value: componentFn.name || 'Component',\n });\n\n return wrappedFn;\n}\n\nfunction remountResolvedRoute(\n resolved: ResolvedRoute,\n pathname: string,\n href: string\n): boolean {\n if (!currentInstance) {\n return false;\n }\n\n // The route handler IS the component function\n // It takes params as props and renders the route\n currentInstance.fn = wrapRootRouteHandler(bindResolvedRouteHandler(resolved));\n currentInstance.props = {};\n\n // Reset state to prevent leakage from previous route\n // Each route navigation starts completely fresh\n currentInstance.stateValues = [];\n currentInstance.expectedStateIndices = [];\n currentInstance.firstRenderComplete = false;\n currentInstance.stateIndexCheck = -1;\n // Increment generation to invalidate pending async evaluations from previous route\n currentInstance.evaluationGeneration++;\n currentInstance.notifyUpdate = null;\n currentInstance.mountOperations = [];\n currentInstance._placeholder = undefined;\n currentInstance.hasPendingUpdate = false;\n\n // Route-local async work should create a fresh abort controller lazily.\n currentInstance.abortController = null;\n\n // Re-execute component against the existing host so reconciliation can\n // preserve any shared layout DOM between sibling routes.\n mountComponent(currentInstance);\n currentPathname = pathname;\n currentHref = href;\n return true;\n}\n\nfunction rerenderResolvedRoute(pathname: string, href: string): boolean {\n if (!currentInstance) {\n return false;\n }\n\n const resolved = resolveRoute(pathname);\n if (!resolved) {\n return false;\n }\n\n currentPathname = pathname;\n currentHref = href;\n currentInstance._enqueueRun?.();\n return true;\n}\n\nfunction resolveNavigationTarget(path: string) {\n const target = parseTargetUrl(path);\n const pathname = target.pathname;\n const href = `${target.pathname}${target.search}${target.hash}`;\n const resolved = resolveRouteRequest(href);\n\n if (isPromise(resolved)) {\n return resolved.then((next) => ({\n href,\n pathname,\n resolved: next,\n }));\n }\n\n return {\n href,\n pathname,\n resolved,\n };\n}\n\nfunction applyNavigationTarget(\n path: string,\n options: NavigateOptions,\n target: {\n href: string;\n pathname: string;\n resolved: RouteRequestResult;\n }\n): void {\n const { href, pathname, resolved } = target;\n const previousPathname = currentPathname;\n\n saveScrollPosition(currentHref);\n\n if (!resolved) {\n if (isDevelopmentEnvironment()) {\n logger.warn(`No route found for path: ${path}`);\n }\n return;\n }\n\n if (resolved.kind === 'redirect') {\n const redirectTarget = parseTargetUrl(resolved.to);\n const redirectHref = `${redirectTarget.pathname}${redirectTarget.search}${redirectTarget.hash}`;\n if (redirectHref === href) {\n if (isDevelopmentEnvironment()) {\n logger.warn(\n `Navigation guard redirected to the same path: ${redirectHref}`\n );\n }\n return;\n }\n\n navigate(redirectHref, { history: 'replace' });\n return;\n }\n\n const nextResolved =\n resolved.kind === 'deny'\n ? createDeniedResolvedRoute(resolved.status)\n : {\n handler: resolved.handler,\n params: resolved.params,\n };\n\n const historyMethod =\n options.history === 'replace' ? 'replaceState' : 'pushState';\n window.history[historyMethod]({ path: href }, '', href);\n syncCurrentRouteSnapshot(\n window.location.pathname,\n window.location.search,\n window.location.hash\n );\n\n if (currentInstance) {\n if (pathname === currentPathname && isRenderResult(resolved)) {\n rerenderResolvedRoute(pathname, href);\n return;\n }\n\n remountResolvedRoute(nextResolved, pathname, href);\n if (pathname !== previousPathname) {\n applyNavigationScroll(\n options.scroll ?? scrollRestorationOptions.navigation\n );\n }\n }\n}\n\n/** Register the current app instance (called by createSPA/hydrateSPA). */\nexport function registerAppInstance(\n instance: ComponentInstance,\n path: string\n): void {\n currentInstance = instance;\n currentPathname = path;\n currentHref = getWindowHref();\n syncCurrentRouteSnapshot(\n window.location.pathname,\n window.location.search,\n window.location.hash\n );\n // Lock further route registrations after the app has started — but allow tests to register routes.\n // Enforce only in production to avoid breaking test infra which registers routes dynamically.\n if (isProductionEnvironment()) {\n lockRouteRegistration();\n }\n}\n\n/**\n * Navigate to a new path\n * Updates URL, resolves route, and re-mounts app with new handler\n */\nexport function navigate(path: string, options: NavigateOptions = {}): void {\n if (typeof window === 'undefined') {\n // SSR context\n return;\n }\n\n const target = resolveNavigationTarget(path);\n if (isPromise(target)) {\n void target.then((resolvedTarget) =>\n applyNavigationTarget(path, options, resolvedTarget)\n );\n return;\n }\n\n applyNavigationTarget(path, options, target);\n}\n\n/**\n * Handle browser back/forward buttons\n */\nfunction handlePopState(_event: PopStateEvent): void {\n const previousHref = currentHref;\n const pathname = window.location.pathname;\n const href = `${window.location.pathname}${window.location.search}${window.location.hash}`;\n\n saveScrollPosition(previousHref);\n\n if (!currentInstance) {\n return;\n }\n\n const resolved = resolveRouteRequest(href);\n\n const applyResolved = (resolved: RouteRequestResult) => {\n if (!resolved) {\n if (isDevelopmentEnvironment()) {\n logger.warn(`No route found for path: ${pathname}`);\n }\n return;\n }\n\n if (resolved.kind === 'redirect') {\n navigate(resolved.to, { history: 'replace' });\n return;\n }\n\n syncCurrentRouteSnapshot(\n window.location.pathname,\n window.location.search,\n window.location.hash\n );\n\n if (pathname === currentPathname && isRenderResult(resolved)) {\n rerenderResolvedRoute(pathname, href);\n return;\n }\n\n remountResolvedRoute(\n resolved.kind === 'deny'\n ? createDeniedResolvedRoute(resolved.status)\n : {\n handler: resolved.handler,\n params: resolved.params,\n },\n pathname,\n href\n );\n\n applyHistoryScroll(href, _event.state);\n };\n\n if (isPromise(resolved)) {\n void resolved.then((next) => applyResolved(next));\n return;\n }\n\n applyResolved(resolved);\n}\n\n/**\n * Setup popstate listener for browser navigation\n */\nexport function initializeNavigation(): void {\n if (typeof window === 'undefined' || navigationInitialized) {\n return;\n }\n\n navigationInitialized = true;\n window.addEventListener('popstate', handlePopState);\n}\n\n/**\n * Cleanup navigation listeners\n */\nexport function cleanupNavigation(): void {\n if (typeof window === 'undefined' || !navigationInitialized) {\n return;\n }\n\n navigationInitialized = false;\n window.removeEventListener('popstate', handlePopState);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"navigate.js","names":[],"sources":["../../src/router/navigate.ts"],"sourcesContent":["/**\n * Client-side navigation with History API\n */\n\nimport {\n resolveRouteRequest,\n resolveRouteFromRoutes,\n lockRouteRegistration,\n syncCurrentRouteSnapshot,\n type ResolvedRoute,\n} from './route';\nimport {\n cleanupComponent,\n mountComponent,\n type ComponentInstance,\n} from '../runtime/component';\nimport {\n isDevelopmentEnvironment,\n isProductionEnvironment,\n} from '../common/env';\nimport { logger } from '../dev/logger';\nimport type {\n Route,\n RouteAuthOptions,\n RouteManifest,\n RouteRenderResult,\n RouteRequestResult,\n} from '../common/router';\nimport { Fragment, ELEMENT_TYPE } from '../jsx';\nimport {\n DefaultPortal,\n clearDefaultPortalForInstance,\n} from '../foundations/structures/portal';\nimport { isPromiseLike } from '../common/promise';\nimport { cleanupInstancesUnder } from '../renderer/cleanup';\n\n// Global app state for navigation\nlet currentInstance: ComponentInstance | null = null;\nlet currentPathname = '/';\nlet currentHref = '/';\nlet navigationInitialized = false;\nlet activeRouteRequestId = 0;\nlet activeRouteRequestController: AbortController | null = null;\nconst MAX_NAVIGATION_REDIRECTS = 20;\n\ntype AppNavigationSource = {\n manifest?: RouteManifest;\n routes?: readonly Route[];\n auth?: RouteAuthOptions;\n};\n\ntype AppRegistration = AppNavigationSource & {\n instance: ComponentInstance;\n pathname: string;\n href: string;\n};\n\nconst registeredApps: AppRegistration[] = [];\n\nfunction syncAppRegistrationLocation(\n app: AppRegistration,\n pathname: string,\n href: string\n): void {\n app.pathname = pathname;\n app.href = href;\n}\n\nexport type NavigationScrollBehavior = 'top' | 'preserve';\nexport type HistoryScrollBehavior = 'restore' | 'top' | 'preserve';\n\nexport type ScrollRestorationOptions = {\n navigation?: NavigationScrollBehavior;\n history?: HistoryScrollBehavior;\n};\n\ntype NormalizedScrollRestorationOptions = {\n enabled: boolean;\n navigation: NavigationScrollBehavior;\n history: HistoryScrollBehavior;\n};\n\nconst DEFAULT_SCROLL_RESTORATION: NormalizedScrollRestorationOptions = {\n enabled: true,\n navigation: 'top',\n history: 'restore',\n};\n\nlet scrollRestorationOptions: NormalizedScrollRestorationOptions = {\n ...DEFAULT_SCROLL_RESTORATION,\n};\n\nconst scrollPositions = new Map<string, { x: number; y: number }>();\n\nexport type NavigateOptions = {\n history?: 'push' | 'replace';\n replace?: boolean;\n scroll?: NavigationScrollBehavior;\n};\n\nfunction getWindowHref(): string {\n if (typeof window === 'undefined') {\n return currentHref;\n }\n\n return `${window.location.pathname}${window.location.search}${window.location.hash}`;\n}\n\nfunction normalizeScrollRestorationOptions(\n options?: boolean | ScrollRestorationOptions\n): NormalizedScrollRestorationOptions {\n if (options === false) {\n return {\n enabled: false,\n navigation: DEFAULT_SCROLL_RESTORATION.navigation,\n history: DEFAULT_SCROLL_RESTORATION.history,\n };\n }\n\n if (options === true || options === undefined) {\n return { ...DEFAULT_SCROLL_RESTORATION };\n }\n\n return {\n enabled: true,\n navigation: options.navigation ?? DEFAULT_SCROLL_RESTORATION.navigation,\n history: options.history ?? DEFAULT_SCROLL_RESTORATION.history,\n };\n}\n\nfunction readScrollPosition(): { x: number; y: number } {\n if (typeof window === 'undefined') {\n return { x: 0, y: 0 };\n }\n\n const x =\n typeof window.scrollX === 'number'\n ? window.scrollX\n : typeof window.pageXOffset === 'number'\n ? window.pageXOffset\n : 0;\n const y =\n typeof window.scrollY === 'number'\n ? window.scrollY\n : typeof window.pageYOffset === 'number'\n ? window.pageYOffset\n : 0;\n\n return { x, y };\n}\n\nfunction writeHistoryScrollPosition(\n href: string,\n position: { x: number; y: number }\n): void {\n if (typeof window === 'undefined' || getWindowHref() !== href) {\n return;\n }\n if (typeof window.history?.replaceState !== 'function') {\n return;\n }\n const state =\n window.history.state && typeof window.history.state === 'object'\n ? window.history.state\n : {};\n\n window.history.replaceState(\n {\n ...state,\n path: href,\n scroll: position,\n },\n '',\n href\n );\n}\n\nfunction saveScrollPosition(href: string): void {\n if (!scrollRestorationOptions.enabled || typeof window === 'undefined') {\n return;\n }\n\n const position = readScrollPosition();\n scrollPositions.set(href, position);\n writeHistoryScrollPosition(href, position);\n}\n\nfunction scrollToPosition(position: { x: number; y: number }): void {\n if (typeof window === 'undefined' || typeof window.scrollTo !== 'function') {\n return;\n }\n\n window.scrollTo(position.x, position.y);\n}\n\nfunction applyNavigationScroll(behavior: NavigationScrollBehavior): void {\n if (!scrollRestorationOptions.enabled || behavior === 'preserve') {\n return;\n }\n\n scrollToPosition({ x: 0, y: 0 });\n}\n\nfunction applyHistoryScroll(href: string, state: PopStateEvent['state']): void {\n if (!scrollRestorationOptions.enabled) {\n return;\n }\n\n if (scrollRestorationOptions.history === 'preserve') {\n return;\n }\n\n if (scrollRestorationOptions.history === 'top') {\n scrollToPosition({ x: 0, y: 0 });\n return;\n }\n\n const fromState =\n state && typeof state === 'object' && 'scroll' in state\n ? (state.scroll as { x?: unknown; y?: unknown })\n : undefined;\n const saved =\n fromState &&\n typeof fromState.x === 'number' &&\n typeof fromState.y === 'number'\n ? { x: fromState.x, y: fromState.y }\n : scrollPositions.get(href);\n\n scrollToPosition(saved ?? { x: 0, y: 0 });\n}\n\nexport function configureScrollRestoration(\n options?: boolean | ScrollRestorationOptions\n): void {\n scrollRestorationOptions = normalizeScrollRestorationOptions(options);\n\n if (typeof window === 'undefined') {\n return;\n }\n\n if ('scrollRestoration' in window.history) {\n try {\n window.history.scrollRestoration = scrollRestorationOptions.enabled\n ? 'manual'\n : 'auto';\n } catch {\n // Ignore environments that expose but do not allow setting scrollRestoration.\n }\n }\n}\n\nfunction parseTargetUrl(path: string): URL {\n const pathname = window.location.pathname || '/';\n const search = window.location.search || '';\n const hash = window.location.hash || '';\n const href =\n typeof window.location.href === 'string' ? window.location.href : '';\n const base =\n href &&\n href !== 'about:blank' &&\n !href.startsWith('about:') &&\n !href.startsWith('data:')\n ? href\n : `http://localhost${pathname}${search}${hash}`;\n\n return new URL(path, base);\n}\n\nfunction isRenderResult(\n result: RouteRequestResult\n): result is RouteRenderResult {\n return result !== null && result.kind === 'render';\n}\n\nfunction getRedirectHistoryMode(\n replace: boolean | undefined\n): 'push' | 'replace' {\n return replace === false ? 'push' : 'replace';\n}\n\nfunction getNavigationHistoryMode(\n options: NavigateOptions\n): 'push' | 'replace' {\n if (options.history) {\n return options.history;\n }\n\n return options.replace ? 'replace' : 'push';\n}\n\nfunction beginRouteRequest(): { id: number; signal: AbortSignal } {\n activeRouteRequestId += 1;\n activeRouteRequestController?.abort();\n activeRouteRequestController = new AbortController();\n\n return {\n id: activeRouteRequestId,\n signal: activeRouteRequestController.signal,\n };\n}\n\nfunction isStaleRouteRequest(requestId: number): boolean {\n return requestId !== activeRouteRequestId;\n}\n\nfunction createDeniedResolvedRoute(status: number): ResolvedRoute {\n return {\n handler: () => ({\n type: 'div',\n props: {\n 'data-route-denied': String(status),\n },\n children: [String(status)],\n }),\n params: {},\n };\n}\n\nfunction bindResolvedRouteHandler(\n resolved: ResolvedRoute\n): ComponentInstance['fn'] {\n return () =>\n resolved.handler(resolved.params) as ReturnType<ComponentInstance['fn']>;\n}\n\nfunction wrapRootRouteHandler(\n componentFn: ComponentInstance['fn']\n): ComponentInstance['fn'] {\n const wrappedFn: ComponentInstance['fn'] = (props, ctx) => {\n const out = componentFn(props, ctx);\n if (isPromiseLike(out)) {\n throw new Error(\n 'Async components are not supported. Components must return synchronously.'\n );\n }\n const portalVNode = {\n $$typeof: ELEMENT_TYPE,\n type: DefaultPortal,\n props: {},\n key: '__default_portal',\n } as unknown;\n\n return {\n $$typeof: ELEMENT_TYPE,\n type: Fragment,\n props: {\n children:\n out === undefined || out === null\n ? [portalVNode]\n : [out, portalVNode],\n },\n } as ReturnType<ComponentInstance['fn']>;\n };\n\n Object.defineProperty(wrappedFn, 'name', {\n value: componentFn.name || 'Component',\n });\n\n return wrappedFn;\n}\n\nfunction cleanupRouteOwnership(instance: ComponentInstance): void {\n const cleanupErrors: unknown[] = [];\n const children = instance.target\n ? Array.from(instance.target.childNodes)\n : [];\n\n for (const child of children) {\n try {\n cleanupInstancesUnder(child, { strict: instance.cleanupStrict });\n } catch (error) {\n cleanupErrors.push(error);\n }\n }\n\n try {\n cleanupComponent(instance);\n } catch (error) {\n cleanupErrors.push(error);\n }\n\n if (cleanupErrors.length > 0) {\n throw new AggregateError(cleanupErrors, 'Route cleanup failed');\n }\n}\n\nfunction remountResolvedRoute(\n instance: ComponentInstance,\n resolved: ResolvedRoute,\n pathname: string,\n href: string\n): boolean {\n // The route handler IS the component function\n // It takes params as props and renders the route\n clearDefaultPortalForInstance(instance);\n cleanupRouteOwnership(instance);\n\n instance.fn = wrapRootRouteHandler(bindResolvedRouteHandler(resolved));\n instance.props = {};\n\n // Reset state to prevent leakage from previous route\n // Each route navigation starts completely fresh\n instance.stateValues = [];\n instance.expectedStateIndices = [];\n instance.firstRenderComplete = false;\n instance.stateIndexCheck = -1;\n // Increment generation to invalidate pending async evaluations from previous route\n instance.evaluationGeneration++;\n instance.notifyUpdate = null;\n instance.mountOperations = [];\n instance.cleanupFns = [];\n instance._placeholder = undefined;\n instance.hasPendingUpdate = false;\n instance._currentRenderToken = undefined;\n instance.lastRenderToken = 0;\n instance._pendingReadSources = undefined;\n instance._lastReadSources = undefined;\n\n // Route-local async work should create a fresh abort controller lazily.\n instance.abortController = null;\n\n // Re-execute component against the existing host so reconciliation can\n // preserve any shared layout DOM between sibling routes.\n mountComponent(instance);\n currentPathname = pathname;\n currentHref = href;\n return true;\n}\n\nfunction rerenderResolvedRoute(\n instance: ComponentInstance,\n pathname: string,\n href: string\n): boolean {\n currentPathname = pathname;\n currentHref = href;\n instance._enqueueRun?.();\n return true;\n}\n\nfunction resolveAppRouteRequest(\n app: AppRegistration,\n pathname: string,\n href: string,\n signal: AbortSignal\n): RouteRequestResult | Promise<RouteRequestResult> {\n if (app.routes) {\n const resolved = resolveRouteFromRoutes(pathname, app.routes);\n if (!resolved) {\n return null;\n }\n\n return {\n kind: 'render',\n handler: resolved.handler,\n params: resolved.params,\n };\n }\n\n return resolveRouteRequest(href, {\n manifest: app.manifest,\n auth: app.auth,\n signal,\n });\n}\n\ntype AppNavigationTarget = {\n app: AppRegistration;\n resolved: RouteRequestResult;\n};\n\nfunction resolveNavigationTargetsForApps(\n pathname: string,\n href: string,\n signal: AbortSignal\n): AppNavigationTarget[] | Promise<AppNavigationTarget[]> {\n const apps = [...registeredApps];\n\n if (apps.length === 1) {\n const app = apps[0]!;\n const resolved = resolveAppRouteRequest(app, pathname, href, signal);\n if (isPromiseLike<RouteRequestResult>(resolved)) {\n return Promise.resolve(resolved).then((next) => [\n {\n app,\n resolved: next,\n },\n ]);\n }\n\n return [\n {\n app,\n resolved,\n },\n ];\n }\n\n const syncTargets: AppNavigationTarget[] = [];\n const pendingTargets: Array<Promise<AppNavigationTarget>> = [];\n\n for (const app of apps) {\n const resolved = resolveAppRouteRequest(app, pathname, href, signal);\n if (isPromiseLike<RouteRequestResult>(resolved)) {\n pendingTargets.push(\n Promise.resolve(resolved).then((next) => ({ app, resolved: next }))\n );\n continue;\n }\n\n syncTargets.push({ app, resolved });\n }\n\n if (pendingTargets.length === 0) {\n return syncTargets;\n }\n\n return Promise.all([\n ...syncTargets.map((target) => Promise.resolve(target)),\n ...pendingTargets,\n ]);\n}\n\nfunction applyNavigationTargets(\n path: string,\n options: NavigateOptions,\n redirectState: NavigationRedirectState,\n pathname: string,\n href: string,\n targets: AppNavigationTarget[]\n): void {\n const previousPathname = currentPathname;\n\n for (const target of targets) {\n const resolved = target.resolved;\n if (!resolved || resolved.kind !== 'redirect') {\n continue;\n }\n\n const redirectTarget = parseTargetUrl(resolved.to);\n const redirectHref = `${redirectTarget.pathname}${redirectTarget.search}${redirectTarget.hash}`;\n if (redirectHref === href) {\n if (isDevelopmentEnvironment()) {\n logger.warn(\n `Navigation guard redirected to the same path: ${redirectHref}`\n );\n }\n return;\n }\n\n if (redirectState.visited.has(redirectHref)) {\n throw new Error(\n `[Askr] Navigation redirect cycle detected at ${redirectHref}.`\n );\n }\n if (redirectState.redirects >= MAX_NAVIGATION_REDIRECTS) {\n throw new Error(\n `[Askr] Navigation redirect limit exceeded (${MAX_NAVIGATION_REDIRECTS}).`\n );\n }\n\n redirectState.visited.add(redirectHref);\n redirectState.redirects++;\n navigateWithRedirectState(\n redirectHref,\n {\n history: getRedirectHistoryMode(resolved.replace),\n },\n redirectState\n );\n return;\n }\n\n const matchedTargets = targets.filter((target) => target.resolved !== null);\n if (matchedTargets.length === 0) {\n if (isDevelopmentEnvironment()) {\n logger.warn(`No route found for path: ${path}`);\n }\n return;\n }\n\n saveScrollPosition(currentHref);\n\n const historyMethod =\n getNavigationHistoryMode(options) === 'replace'\n ? 'replaceState'\n : 'pushState';\n window.history[historyMethod]({ path: href }, '', href);\n syncCurrentRouteSnapshot(\n window.location.pathname,\n window.location.search,\n window.location.hash\n );\n\n for (const target of matchedTargets) {\n const resolved = target.resolved!;\n if (resolved.kind === 'redirect') {\n continue;\n }\n\n if (pathname === target.app.pathname && isRenderResult(resolved)) {\n rerenderResolvedRoute(target.app.instance, pathname, href);\n syncAppRegistrationLocation(target.app, pathname, href);\n continue;\n }\n\n remountResolvedRoute(\n target.app.instance,\n resolved.kind === 'deny'\n ? createDeniedResolvedRoute(resolved.status)\n : {\n handler: resolved.handler,\n params: resolved.params,\n },\n pathname,\n href\n );\n syncAppRegistrationLocation(target.app, pathname, href);\n }\n\n if (pathname !== previousPathname) {\n applyNavigationScroll(\n options.scroll ?? scrollRestorationOptions.navigation\n );\n }\n}\n\n/** Register the current app instance (called by createSPA/hydrateSPA). */\nexport function registerAppInstance(\n instance: ComponentInstance,\n path: string,\n source: AppNavigationSource = {}\n): void {\n const existingIndex = registeredApps.findIndex(\n (app) => app.instance === instance\n );\n const registration: AppRegistration = {\n instance,\n pathname: path,\n href: getWindowHref(),\n ...source,\n };\n if (existingIndex >= 0) {\n registeredApps[existingIndex] = registration;\n } else {\n registeredApps.push(registration);\n }\n\n currentInstance = instance;\n currentPathname = path;\n currentHref = getWindowHref();\n syncCurrentRouteSnapshot(\n window.location.pathname,\n window.location.search,\n window.location.hash\n );\n // Lock further route registrations after the app has started — but allow tests to register routes.\n // Enforce only in production to avoid breaking test infra which registers routes dynamically.\n if (isProductionEnvironment()) {\n lockRouteRegistration();\n }\n}\n\nexport function unregisterAppInstance(instance: ComponentInstance): void {\n const existingIndex = registeredApps.findIndex(\n (app) => app.instance === instance\n );\n if (existingIndex >= 0) {\n registeredApps.splice(existingIndex, 1);\n }\n\n if (currentInstance !== instance) {\n return;\n }\n\n const nextApp =\n registeredApps.length > 0\n ? registeredApps[registeredApps.length - 1]!\n : null;\n currentInstance = nextApp?.instance ?? null;\n if (nextApp) {\n currentPathname = nextApp.pathname;\n currentHref = nextApp.href;\n return;\n }\n\n activeRouteRequestId += 1;\n activeRouteRequestController?.abort();\n activeRouteRequestController = null;\n currentPathname = '/';\n currentHref = '/';\n}\n\n/**\n * Navigate to a new path\n * Updates URL, resolves route, and re-mounts app with new handler\n */\nexport function navigate(path: string, options: NavigateOptions = {}): void {\n if (typeof window === 'undefined') {\n return;\n }\n\n const initialTarget = parseTargetUrl(path);\n navigateWithRedirectState(path, options, {\n redirects: 0,\n visited: new Set([\n `${initialTarget.pathname}${initialTarget.search}${initialTarget.hash}`,\n ]),\n });\n}\n\ntype NavigationRedirectState = {\n redirects: number;\n visited: Set<string>;\n};\n\nfunction navigateWithRedirectState(\n path: string,\n options: NavigateOptions,\n redirectState: NavigationRedirectState\n): void {\n if (typeof window === 'undefined') {\n // SSR context\n return;\n }\n\n const request = beginRouteRequest();\n\n const target = parseTargetUrl(path);\n const pathname = target.pathname;\n const href = `${target.pathname}${target.search}${target.hash}`;\n const resolvedTargets = resolveNavigationTargetsForApps(\n pathname,\n href,\n request.signal\n );\n\n if (isPromiseLike(resolvedTargets)) {\n void Promise.resolve(resolvedTargets).then(\n (targets) => {\n if (isStaleRouteRequest(request.id)) {\n return;\n }\n\n try {\n applyNavigationTargets(\n path,\n options,\n redirectState,\n pathname,\n href,\n targets\n );\n } catch (error) {\n logger.error('[Askr] navigation failed:', error);\n }\n },\n (error) => {\n logger.error('[Askr] navigation failed:', error);\n }\n );\n return;\n }\n\n applyNavigationTargets(\n path,\n options,\n redirectState,\n pathname,\n href,\n resolvedTargets\n );\n}\n\n/**\n * Handle browser back/forward buttons\n */\nfunction handlePopState(_event: PopStateEvent): void {\n const request = beginRouteRequest();\n const previousHref = currentHref;\n const pathname = window.location.pathname;\n const href = `${window.location.pathname}${window.location.search}${window.location.hash}`;\n\n saveScrollPosition(previousHref);\n\n if (registeredApps.length === 0) {\n return;\n }\n\n const applyResolved = (targets: AppNavigationTarget[]) => {\n if (isStaleRouteRequest(request.id)) {\n return;\n }\n\n const matchedTargets = targets.filter((target) => target.resolved !== null);\n if (matchedTargets.length === 0) {\n if (isDevelopmentEnvironment()) {\n logger.warn(`No route found for path: ${pathname}`);\n }\n return;\n }\n\n for (const target of matchedTargets) {\n const resolved = target.resolved!;\n if (resolved.kind !== 'redirect') {\n continue;\n }\n\n navigate(resolved.to, {\n history: getRedirectHistoryMode(resolved.replace),\n });\n return;\n }\n\n syncCurrentRouteSnapshot(\n window.location.pathname,\n window.location.search,\n window.location.hash\n );\n\n for (const target of matchedTargets) {\n const resolved = target.resolved!;\n if (pathname === target.app.pathname && isRenderResult(resolved)) {\n rerenderResolvedRoute(target.app.instance, pathname, href);\n syncAppRegistrationLocation(target.app, pathname, href);\n continue;\n }\n\n if (resolved.kind === 'redirect') {\n continue;\n }\n\n remountResolvedRoute(\n target.app.instance,\n resolved.kind === 'deny'\n ? createDeniedResolvedRoute(resolved.status)\n : {\n handler: resolved.handler,\n params: resolved.params,\n },\n pathname,\n href\n );\n syncAppRegistrationLocation(target.app, pathname, href);\n }\n\n applyHistoryScroll(href, _event.state);\n };\n\n const resolvedTargets = resolveNavigationTargetsForApps(\n pathname,\n href,\n request.signal\n );\n\n if (isPromiseLike<AppNavigationTarget[]>(resolvedTargets)) {\n void Promise.resolve(resolvedTargets).then(\n (next) => {\n try {\n applyResolved(next);\n } catch (error) {\n logger.error('[Askr] popstate navigation failed:', error);\n }\n },\n (error) => {\n logger.error('[Askr] popstate navigation failed:', error);\n }\n );\n return;\n }\n\n applyResolved(resolvedTargets);\n}\n\n/**\n * Setup popstate listener for browser navigation\n */\nexport function initializeNavigation(): void {\n if (typeof window === 'undefined' || navigationInitialized) {\n return;\n }\n\n navigationInitialized = true;\n window.addEventListener('popstate', handlePopState);\n}\n\n/**\n * Cleanup navigation listeners\n */\nexport function cleanupNavigation(): void {\n activeRouteRequestId += 1;\n activeRouteRequestController?.abort();\n activeRouteRequestController = null;\n\n if (typeof window === 'undefined' || !navigationInitialized) {\n return;\n }\n\n navigationInitialized = false;\n window.removeEventListener('popstate', handlePopState);\n}\n"],"mappings":"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"}
@@ -1,15 +1,15 @@
1
- import { AccessDecision, AccessDenyStatus, AccessRedirectStatus, RouteAuthOptions, RouteContext, RoutePolicy } from "../common/router.js";
1
+ import { AccessAllowDecision, AccessDecision, AccessDenyDecision, AccessDenyStatus, AccessRedirectDecision, AccessRedirectStatus, RouteAuthOptions, RouteContext, RoutePolicy } from "../common/router.js";
2
2
 
3
3
  //#region src/router/policy.d.ts
4
- declare function allow(): AccessDecision;
4
+ declare function allow(): AccessAllowDecision;
5
5
  declare function redirect(to: string, init?: {
6
6
  status?: AccessRedirectStatus;
7
7
  replace?: boolean;
8
- }): AccessDecision;
9
- declare function deny(status: AccessDenyStatus): AccessDecision;
10
- declare function unauthorized(): AccessDecision;
11
- declare function forbidden(): AccessDecision;
12
- declare function notFound(): AccessDecision;
8
+ }): AccessRedirectDecision;
9
+ declare function deny(status: AccessDenyStatus): AccessDenyDecision;
10
+ declare function unauthorized(): AccessDenyDecision;
11
+ declare function forbidden(): AccessDenyDecision;
12
+ declare function notFound(): AccessDenyDecision;
13
13
  declare function normalizeAccessDecision(result: AccessDecision): AccessDecision;
14
14
  declare function withRouteAuthOptions(context: RouteContext, auth: RouteAuthOptions | undefined): RouteContext;
15
15
  declare function requireAuth(): RoutePolicy;
@@ -1 +1 @@
1
- {"version":3,"file":"policy.d.ts","names":[],"sources":["../../src/router/policy.ts"],"mappings":";;;iBAuBgB,KAAA,CAAA,GAAS,cAAA;AAAA,iBAIT,QAAA,CACd,EAAA,UACA,IAAA;EACE,MAAA,GAAS,oBAAA;EACT,OAAA;AAAA,IAED,cAAA;AAAA,iBASa,IAAA,CAAK,MAAA,EAAQ,gBAAA,GAAmB,cAAA;AAAA,iBAIhC,YAAA,CAAA,GAAgB,cAAA;AAAA,iBAIhB,SAAA,CAAA,GAAa,cAAA;AAAA,iBAIb,QAAA,CAAA,GAAY,cAAA;AAAA,iBAIZ,uBAAA,CACd,MAAA,EAAQ,cAAA,GACP,cAAA;AAAA,iBAIa,oBAAA,CACd,OAAA,EAAS,YAAA,EACT,IAAA,EAAM,gBAAA,eACL,YAAA;AAAA,iBA8Ha,WAAA,CAAA,GAAe,WAAA;AAAA,iBAUf,YAAA,CAAA,GAAgB,WAAA;AAAA,iBAUhB,WAAA,CAAY,IAAA,WAAe,WAAA;AAAA,iBAmB3B,iBAAA,CAAkB,UAAA,WAAqB,WAAA;AAAA,iBAmBjC,mBAAA,CACpB,MAAA,EAAQ,WAAA,EACR,OAAA,EAAS,YAAA,GACR,OAAA,CAAQ,cAAA"}
1
+ {"version":3,"file":"policy.d.ts","names":[],"sources":["../../src/router/policy.ts"],"mappings":";;;iBAuBgB,KAAA,CAAA,GAAS,mBAAmB;AAAA,iBAI5B,QAAA,CACd,EAAA,UACA,IAAA;EACE,MAAA,GAAS,oBAAA;EACT,OAAA;AAAA,IAED,sBAAsB;AAAA,iBAST,IAAA,CAAK,MAAA,EAAQ,gBAAA,GAAmB,kBAAkB;AAAA,iBAIlD,YAAA,CAAA,GAAgB,kBAAkB;AAAA,iBAIlC,SAAA,CAAA,GAAa,kBAAkB;AAAA,iBAI/B,QAAA,CAAA,GAAY,kBAAkB;AAAA,iBAI9B,uBAAA,CACd,MAAA,EAAQ,cAAA,GACP,cAAc;AAAA,iBAID,oBAAA,CACd,OAAA,EAAS,YAAA,EACT,IAAA,EAAM,gBAAA,eACL,YAAA;AAAA,iBA8Ha,WAAA,CAAA,GAAe,WAAW;AAAA,iBAU1B,YAAA,CAAA,GAAgB,WAAW;AAAA,iBAU3B,WAAA,CAAY,IAAA,WAAe,WAAW;AAAA,iBAqBtC,iBAAA,CAAkB,UAAA,WAAqB,WAAW;AAAA,iBAqB5C,mBAAA,CACpB,MAAA,EAAQ,WAAA,EACR,OAAA,EAAS,YAAA,GACR,OAAA,CAAQ,cAAA"}
@@ -1,8 +1,6 @@
1
+ import { isPromiseLike } from "../common/promise.js";
1
2
  //#region src/router/policy.ts
2
3
  const ROUTE_AUTH_OPTIONS = Symbol.for("__ASKR_ROUTE_AUTH_OPTIONS__");
3
- function isPromise(value) {
4
- return value instanceof Promise;
5
- }
6
4
  function allow() {
7
5
  return { kind: "allow" };
8
6
  }
@@ -72,12 +70,12 @@ function defaultHasPermission(user, permission) {
72
70
  }
73
71
  function resolveUnauthenticatedRedirect(context) {
74
72
  const loginPath = resolvePathSetting(assertRouteAuthOptions(context).loginPath, "/login", context);
75
- if (isPromise(loginPath)) return loginPath.then((nextPath) => redirect(appendNextTarget(nextPath, context.href), { replace: context.mode === "spa" }));
73
+ if (isPromiseLike(loginPath)) return Promise.resolve(loginPath).then((nextPath) => redirect(appendNextTarget(nextPath, context.href), { replace: context.mode === "spa" }));
76
74
  return redirect(appendNextTarget(loginPath, context.href), { replace: context.mode === "spa" });
77
75
  }
78
76
  function resolveAuthenticatedRedirect(context) {
79
77
  const target = resolvePathSetting(assertRouteAuthOptions(context).guestRedirectTo, "/", context);
80
- if (isPromise(target)) return target.then((nextTarget) => redirect(nextTarget, { replace: true }));
78
+ if (isPromiseLike(target)) return Promise.resolve(target).then((nextTarget) => redirect(nextTarget, { replace: true }));
81
79
  return redirect(target, { replace: true });
82
80
  }
83
81
  function requireAuth() {
@@ -97,7 +95,7 @@ function requireRole(role) {
97
95
  if (!context.user) return resolveUnauthenticatedRedirect(context);
98
96
  const auth = assertRouteAuthOptions(context);
99
97
  const hasRole = auth.hasRole ? auth.hasRole(context.user, role, context) : defaultHasRole(context.user, role);
100
- if (isPromise(hasRole)) return hasRole.then((next) => next ? allow() : forbidden());
98
+ if (isPromiseLike(hasRole)) return Promise.resolve(hasRole).then((next) => next ? allow() : forbidden());
101
99
  return hasRole ? allow() : forbidden();
102
100
  };
103
101
  }
@@ -106,7 +104,7 @@ function requirePermission(permission) {
106
104
  if (!context.user) return resolveUnauthenticatedRedirect(context);
107
105
  const auth = assertRouteAuthOptions(context);
108
106
  const hasPermission = auth.hasPermission ? auth.hasPermission(context.user, permission, context) : defaultHasPermission(context.user, permission);
109
- if (isPromise(hasPermission)) return hasPermission.then((next) => next ? allow() : forbidden());
107
+ if (isPromiseLike(hasPermission)) return Promise.resolve(hasPermission).then((next) => next ? allow() : forbidden());
110
108
  return hasPermission ? allow() : forbidden();
111
109
  };
112
110
  }