@absolutejs/absolute 0.20.0-beta.182 → 0.20.0-beta.183

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 (38) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/changelog.json +21 -0
  3. package/dist/angular/components/core/streamingSlotRegistrar.js +1 -1
  4. package/dist/angular/components/core/streamingSlotRegistry.js +2 -2
  5. package/dist/angular/index.js +177 -37
  6. package/dist/angular/index.js.map +6 -5
  7. package/dist/angular/server.js +177 -37
  8. package/dist/angular/server.js.map +6 -5
  9. package/dist/build.js +22 -8
  10. package/dist/build.js.map +5 -5
  11. package/dist/cli/{build-p3qra1nj.js → build-df81wh71.js} +5 -2
  12. package/dist/cli/{buildReactServerComponents-vc9hh2rd.js → buildReactServerComponents-k6amghrj.js} +1 -1
  13. package/dist/cli/config/server.js +19 -1
  14. package/dist/cli/{index-mmm6d3k5.js → index-3dz754hk.js} +2 -2
  15. package/dist/cli/{index-7zwzyymv.js → index-pq4qqz0r.js} +1 -1
  16. package/dist/cli/index.js +1 -1
  17. package/dist/cli/{typecheck-yjme4fv1.js → typecheck-bvm0ndq4.js} +153 -62
  18. package/dist/dev/serverBootstrap.js +8 -2
  19. package/dist/index.js +24 -10
  20. package/dist/index.js.map +5 -5
  21. package/dist/react/index.js +10 -2
  22. package/dist/react/index.js.map +3 -3
  23. package/dist/react/server.js +10 -2
  24. package/dist/react/server.js.map +3 -3
  25. package/dist/src/angular/colocatedRouteMatch.d.ts +23 -0
  26. package/dist/src/cli/scripts/typecheck.d.ts +1 -0
  27. package/dist/src/cli/scripts/typecheckDiagnostics.d.ts +41 -0
  28. package/dist/src/dev/hot/analyze.d.ts +1 -1
  29. package/dist/src/utils/spaRouteManifest.d.ts +1 -0
  30. package/dist/svelte/index.js +10 -2
  31. package/dist/svelte/index.js.map +3 -3
  32. package/dist/svelte/server.js +10 -2
  33. package/dist/svelte/server.js.map +3 -3
  34. package/dist/vue/index.js +10 -2
  35. package/dist/vue/index.js.map +3 -3
  36. package/dist/vue/server.js +10 -2
  37. package/dist/vue/server.js.map +3 -3
  38. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -6,6 +6,20 @@ This file is generated by `absolute-changelog` from the entries in
6
6
  `changelog/`. Edit an entry, not this file — and add new ones under
7
7
  `changelog/unreleased/`.
8
8
 
9
+ ## 0.20.0-beta.183 — 2026-11-15
10
+
11
+ ### Changed
12
+
13
+ - **Angular pages with colocated routes return 404 for unknown sub-paths**
14
+ An Angular page that exports its own `routes` array and is mounted under a wildcard route (such as `.get('/ng/*', ...)`) never returned 404, even for a path none of its routes match. These pages now return 404 for an unknown sub-path, like the other SPA hosts. On each request the handler takes the path below the mount Elysia matched and checks it against the page's `routes` the way Angular's router does: path segments, `:params`, empty paths, `pathMatch: 'full'`, nested `children`, and `redirectTo` sources. A page navigation or HTMX request gets the not-found page (`not-found.html`) and any other client gets Elysia's JSON 404. That JSON rule now covers the React, Svelte, Vue and Angular SPA hosts' 404s too. A route the server can't resolve counts as a match: `loadChildren` matches anything under its path, and a `matcher` function matches everything. To keep the old behaviour for a page, add a `{ path: '**', component: ... }` route, which matches every path.
15
+
16
+ ### Fixed
17
+
18
+ - **Dev SSR recovers when you add a missing import, without restarting the dev server**
19
+ In `absolute dev`, saving a React component that renders another component before its import line exists (`<Card />` with no `import { Card }`), then adding the import, left server rendering failing with `Card is not defined` until the dev server restarted. The server updates edited modules in place and keeps every top-level statement whose text did not change; adding an import changes no statement, so it kept the component created by the previous save, which still looked `Card` up as a global. A statement now also counts as changed when a name it uses gets a different binding (a new or removed import, an import from another module, or an import swapped for a local declaration), so the component is re-created with the import. Separately, a second save of a React file that landed before the client rebuild for the first save ran could be skipped on the server, which kept rendering the first save until the next edit; every save now reaches the server.
20
+ - **An error in a .ts file inside svelteDirectory is reported once, not by both the plain checker and svelte-check**
21
+ svelte-check's program includes a configured svelteDirectory whole, so a plain TypeScript error in a .ts file there was printed under both [bun check] (or [tsc]) and [svelte-check]. `absolute typecheck` now reads svelte-check's machine-readable output and drops the errors the plain checker already reported, matching them by file, line, column and code; errors inside .svelte files stay under svelte-check, and svelte-check passes when every error it found was a duplicate. Its remaining errors are printed in the same shape as tsc's: file:line:column, the TS code, the message and the underlined line.
22
+
9
23
  ## 0.20.0-beta.182 — 2026-11-15
10
24
 
11
25
  ### Security
package/changelog.json CHANGED
@@ -2,6 +2,27 @@
2
2
  "contract": 1,
3
3
  "name": "@absolutejs/absolute",
4
4
  "releases": [
5
+ {
6
+ "changes": [
7
+ {
8
+ "detail": "An Angular page that exports its own `routes` array and is mounted under a wildcard route (such as `.get('/ng/*', ...)`) never returned 404, even for a path none of its routes match. These pages now return 404 for an unknown sub-path, like the other SPA hosts. On each request the handler takes the path below the mount Elysia matched and checks it against the page's `routes` the way Angular's router does: path segments, `:params`, empty paths, `pathMatch: 'full'`, nested `children`, and `redirectTo` sources. A page navigation or HTMX request gets the not-found page (`not-found.html`) and any other client gets Elysia's JSON 404. That JSON rule now covers the React, Svelte, Vue and Angular SPA hosts' 404s too. A route the server can't resolve counts as a match: `loadChildren` matches anything under its path, and a `matcher` function matches everything. To keep the old behaviour for a page, add a `{ path: '**', component: ... }` route, which matches every path.",
9
+ "kind": "changed",
10
+ "summary": "Angular pages with colocated routes return 404 for unknown sub-paths"
11
+ },
12
+ {
13
+ "detail": "In `absolute dev`, saving a React component that renders another component before its import line exists (`<Card />` with no `import { Card }`), then adding the import, left server rendering failing with `Card is not defined` until the dev server restarted. The server updates edited modules in place and keeps every top-level statement whose text did not change; adding an import changes no statement, so it kept the component created by the previous save, which still looked `Card` up as a global. A statement now also counts as changed when a name it uses gets a different binding (a new or removed import, an import from another module, or an import swapped for a local declaration), so the component is re-created with the import. Separately, a second save of a React file that landed before the client rebuild for the first save ran could be skipped on the server, which kept rendering the first save until the next edit; every save now reaches the server.",
14
+ "kind": "fixed",
15
+ "summary": "Dev SSR recovers when you add a missing import, without restarting the dev server"
16
+ },
17
+ {
18
+ "detail": "svelte-check's program includes a configured svelteDirectory whole, so a plain TypeScript error in a .ts file there was printed under both [bun check] (or [tsc]) and [svelte-check]. `absolute typecheck` now reads svelte-check's machine-readable output and drops the errors the plain checker already reported, matching them by file, line, column and code; errors inside .svelte files stay under svelte-check, and svelte-check passes when every error it found was a duplicate. Its remaining errors are printed in the same shape as tsc's: file:line:column, the TS code, the message and the underlined line.",
19
+ "kind": "fixed",
20
+ "summary": "An error in a .ts file inside svelteDirectory is reported once, not by both the plain checker and svelte-check"
21
+ }
22
+ ],
23
+ "date": "2026-11-15",
24
+ "version": "0.20.0-beta.183"
25
+ },
5
26
  {
6
27
  "changes": [
7
28
  {
@@ -1,5 +1,5 @@
1
1
  // @bun
2
- // .angular-partial-tmp-SKWVOm/src/core/streamingSlotRegistrar.ts
2
+ // .angular-partial-tmp-NwkjSK/src/core/streamingSlotRegistrar.ts
3
3
  var STREAMING_SLOT_REGISTRAR_KEY = Symbol.for("absolutejs.streamingSlotRegistrar");
4
4
  var STREAMING_SLOT_WARNING_STORAGE_KEY = Symbol.for("absolutejs.streamingSlotWarningController");
5
5
  var STREAMING_SLOT_COLLECTION_STORAGE_KEY = Symbol.for("absolutejs.streamingSlotCollectionController");
@@ -1,5 +1,5 @@
1
1
  // @bun
2
- // .angular-partial-tmp-SKWVOm/src/core/streamingSlotRegistrar.ts
2
+ // .angular-partial-tmp-NwkjSK/src/core/streamingSlotRegistrar.ts
3
3
  var STREAMING_SLOT_REGISTRAR_KEY = Symbol.for("absolutejs.streamingSlotRegistrar");
4
4
  var STREAMING_SLOT_WARNING_STORAGE_KEY = Symbol.for("absolutejs.streamingSlotWarningController");
5
5
  var STREAMING_SLOT_COLLECTION_STORAGE_KEY = Symbol.for("absolutejs.streamingSlotCollectionController");
@@ -46,7 +46,7 @@ var warnMissingStreamingSlotCollector = (primitiveName) => {
46
46
  getWarningController()?.maybeWarn(primitiveName);
47
47
  };
48
48
 
49
- // .angular-partial-tmp-SKWVOm/src/core/streamingSlotRegistry.ts
49
+ // .angular-partial-tmp-NwkjSK/src/core/streamingSlotRegistry.ts
50
50
  var STREAMING_SLOT_STORAGE_KEY = Symbol.for("absolutejs.streamingSlotAsyncLocalStorage");
51
51
  var isObjectRecord2 = (value) => Boolean(value) && typeof value === "object";
52
52
  var isAsyncLocalStorage = (value) => isObjectRecord2(value) && ("getStore" in value) && typeof value.getStore === "function" && ("run" in value) && typeof value.run === "function";
@@ -1437,6 +1437,7 @@ var init_angularDeps = __esm(() => {
1437
1437
  });
1438
1438
 
1439
1439
  // src/utils/spaRouteManifest.ts
1440
+ import { NotFound } from "elysia";
1440
1441
  import { basename as basename2 } from "path";
1441
1442
  var SPA_ROUTES_KEY = "__absoluteSpaRoutes", setSpaRouteManifest = (hosts) => {
1442
1443
  Reflect.set(globalThis, SPA_ROUTES_KEY, hosts);
@@ -1501,7 +1502,14 @@ var SPA_ROUTES_KEY = "__absoluteSpaRoutes", setSpaRouteManifest = (hosts) => {
1501
1502
  }, renderSpaNotFound = async (framework, pageName, request) => {
1502
1503
  if (isKnownSpaRoute(framework, pageName, request))
1503
1504
  return null;
1504
- return await renderFirstNotFound() ?? new Response("Not found", {
1505
+ return renderSpaNotFoundResponse(request);
1506
+ }, renderSpaNotFoundResponse = async (request) => {
1507
+ if (request && !isPageRequest(request)) {
1508
+ return new NotFound().toResponse();
1509
+ }
1510
+ return await renderFirstNotFound({
1511
+ preferHtmx: request?.headers.get("hx-request") === "true"
1512
+ }) ?? new Response("Not found", {
1505
1513
  headers: { "Content-Type": "text/plain" },
1506
1514
  status: 404
1507
1515
  });
@@ -1558,8 +1566,8 @@ var buildRequestProviders = (deps, request, requestContext, responseInit) => [
1558
1566
  // src/angular/zoneRuntime.ts
1559
1567
  var ZONE_NODE_SPECIFIER = "zone.js/node", MISSING_ZONE_MESSAGE = 'angular.changeDetection is "zone" but zone.js could not be loaded. Install it in your app (bun add zone.js).', isAngularZoneMode = () => globalThis.__absoluteAngularChangeDetection === "zone", setAngularChangeDetection = (mode) => {
1560
1568
  globalThis.__absoluteAngularChangeDetection = mode ?? "zoneless";
1561
- }, isRecord2 = (value) => typeof value === "object" && value !== null, adoptIntoNativePromise = (NativePromise, value) => {
1562
- if (value instanceof NativePromise || !isRecord2(value))
1569
+ }, isRecord3 = (value) => typeof value === "object" && value !== null, adoptIntoNativePromise = (NativePromise, value) => {
1570
+ if (value instanceof NativePromise || !isRecord3(value))
1563
1571
  return value;
1564
1572
  const { then } = value;
1565
1573
  if (typeof then !== "function")
@@ -1576,28 +1584,28 @@ var ZONE_NODE_SPECIFIER = "zone.js/node", MISSING_ZONE_MESSAGE = 'angular.change
1576
1584
  }, wrapRoute = (NativePromise, route) => {
1577
1585
  if (typeof route === "function")
1578
1586
  return wrapHandler(NativePromise, route);
1579
- if (!isRecord2(route) || route instanceof Response || route instanceof Blob)
1587
+ if (!isRecord3(route) || route instanceof Response || route instanceof Blob)
1580
1588
  return route;
1581
1589
  return Object.fromEntries(Object.entries(route).map(([method, handler]) => [
1582
1590
  method,
1583
1591
  wrapHandler(NativePromise, handler)
1584
1592
  ]));
1585
1593
  }, wrapServeOptions = (NativePromise, options) => {
1586
- if (!isRecord2(options))
1594
+ if (!isRecord3(options))
1587
1595
  return options;
1588
1596
  const wrapped = { ...options };
1589
1597
  if (options.fetch)
1590
1598
  wrapped.fetch = wrapHandler(NativePromise, options.fetch);
1591
1599
  if (options.error)
1592
1600
  wrapped.error = wrapHandler(NativePromise, options.error);
1593
- if (isRecord2(options.routes))
1601
+ if (isRecord3(options.routes))
1594
1602
  wrapped.routes = Object.fromEntries(Object.entries(options.routes).map(([path, route]) => [
1595
1603
  path,
1596
1604
  wrapRoute(NativePromise, route)
1597
1605
  ]));
1598
1606
  return wrapped;
1599
1607
  }, wrapServerReload = (NativePromise, server) => {
1600
- if (!isRecord2(server))
1608
+ if (!isRecord3(server))
1601
1609
  return;
1602
1610
  const { reload } = server;
1603
1611
  if (typeof reload !== "function")
@@ -3475,15 +3483,15 @@ var init_stylePreprocessor = __esm(() => {
3475
3483
  });
3476
3484
 
3477
3485
  // src/svelte/router/lazyModule.ts
3478
- var LAZY_RESOLVED_KEY, LAZY_SOURCE_EXPORT = "__ABSOLUTE_SVELTE_SOURCE__", isRecord3 = (value) => typeof value === "object" && value !== null, isComponent = (value) => typeof value === "function", toLazyComponent = (value) => {
3486
+ var LAZY_RESOLVED_KEY, LAZY_SOURCE_EXPORT = "__ABSOLUTE_SVELTE_SOURCE__", isRecord4 = (value) => typeof value === "object" && value !== null, isComponent = (value) => typeof value === "function", toLazyComponent = (value) => {
3479
3487
  if (isComponent(value))
3480
3488
  return value;
3481
- if (!isRecord3(value))
3489
+ if (!isRecord4(value))
3482
3490
  return;
3483
3491
  const fallback = value.default;
3484
3492
  return isComponent(fallback) ? fallback : undefined;
3485
3493
  }, readSourceFrom = (value) => {
3486
- if (!isRecord3(value) && !isComponent(value))
3494
+ if (!isRecord4(value) && !isComponent(value))
3487
3495
  return;
3488
3496
  const source = Reflect.get(value, LAZY_SOURCE_EXPORT);
3489
3497
  return typeof source === "string" ? source : undefined;
@@ -3492,7 +3500,7 @@ var LAZY_RESOLVED_KEY, LAZY_SOURCE_EXPORT = "__ABSOLUTE_SVELTE_SOURCE__", isReco
3492
3500
  const fromValue = readSourceFrom(value);
3493
3501
  if (fromValue !== undefined)
3494
3502
  return fromValue;
3495
- if (!isRecord3(value))
3503
+ if (!isRecord4(value))
3496
3504
  return;
3497
3505
  return readSourceFrom(value.default);
3498
3506
  };
@@ -3876,14 +3884,14 @@ var defineIslandComponent = (component, options) => ({
3876
3884
  component,
3877
3885
  export: options.export,
3878
3886
  source: options.source
3879
- }), defineIslandRegistry = (registry) => registry, isRecord4 = (value) => typeof value === "object" && value !== null, getIslandBuildReference = (component) => {
3887
+ }), defineIslandRegistry = (registry) => registry, isRecord5 = (value) => typeof value === "object" && value !== null, getIslandBuildReference = (component) => {
3880
3888
  if (!isIslandComponentDefinition(component))
3881
3889
  return null;
3882
3890
  return {
3883
3891
  export: component.export,
3884
3892
  source: component.source
3885
3893
  };
3886
- }, isIslandComponentDefinition = (value) => isRecord4(value) && ("component" in value) && ("source" in value) && typeof value.source === "string", parseIslandProps = (rawProps) => {
3894
+ }, isIslandComponentDefinition = (value) => isRecord5(value) && ("component" in value) && ("source" in value) && typeof value.source === "string", parseIslandProps = (rawProps) => {
3887
3895
  if (!rawProps)
3888
3896
  return {};
3889
3897
  return JSON.parse(rawProps);
@@ -3907,7 +3915,7 @@ var init_islandMarkupAttributes = __esm(() => {
3907
3915
  var islandSequence = 0, resolvedServerComponentCache, resolvedServerBuildComponentCache, syncIslandGeneration3, nextIslandId = () => {
3908
3916
  islandSequence += 1;
3909
3917
  return `island-${islandSequence}`;
3910
- }, isRecord5 = (value) => typeof value === "object" && value !== null, isReactServerIslandComponent = (value) => typeof value === "function", isSvelteServerIslandComponent = (value) => typeof value === "function", isVueServerIslandComponent = (value) => typeof value === "function" || isRecord5(value), isAngularServerIslandComponent = (value) => typeof value === "function", resolveBuildReferencePath = (source, registryPath) => {
3918
+ }, isRecord6 = (value) => typeof value === "object" && value !== null, isReactServerIslandComponent = (value) => typeof value === "function", isSvelteServerIslandComponent = (value) => typeof value === "function", isVueServerIslandComponent = (value) => typeof value === "function" || isRecord6(value), isAngularServerIslandComponent = (value) => typeof value === "function", resolveBuildReferencePath = (source, registryPath) => {
3911
3919
  if (source.startsWith("file://"))
3912
3920
  return new URL(source).pathname;
3913
3921
  if (source.startsWith("."))
@@ -4376,13 +4384,13 @@ var ABSOLUTE_MOBILE_PAGE_MEDIA_TYPE = "application/vnd.absolute.page+json", ABSO
4376
4384
  }, headerVaryValues, envelopeResponse = (response, status) => new Response(JSON.stringify({
4377
4385
  protocol: ABSOLUTE_MOBILE_PAGE_PROTOCOL_VERSION,
4378
4386
  response
4379
- }), { headers: responseHeaders(), status }), isRecord7 = (value) => typeof value === "object" && value !== null && !Array.isArray(value), normalizeJsonValue = (value) => {
4387
+ }), { headers: responseHeaders(), status }), isRecord8 = (value) => typeof value === "object" && value !== null && !Array.isArray(value), normalizeJsonValue = (value) => {
4380
4388
  const serialized = JSON.stringify(value);
4381
4389
  if (serialized === undefined) {
4382
4390
  throw new TypeError("Mobile page props must be JSON-serializable.");
4383
4391
  }
4384
4392
  const parsed = JSON.parse(serialized);
4385
- if (!isRecord7(parsed)) {
4393
+ if (!isRecord8(parsed)) {
4386
4394
  throw new TypeError("Mobile page props must serialize to an object.");
4387
4395
  }
4388
4396
  return parsed;
@@ -4784,7 +4792,7 @@ var SLOT_ID_PREFIX = "abs-slot-", CLOSING_BODY_TAG = "</body>", CLOSING_HEAD_TAG
4784
4792
  getStreamingSlotsRuntimeScript()
4785
4793
  ].filter(Boolean).join(";");
4786
4794
  return `<script${createNonceAttr(nonce)}>${escapeScriptContent(runtimeBody)}</script>`;
4787
- }, toUint8 = (value, encoder) => encoder.encode(value), isRecord8 = (value) => Boolean(value) && typeof value === "object", isSafeHtmlLike = (value) => isRecord8(value) && typeof value.changingThisBreaksApplicationSecurity === "string", normalizeSafeHtml = (value) => {
4795
+ }, toUint8 = (value, encoder) => encoder.encode(value), isRecord9 = (value) => Boolean(value) && typeof value === "object", isSafeHtmlLike = (value) => isRecord9(value) && typeof value.changingThisBreaksApplicationSecurity === "string", normalizeSafeHtml = (value) => {
4788
4796
  if (isSafeHtmlLike(value)) {
4789
4797
  return value.changingThisBreaksApplicationSecurity;
4790
4798
  }
@@ -4845,7 +4853,7 @@ var SLOT_ID_PREFIX = "abs-slot-", CLOSING_BODY_TAG = "</body>", CLOSING_HEAD_TAG
4845
4853
  }, createTimeoutError = (slot, timeoutMs) => {
4846
4854
  const error = Object.assign(new Error(`Streaming slot "${slot.id}" timed out after ${timeoutMs}ms`), { __absTimeout: true });
4847
4855
  return error;
4848
- }, isSlotPatchPayloadObject = (value) => isRecord8(value) && ("html" in value), isTimeoutError = (error) => isRecord8(error) && error.__absTimeout === true, toStreamingSlot = (slot, policy) => ({
4856
+ }, isSlotPatchPayloadObject = (value) => isRecord9(value) && ("html" in value), isTimeoutError = (error) => isRecord9(error) && error.__absTimeout === true, toStreamingSlot = (slot, policy) => ({
4849
4857
  dynamic: slot.dynamic,
4850
4858
  errorHtml: slot.errorHtml === undefined ? policy.errorHtml : slot.errorHtml,
4851
4859
  fallbackHtml: normalizeSlotText(slot.fallbackHtml, policy.fallbackHtml),
@@ -16510,6 +16518,129 @@ var resolveRequestBaseHref = (route, request) => deriveAngularBaseHref(route, re
16510
16518
  // src/angular/pageHandler.ts
16511
16519
  init_spaRouteManifest();
16512
16520
 
16521
+ // src/angular/colocatedRouteMatch.ts
16522
+ var MAX_ROUTE_DEPTH = 32;
16523
+ var isRecord2 = (value) => typeof value === "object" && value !== null;
16524
+ var PRIMARY_OUTLET = "primary";
16525
+ var routeOutlet = (route) => typeof route.outlet === "string" ? route.outlet : PRIMARY_OUTLET;
16526
+ var matchRoute = (route, segments) => {
16527
+ if (!isRecord2(route))
16528
+ return true;
16529
+ if (routeOutlet(route) !== PRIMARY_OUTLET)
16530
+ return false;
16531
+ if (typeof route.matcher === "function")
16532
+ return true;
16533
+ if (typeof route.path !== "string")
16534
+ return true;
16535
+ const { path } = route;
16536
+ const parts = path.split("/").filter((part) => part !== "");
16537
+ if (parts.includes("**"))
16538
+ return true;
16539
+ const isFull = route.pathMatch === "full";
16540
+ if (parts.length > segments.length)
16541
+ return false;
16542
+ if (isFull && parts.length < segments.length)
16543
+ return false;
16544
+ const consumed = parts.every((part, index) => part.startsWith(":") || part === segments[index]);
16545
+ if (!consumed)
16546
+ return false;
16547
+ const rest = segments.slice(parts.length);
16548
+ if (route.redirectTo !== undefined)
16549
+ return true;
16550
+ if (route.loadChildren !== undefined)
16551
+ return true;
16552
+ if (route.children !== undefined)
16553
+ return { children: route.children, rest };
16554
+ return rest.length === 0;
16555
+ };
16556
+ var matchRoutes = (routes, segments, depth) => {
16557
+ if (!Array.isArray(routes) || depth > MAX_ROUTE_DEPTH)
16558
+ return true;
16559
+ if (segments.length === 0)
16560
+ return true;
16561
+ return routes.some((route) => {
16562
+ const result = matchRoute(route, segments);
16563
+ if (typeof result === "boolean")
16564
+ return result;
16565
+ return matchRoutes(result.children, result.rest, depth + 1);
16566
+ });
16567
+ };
16568
+ var decodeSegment = (segment) => {
16569
+ try {
16570
+ return decodeURIComponent(segment);
16571
+ } catch {
16572
+ return null;
16573
+ }
16574
+ };
16575
+ var toUrlSegments = (path) => {
16576
+ if (path.includes("(") || path.includes(")"))
16577
+ return null;
16578
+ const decoded = path.split("/").map((raw) => raw.split(";")[0] ?? "").filter((segment) => segment !== "").map(decodeSegment);
16579
+ if (decoded.some((segment) => segment === null))
16580
+ return null;
16581
+ return decoded.filter((segment) => segment !== null);
16582
+ };
16583
+ var matchesAngularColocatedRoutes = (routes, path) => {
16584
+ const segments = toUrlSegments(path);
16585
+ if (segments === null)
16586
+ return true;
16587
+ return matchRoutes(routes, segments, 0);
16588
+ };
16589
+ var pathBelowBaseHref = (pathname, baseHref) => {
16590
+ const collapsed = pathname.replace(/\/{2,}/g, "/");
16591
+ const base = baseHref.endsWith("/") ? baseHref : `${baseHref}/`;
16592
+ if (`${collapsed}/` === base)
16593
+ return "";
16594
+ if (!collapsed.startsWith(base))
16595
+ return null;
16596
+ return collapsed.slice(base.length);
16597
+ };
16598
+ var WILDCARD_SUFFIX2 = "/*";
16599
+ var ROOT_WILDCARD = "/*";
16600
+ var MAX_PROVIDER_DEPTH = 8;
16601
+ var providesBaseHref = (provider, depth = 0) => {
16602
+ if (depth > MAX_PROVIDER_DEPTH)
16603
+ return true;
16604
+ if (Array.isArray(provider))
16605
+ return provider.some((entry) => providesBaseHref(entry, depth + 1));
16606
+ if (!isRecord2(provider))
16607
+ return false;
16608
+ const nested = Reflect.get(provider, "\u0275providers");
16609
+ if (Array.isArray(nested))
16610
+ return providesBaseHref(nested, depth + 1);
16611
+ if (!("provide" in provider))
16612
+ return false;
16613
+ return String(provider.provide).includes("appBaseHref");
16614
+ };
16615
+ var isUnmatchedColocatedRoute = ({
16616
+ handlerProviders,
16617
+ pageModule,
16618
+ request,
16619
+ route,
16620
+ runtimeBaseHref
16621
+ }) => {
16622
+ if (!request || !route?.endsWith(WILDCARD_SUFFIX2))
16623
+ return false;
16624
+ const { providers, routes } = pageModule;
16625
+ if (!Array.isArray(routes) || !Array.isArray(providers))
16626
+ return false;
16627
+ if (providesBaseHref(handlerProviders))
16628
+ return false;
16629
+ const baseHref = runtimeBaseHref ?? (route === ROOT_WILDCARD ? "/" : null);
16630
+ if (baseHref === null)
16631
+ return false;
16632
+ let pathname;
16633
+ try {
16634
+ ({ pathname } = new URL(request.url));
16635
+ } catch {
16636
+ return false;
16637
+ }
16638
+ const path = pathBelowBaseHref(pathname, baseHref);
16639
+ if (path === null)
16640
+ return false;
16641
+ return !matchesAngularColocatedRoutes(routes, path);
16642
+ };
16643
+
16513
16644
  // src/angular/lowerServerIslands.ts
16514
16645
  init_renderIslandMarkup();
16515
16646
  var ANGULAR_ISLAND_TAG_RE = /<absolute-island\b([^>]*)>[\s\S]*?<\/absolute-island>/gi;
@@ -16527,7 +16658,7 @@ var islandHydrationModes = [
16527
16658
  "none"
16528
16659
  ];
16529
16660
  var decodeHtmlAttribute = (value) => value.replaceAll("&quot;", '"').replaceAll("&#34;", '"').replaceAll("&apos;", "'").replaceAll("&#39;", "'").replaceAll("&amp;", "&").replaceAll("&lt;", "<").replaceAll("&gt;", ">");
16530
- var isRecord6 = (value) => typeof value === "object" && value !== null;
16661
+ var isRecord7 = (value) => typeof value === "object" && value !== null;
16531
16662
  var isIslandFramework = (value) => islandFrameworks.some((framework) => framework === value);
16532
16663
  var isIslandHydrate = (value) => islandHydrationModes.some((mode) => mode === value);
16533
16664
  var parseAttributes = (attributeString) => {
@@ -16558,7 +16689,7 @@ var parseAngularIslandProps = (attributeString) => {
16558
16689
  let parsedProps = {};
16559
16690
  try {
16560
16691
  const candidate = JSON.parse(serializedProps);
16561
- parsedProps = isRecord6(candidate) ? candidate : {};
16692
+ parsedProps = isRecord7(candidate) ? candidate : {};
16562
16693
  } catch {}
16563
16694
  return {
16564
16695
  component,
@@ -16927,11 +17058,11 @@ var runWithStreamingSlotWarningScope = (task, metadata) => ensureWarningStorage(
16927
17058
 
16928
17059
  // src/angular/pageHandler.ts
16929
17060
  init_ssrRender();
16930
- var isRecord9 = (value) => typeof value === "object" && value !== null;
17061
+ var isRecord10 = (value) => typeof value === "object" && value !== null;
16931
17062
  var isAngularComponent = (value) => typeof value === "function";
16932
17063
  var componentHostsRouterOutlet = (component) => {
16933
17064
  const componentDef = Reflect.get(component, "\u0275cmp");
16934
- if (!isRecord9(componentDef))
17065
+ if (!isRecord10(componentDef))
16935
17066
  return false;
16936
17067
  const rawDependencies = componentDef.dependencies;
16937
17068
  const dependencies = typeof rawDependencies === "function" ? Reflect.apply(rawDependencies, undefined, []) : rawDependencies;
@@ -16941,7 +17072,7 @@ var componentHostsRouterOutlet = (component) => {
16941
17072
  if (dependency === null || typeof dependency !== "object" && typeof dependency !== "function")
16942
17073
  return false;
16943
17074
  const directiveDef = Reflect.get(dependency, "\u0275dir") ?? dependency;
16944
- if (!isRecord9(directiveDef))
17075
+ if (!isRecord10(directiveDef))
16945
17076
  return false;
16946
17077
  const { selectors } = directiveDef;
16947
17078
  return Array.isArray(selectors) && JSON.stringify(selectors).includes("router-outlet");
@@ -16971,7 +17102,7 @@ var ensureAngularCompiler = () => {
16971
17102
  }
16972
17103
  return compilerImportPromise;
16973
17104
  };
16974
- var readAngularPageModule = (value) => isRecord9(value) ? value : null;
17105
+ var readAngularPageModule = (value) => isRecord10(value) ? value : null;
16975
17106
  var resolveAngularSsrOutDir = (projectRoot = process.cwd(), configuredOutDir = process.env.ABSOLUTE_ANGULAR_SSR_OUTDIR) => configuredOutDir ?? join10(projectRoot, ".absolutejs", "runtime", "angular-ssr");
16976
17107
  var createAngularRuntimeCacheBuster = () => `${Date.now().toString(BASE_36_RADIX)}.${Math.random().toString(BASE_36_RADIX).substring(2, RANDOM_ID_END_INDEX)}`;
16977
17108
  var existingNodeModulesLinkMatches = async (nodeModulesLink, expectedTarget) => {
@@ -17089,7 +17220,7 @@ var handleAngularPageRequest = async (input) => {
17089
17220
  ],
17090
17221
  runtimes: [String(ABSOLUTE_MOBILE_PAGE_PROTOCOL_VERSION)]
17091
17222
  },
17092
- props: isRecord9(input.requestContext) ? input.requestContext : Object.create(null),
17223
+ props: isRecord10(input.requestContext) ? input.requestContext : Object.create(null),
17093
17224
  request,
17094
17225
  status: input.responseInit?.status,
17095
17226
  route: () => {
@@ -17126,18 +17257,27 @@ var handleAngularPageRequest = async (input) => {
17126
17257
  });
17127
17258
  try {
17128
17259
  const handlerCallsite = options?.collectStreamingSlots === true ? undefined : getCurrentRouteRegistrationCallsite() ?? captureStreamingSlotWarningCallsite();
17260
+ const baseDeps = await getAngularDeps();
17261
+ const runtimePagePath = await resolveRuntimeAngularModulePath(resolvedPagePath);
17262
+ const importedPageModule = await import(buildRuntimeModuleSpecifier(runtimePagePath.path, runtimePagePath.cacheBuster));
17263
+ const pageModule = readAngularPageModule(importedPageModule);
17264
+ if (!pageModule) {
17265
+ throw new Error(`Invalid Angular page module: ${resolvedPagePath}`);
17266
+ }
17267
+ const PageComponent = resolvePageComponent(pageModule);
17268
+ if (!isAngularComponent(PageComponent)) {
17269
+ throw new Error(`Angular page module must export an Angular component: ${resolvedPagePath}`);
17270
+ }
17271
+ if (isUnmatchedColocatedRoute({
17272
+ handlerProviders: input.providers,
17273
+ pageModule,
17274
+ request,
17275
+ route: getCurrentAbsoluteRoute(request),
17276
+ runtimeBaseHref
17277
+ })) {
17278
+ return withPageCacheHeaders(await renderSpaNotFoundResponse(request), request);
17279
+ }
17129
17280
  const renderPageResponse = async () => {
17130
- const baseDeps = await getAngularDeps();
17131
- const runtimePagePath = await resolveRuntimeAngularModulePath(resolvedPagePath);
17132
- const importedPageModule = await import(buildRuntimeModuleSpecifier(runtimePagePath.path, runtimePagePath.cacheBuster));
17133
- const pageModule = readAngularPageModule(importedPageModule);
17134
- if (!pageModule) {
17135
- throw new Error(`Invalid Angular page module: ${resolvedPagePath}`);
17136
- }
17137
- const PageComponent = resolvePageComponent(pageModule);
17138
- if (!isAngularComponent(PageComponent)) {
17139
- throw new Error(`Angular page module must export an Angular component: ${resolvedPagePath}`);
17140
- }
17141
17281
  const hasIslands = typeof pageModule.__ABSOLUTE_PAGE_HAS_ISLANDS__ === "boolean" ? pageModule.__ABSOLUTE_PAGE_HAS_ISLANDS__ : false;
17142
17282
  const usesLegacyAnimations = pageModule.__ABSOLUTE_PAGE_USES_LEGACY_ANIMATIONS__ === true;
17143
17283
  const deps = baseDeps;
@@ -17709,5 +17849,5 @@ export {
17709
17849
  withPendingTask
17710
17850
  };
17711
17851
 
17712
- //# debugId=158D360FF14BEE8864756E2164756E21
17852
+ //# debugId=D3C5CBDE25ADE9C364756E2164756E21
17713
17853
  //# sourceMappingURL=index.js.map