@rangojs/router 0.0.0-experimental.32 → 0.0.0-experimental.3232cd17
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.
- package/AGENTS.md +4 -0
- package/README.md +198 -44
- package/dist/bin/rango.js +287 -105
- package/dist/testing/vitest.js +82 -0
- package/dist/vite/index.js +3248 -1117
- package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/package.json +73 -21
- package/skills/api-client/SKILL.md +211 -0
- package/skills/breadcrumbs/SKILL.md +107 -1
- package/skills/bundle-analysis/SKILL.md +159 -0
- package/skills/cache-guide/SKILL.md +245 -21
- package/skills/caching/SKILL.md +302 -6
- package/skills/composability/SKILL.md +27 -2
- package/skills/css/SKILL.md +76 -0
- package/skills/document-cache/SKILL.md +78 -55
- package/skills/handler-use/SKILL.md +364 -0
- package/skills/hooks/SKILL.md +270 -30
- package/skills/host-router/SKILL.md +82 -22
- package/skills/i18n/SKILL.md +276 -0
- package/skills/intercept/SKILL.md +49 -5
- package/skills/layout/SKILL.md +35 -9
- package/skills/links/SKILL.md +249 -17
- package/skills/loader/SKILL.md +294 -30
- package/skills/middleware/SKILL.md +52 -13
- package/skills/migrate-nextjs/SKILL.md +584 -0
- package/skills/migrate-react-router/SKILL.md +769 -0
- package/skills/mime-routes/SKILL.md +27 -0
- package/skills/observability/SKILL.md +137 -0
- package/skills/parallel/SKILL.md +203 -7
- package/skills/prerender/SKILL.md +123 -100
- package/skills/rango/SKILL.md +250 -22
- package/skills/react-compiler/SKILL.md +168 -0
- package/skills/response-routes/SKILL.md +122 -47
- package/skills/route/SKILL.md +97 -5
- package/skills/router-setup/SKILL.md +90 -5
- package/skills/server-actions/SKILL.md +775 -0
- package/skills/streams-and-websockets/SKILL.md +283 -0
- package/skills/tailwind/SKILL.md +27 -3
- package/skills/testing/SKILL.md +129 -0
- package/skills/testing/bindings.md +89 -0
- package/skills/testing/cache-prerender.md +124 -0
- package/skills/testing/client-components.md +122 -0
- package/skills/testing/e2e-parity.md +125 -0
- package/skills/testing/flight.md +92 -0
- package/skills/testing/handles.md +129 -0
- package/skills/testing/loader.md +128 -0
- package/skills/testing/middleware.md +99 -0
- package/skills/testing/render-handler.md +121 -0
- package/skills/testing/response-routes.md +95 -0
- package/skills/testing/reverse-and-types.md +84 -0
- package/skills/testing/server-actions.md +107 -0
- package/skills/testing/server-tree.md +128 -0
- package/skills/testing/setup.md +120 -0
- package/skills/typesafety/SKILL.md +329 -27
- package/skills/use-cache/SKILL.md +36 -5
- package/skills/view-transitions/SKILL.md +294 -0
- package/src/__augment-tests__/augment.ts +81 -0
- package/src/__augment-tests__/augmented.check.ts +116 -0
- package/src/__internal.ts +67 -40
- package/src/browser/action-coordinator.ts +53 -36
- package/src/browser/action-fence.ts +47 -0
- package/src/browser/app-shell.ts +39 -0
- package/src/browser/app-version.ts +14 -0
- package/src/browser/cookie-name.ts +140 -0
- package/src/browser/event-controller.ts +86 -147
- package/src/browser/history-state.ts +21 -0
- package/src/browser/index.ts +3 -3
- package/src/browser/invalidate-client-cache.ts +52 -0
- package/src/browser/link-interceptor.ts +4 -0
- package/src/browser/navigation-bridge.ts +148 -19
- package/src/browser/navigation-client.ts +187 -67
- package/src/browser/navigation-store-handle.ts +38 -0
- package/src/browser/navigation-store.ts +76 -67
- package/src/browser/navigation-transaction.ts +18 -66
- package/src/browser/partial-update.ts +123 -94
- package/src/browser/prefetch/cache.ts +214 -36
- package/src/browser/prefetch/fetch.ts +260 -38
- package/src/browser/prefetch/policy.ts +6 -0
- package/src/browser/prefetch/queue.ts +126 -20
- package/src/browser/prefetch/resource-ready.ts +77 -0
- package/src/browser/rango-state.ts +158 -76
- package/src/browser/react/Link.tsx +93 -11
- package/src/browser/react/NavigationProvider.tsx +115 -34
- package/src/browser/react/ScrollRestoration.tsx +10 -6
- package/src/browser/react/context.ts +7 -2
- package/src/browser/react/filter-segment-order.ts +49 -7
- package/src/browser/react/index.ts +0 -48
- package/src/browser/react/location-state-shared.ts +166 -8
- package/src/browser/react/location-state.ts +39 -14
- package/src/browser/react/use-action.ts +6 -15
- package/src/browser/react/use-handle.ts +23 -69
- package/src/browser/react/use-link-status.ts +0 -4
- package/src/browser/react/use-navigation.ts +22 -5
- package/src/browser/react/use-params.ts +20 -10
- package/src/browser/react/use-reverse.ts +106 -0
- package/src/browser/react/use-router.ts +46 -11
- package/src/browser/react/use-search-params.ts +0 -5
- package/src/browser/react/use-segments.ts +11 -21
- package/src/browser/response-adapter.ts +52 -1
- package/src/browser/rsc-router.tsx +215 -76
- package/src/browser/scroll-restoration.ts +46 -39
- package/src/browser/segment-reconciler.ts +36 -9
- package/src/browser/segment-structure-assert.ts +2 -2
- package/src/browser/server-action-bridge.ts +176 -50
- package/src/browser/types.ts +95 -11
- package/src/browser/validate-redirect-origin.ts +43 -16
- package/src/build/collect-fallback-refs.ts +107 -0
- package/src/build/generate-manifest.ts +65 -40
- package/src/build/generate-route-types.ts +5 -0
- package/src/build/index.ts +8 -2
- package/src/build/prefix-tree-utils.ts +123 -0
- package/src/build/route-trie.ts +137 -32
- package/src/build/route-types/codegen.ts +4 -4
- package/src/build/route-types/include-resolution.ts +9 -2
- package/src/build/route-types/param-extraction.ts +6 -3
- package/src/build/route-types/per-module-writer.ts +7 -4
- package/src/build/route-types/router-processing.ts +278 -96
- package/src/build/route-types/scan-filter.ts +9 -2
- package/src/build/route-types/source-scan.ts +118 -0
- package/src/build/runtime-discovery.ts +9 -20
- package/src/cache/cache-error.ts +104 -0
- package/src/cache/cache-policy.ts +68 -28
- package/src/cache/cache-runtime.ts +149 -43
- package/src/cache/cache-scope.ts +148 -81
- package/src/cache/cache-tag.ts +98 -0
- package/src/cache/cf/cf-cache-store.ts +2550 -93
- package/src/cache/cf/index.ts +11 -17
- package/src/cache/document-cache.ts +78 -27
- package/src/cache/handle-snapshot.ts +63 -0
- package/src/cache/index.ts +23 -20
- package/src/cache/memory-segment-store.ts +136 -37
- package/src/cache/profile-registry.ts +6 -30
- package/src/cache/read-through-swr.ts +41 -11
- package/src/cache/segment-codec.ts +0 -16
- package/src/cache/tag-invalidation.ts +230 -0
- package/src/cache/taint.ts +55 -0
- package/src/cache/types.ts +33 -100
- package/src/cache/vercel/index.ts +11 -0
- package/src/cache/vercel/vercel-cache-store.ts +799 -0
- package/src/client.rsc.tsx +6 -21
- package/src/client.tsx +108 -290
- package/src/component-utils.ts +19 -0
- package/src/context-var.ts +84 -2
- package/src/debug.ts +2 -2
- package/src/decode-loader-results.ts +36 -0
- package/src/defer.ts +196 -0
- package/src/deps/ssr.ts +0 -1
- package/src/errors.ts +30 -4
- package/src/handle.ts +70 -22
- package/src/handles/MetaTags.tsx +0 -14
- package/src/handles/breadcrumbs.ts +16 -5
- package/src/handles/meta.ts +0 -39
- package/src/host/cookie-handler.ts +0 -36
- package/src/host/errors.ts +0 -24
- package/src/host/index.ts +8 -2
- package/src/host/pattern-matcher.ts +7 -50
- package/src/host/router.ts +107 -99
- package/src/host/testing.ts +40 -27
- package/src/host/types.ts +37 -4
- package/src/host/utils.ts +1 -1
- package/src/href-client.ts +137 -22
- package/src/index.rsc.ts +52 -26
- package/src/index.ts +100 -38
- package/src/internal-debug.ts +2 -4
- package/src/loader-store.ts +500 -0
- package/src/loader.rsc.ts +20 -13
- package/src/loader.ts +12 -11
- package/src/missing-id-error.ts +68 -0
- package/src/network-error-thrower.tsx +1 -6
- package/src/outlet-context.ts +1 -1
- package/src/outlet-provider.tsx +1 -5
- package/src/prerender/param-hash.ts +10 -11
- package/src/prerender/store.ts +37 -41
- package/src/prerender.ts +198 -82
- package/src/redirect-origin.ts +100 -0
- package/src/response-utils.ts +37 -0
- package/src/reverse.ts +65 -15
- package/src/root-error-boundary.tsx +1 -19
- package/src/route-content-wrapper.tsx +7 -72
- package/src/route-definition/dsl-helpers.ts +437 -274
- package/src/route-definition/helper-factories.ts +29 -139
- package/src/route-definition/helpers-types.ts +113 -37
- package/src/route-definition/index.ts +3 -0
- package/src/route-definition/redirect.ts +52 -10
- package/src/route-definition/resolve-handler-use.ts +161 -0
- package/src/route-definition/use-item-types.ts +32 -0
- package/src/route-map-builder.ts +7 -17
- package/src/route-types.ts +37 -41
- package/src/router/basename.ts +14 -0
- package/src/router/content-negotiation.ts +108 -9
- package/src/router/error-handling.ts +13 -17
- package/src/router/find-match.ts +45 -22
- package/src/router/handler-context.ts +83 -41
- package/src/router/intercept-resolution.ts +25 -23
- package/src/router/lazy-includes.ts +19 -53
- package/src/router/loader-resolution.ts +213 -30
- package/src/router/logging.ts +5 -8
- package/src/router/manifest.ts +49 -45
- package/src/router/match-api.ts +120 -204
- package/src/router/match-context.ts +0 -22
- package/src/router/match-handlers.ts +58 -58
- package/src/router/match-middleware/background-revalidation.ts +27 -6
- package/src/router/match-middleware/cache-lookup.ts +205 -249
- package/src/router/match-middleware/cache-store.ts +45 -32
- package/src/router/match-middleware/intercept-resolution.ts +8 -28
- package/src/router/match-middleware/segment-resolution.ts +52 -18
- package/src/router/match-pipelines.ts +1 -42
- package/src/router/match-result.ts +104 -40
- package/src/router/metrics.ts +5 -34
- package/src/router/middleware-types.ts +13 -142
- package/src/router/middleware.ts +173 -143
- package/src/router/navigation-snapshot.ts +131 -0
- package/src/router/params-util.ts +23 -0
- package/src/router/pattern-matching.ts +109 -63
- package/src/router/prerender-match.ts +190 -54
- package/src/router/preview-match.ts +32 -102
- package/src/router/request-classification.ts +276 -0
- package/src/router/revalidation.ts +63 -55
- package/src/router/route-snapshot.ts +244 -0
- package/src/router/router-context.ts +6 -28
- package/src/router/router-interfaces.ts +100 -35
- package/src/router/router-options.ts +91 -11
- package/src/router/router-registry.ts +2 -5
- package/src/router/segment-resolution/fresh.ts +242 -75
- package/src/router/segment-resolution/helpers.ts +63 -24
- package/src/router/segment-resolution/loader-cache.ts +41 -37
- package/src/router/segment-resolution/revalidation.ts +456 -372
- package/src/router/segment-resolution/static-store.ts +19 -5
- package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
- package/src/router/segment-resolution/view-transition-default.ts +36 -0
- package/src/router/segment-resolution.ts +4 -1
- package/src/router/segment-wrappers.ts +2 -3
- package/src/router/state-cookie-name.ts +33 -0
- package/src/router/substitute-pattern-params.ts +56 -0
- package/src/router/telemetry-otel.ts +0 -20
- package/src/router/telemetry.ts +96 -19
- package/src/router/timeout.ts +0 -20
- package/src/router/trie-matching.ts +91 -46
- package/src/router/types.ts +10 -63
- package/src/router/url-params.ts +44 -0
- package/src/router.ts +134 -43
- package/src/rsc/handler-context.ts +3 -2
- package/src/rsc/handler.ts +492 -383
- package/src/rsc/helpers.ts +162 -46
- package/src/rsc/index.ts +1 -1
- package/src/rsc/json-route-result.ts +38 -0
- package/src/rsc/loader-fetch.ts +23 -3
- package/src/rsc/manifest-init.ts +33 -42
- package/src/rsc/origin-guard.ts +39 -25
- package/src/rsc/progressive-enhancement.ts +30 -3
- package/src/rsc/redirect-guard.ts +99 -0
- package/src/rsc/response-error.ts +79 -12
- package/src/rsc/response-route-handler.ts +90 -63
- package/src/rsc/rsc-rendering.ts +56 -54
- package/src/rsc/runtime-warnings.ts +23 -10
- package/src/rsc/server-action.ts +74 -67
- package/src/rsc/ssr-setup.ts +18 -2
- package/src/rsc/types.ts +25 -6
- package/src/runtime-env.ts +18 -0
- package/src/search-params.ts +4 -20
- package/src/segment-content-promise.ts +67 -0
- package/src/segment-loader-promise.ts +134 -0
- package/src/segment-system.tsx +272 -129
- package/src/serialize.ts +243 -0
- package/src/server/context.ts +309 -61
- package/src/server/cookie-store.ts +80 -5
- package/src/server/handle-store.ts +26 -24
- package/src/server/loader-registry.ts +10 -28
- package/src/server/request-context.ts +338 -126
- package/src/ssr/index.tsx +23 -15
- package/src/static-handler.ts +27 -18
- package/src/testing/cache-status.ts +162 -0
- package/src/testing/collect-handle.ts +40 -0
- package/src/testing/dispatch.ts +618 -0
- package/src/testing/dom.entry.ts +22 -0
- package/src/testing/e2e/fixture.ts +188 -0
- package/src/testing/e2e/index.ts +128 -0
- package/src/testing/e2e/matchers.ts +35 -0
- package/src/testing/e2e/page-helpers.ts +272 -0
- package/src/testing/e2e/parity.ts +387 -0
- package/src/testing/e2e/server.ts +195 -0
- package/src/testing/flight-matchers.ts +97 -0
- package/src/testing/flight-normalize.ts +11 -0
- package/src/testing/flight-runtime.d.ts +57 -0
- package/src/testing/flight-tree.ts +682 -0
- package/src/testing/flight.entry.ts +52 -0
- package/src/testing/flight.ts +232 -0
- package/src/testing/generated-routes.ts +183 -0
- package/src/testing/index.ts +99 -0
- package/src/testing/internal/context.ts +348 -0
- package/src/testing/internal/flight-client-globals.ts +30 -0
- package/src/testing/internal/seed-vars.ts +54 -0
- package/src/testing/render-handler.ts +330 -0
- package/src/testing/render-route.tsx +566 -0
- package/src/testing/run-loader.ts +378 -0
- package/src/testing/run-middleware.ts +205 -0
- package/src/testing/vitest-stubs/cloudflare-email.ts +9 -0
- package/src/testing/vitest-stubs/cloudflare-workers.ts +21 -0
- package/src/testing/vitest-stubs/plugin-rsc.ts +16 -0
- package/src/testing/vitest-stubs/version.ts +5 -0
- package/src/testing/vitest.ts +305 -0
- package/src/theme/ThemeProvider.tsx +0 -52
- package/src/theme/ThemeScript.tsx +0 -6
- package/src/theme/constants.ts +0 -12
- package/src/theme/index.ts +0 -7
- package/src/theme/theme-context.ts +1 -5
- package/src/theme/theme-script.ts +0 -14
- package/src/theme/use-theme.ts +0 -3
- package/src/types/boundaries.ts +0 -35
- package/src/types/cache-types.ts +17 -8
- package/src/types/error-types.ts +30 -90
- package/src/types/global-namespace.ts +54 -41
- package/src/types/handler-context.ts +233 -81
- package/src/types/index.ts +1 -10
- package/src/types/loader-types.ts +44 -15
- package/src/types/request-scope.ts +107 -0
- package/src/types/route-config.ts +6 -50
- package/src/types/route-entry.ts +19 -7
- package/src/types/segments.ts +37 -14
- package/src/urls/include-helper.ts +33 -70
- package/src/urls/index.ts +1 -11
- package/src/urls/path-helper-types.ts +58 -11
- package/src/urls/path-helper.ts +57 -111
- package/src/urls/pattern-types.ts +48 -19
- package/src/urls/response-types.ts +25 -22
- package/src/urls/type-extraction.ts +58 -139
- package/src/urls/urls-function.ts +1 -18
- package/src/use-loader.tsx +346 -89
- package/src/vite/debug.ts +185 -0
- package/src/vite/discovery/bundle-postprocess.ts +36 -38
- package/src/vite/discovery/discover-routers.ts +130 -85
- package/src/vite/discovery/discovery-errors.ts +194 -0
- package/src/vite/discovery/gate-state.ts +171 -0
- package/src/vite/discovery/prerender-collection.ts +192 -99
- package/src/vite/discovery/route-types-writer.ts +40 -84
- package/src/vite/discovery/self-gen-tracking.ts +27 -1
- package/src/vite/discovery/state.ts +51 -6
- package/src/vite/discovery/virtual-module-codegen.ts +14 -34
- package/src/vite/index.ts +8 -0
- package/src/vite/plugin-types.ts +187 -69
- package/src/vite/plugins/cjs-to-esm.ts +8 -18
- package/src/vite/plugins/client-ref-dedup.ts +16 -11
- package/src/vite/plugins/client-ref-hashing.ts +28 -15
- package/src/vite/plugins/cloudflare-protocol-loader-hook.d.mts +23 -0
- package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/src/vite/plugins/cloudflare-protocol-stub.ts +194 -0
- package/src/vite/plugins/expose-action-id.ts +49 -98
- package/src/vite/plugins/expose-id-utils.ts +11 -50
- package/src/vite/plugins/expose-ids/export-analysis.ts +76 -34
- package/src/vite/plugins/expose-ids/handler-transform.ts +10 -48
- package/src/vite/plugins/expose-ids/loader-transform.ts +3 -20
- package/src/vite/plugins/expose-ids/router-transform.ts +20 -16
- package/src/vite/plugins/expose-internal-ids.ts +554 -317
- package/src/vite/plugins/performance-tracks.ts +89 -0
- package/src/vite/plugins/refresh-cmd.ts +89 -27
- package/src/vite/plugins/use-cache-transform.ts +73 -83
- package/src/vite/plugins/vercel-output.ts +258 -0
- package/src/vite/plugins/version-injector.ts +21 -25
- package/src/vite/plugins/version-plugin.ts +41 -20
- package/src/vite/plugins/virtual-entries.ts +2 -17
- package/src/vite/rango.ts +257 -289
- package/src/vite/router-discovery.ts +930 -140
- package/src/vite/utils/ast-handler-extract.ts +15 -31
- package/src/vite/utils/banner.ts +4 -4
- package/src/vite/utils/bundle-analysis.ts +10 -15
- package/src/vite/utils/client-chunks.ts +184 -0
- package/src/vite/utils/forward-user-plugins.ts +171 -0
- package/src/vite/utils/manifest-utils.ts +4 -59
- package/src/vite/utils/package-resolution.ts +20 -52
- package/src/vite/utils/prerender-utils.ts +27 -29
- package/src/vite/utils/shared-utils.ts +92 -42
- package/src/browser/action-response-classifier.ts +0 -99
- package/src/browser/react/use-client-cache.ts +0 -58
- package/src/browser/shallow.ts +0 -40
- package/src/handles/index.ts +0 -7
- package/src/router/middleware-cookies.ts +0 -55
|
@@ -39,18 +39,29 @@ export interface BreadcrumbItem {
|
|
|
39
39
|
/**
|
|
40
40
|
* Collect function for Breadcrumbs handle.
|
|
41
41
|
* Flattens segments in parent-to-child order with deduplication by href
|
|
42
|
-
* (last item for each href wins).
|
|
42
|
+
* (last item for each href wins). Deferred slots (`ctx.use(Breadcrumbs).defer()`)
|
|
43
|
+
* arrive as pending Promise entries with no href yet; they are passed through by
|
|
44
|
+
* identity and excluded from the href dedup so concurrent deferred crumbs do not
|
|
45
|
+
* all collapse under a single `undefined` href.
|
|
43
46
|
*/
|
|
44
47
|
function collectBreadcrumbs(segments: BreadcrumbItem[][]): BreadcrumbItem[] {
|
|
45
48
|
const all = segments.flat();
|
|
46
|
-
const seen = new Map<string, number>();
|
|
47
49
|
|
|
50
|
+
const isResolvedItem = (item: unknown): item is BreadcrumbItem =>
|
|
51
|
+
item != null &&
|
|
52
|
+
typeof item === "object" &&
|
|
53
|
+
typeof (item as { then?: unknown }).then !== "function" &&
|
|
54
|
+
typeof (item as { href?: unknown }).href === "string";
|
|
55
|
+
|
|
56
|
+
const seen = new Map<string, number>();
|
|
48
57
|
for (let i = 0; i < all.length; i++) {
|
|
49
|
-
seen.set(all[i].href, i);
|
|
58
|
+
if (isResolvedItem(all[i])) seen.set(all[i].href, i);
|
|
50
59
|
}
|
|
51
60
|
|
|
52
|
-
//
|
|
53
|
-
return all.filter(
|
|
61
|
+
// Deferred items bypass dedup (excluded via !isResolvedItem check).
|
|
62
|
+
return all.filter(
|
|
63
|
+
(item, index) => !isResolvedItem(item) || seen.get(item.href) === index,
|
|
64
|
+
);
|
|
54
65
|
}
|
|
55
66
|
|
|
56
67
|
/**
|
package/src/handles/meta.ts
CHANGED
|
@@ -35,9 +35,6 @@ import type {
|
|
|
35
35
|
UnsetDescriptor,
|
|
36
36
|
} from "../router/types.js";
|
|
37
37
|
|
|
38
|
-
/**
|
|
39
|
-
* Type guard for unset descriptor
|
|
40
|
-
*/
|
|
41
38
|
function isUnsetDescriptor(
|
|
42
39
|
descriptor: MetaDescriptor,
|
|
43
40
|
): descriptor is UnsetDescriptor {
|
|
@@ -49,9 +46,6 @@ function isUnsetDescriptor(
|
|
|
49
46
|
);
|
|
50
47
|
}
|
|
51
48
|
|
|
52
|
-
/**
|
|
53
|
-
* Type guard for title descriptor (any form)
|
|
54
|
-
*/
|
|
55
49
|
function isTitleDescriptor(
|
|
56
50
|
descriptor: MetaDescriptor,
|
|
57
51
|
): descriptor is { title: TitleDescriptor } {
|
|
@@ -62,9 +56,6 @@ function isTitleDescriptor(
|
|
|
62
56
|
);
|
|
63
57
|
}
|
|
64
58
|
|
|
65
|
-
/**
|
|
66
|
-
* Type guard for title template descriptor
|
|
67
|
-
*/
|
|
68
59
|
function isTitleTemplate(
|
|
69
60
|
title: TitleDescriptor,
|
|
70
61
|
): title is { template: string; default: string } {
|
|
@@ -76,21 +67,13 @@ function isTitleTemplate(
|
|
|
76
67
|
);
|
|
77
68
|
}
|
|
78
69
|
|
|
79
|
-
/**
|
|
80
|
-
* Type guard for absolute title descriptor
|
|
81
|
-
*/
|
|
82
70
|
function isAbsoluteTitle(
|
|
83
71
|
title: TitleDescriptor,
|
|
84
72
|
): title is { absolute: string } {
|
|
85
73
|
return typeof title === "object" && title !== null && "absolute" in title;
|
|
86
74
|
}
|
|
87
75
|
|
|
88
|
-
/**
|
|
89
|
-
* Get a unique key for a meta descriptor for deduplication.
|
|
90
|
-
* Returns undefined for descriptors that shouldn't be deduplicated.
|
|
91
|
-
*/
|
|
92
76
|
function getMetaKey(descriptor: MetaDescriptor): string | undefined {
|
|
93
|
-
// Skip unset descriptors - they are processed separately
|
|
94
77
|
if (isUnsetDescriptor(descriptor)) {
|
|
95
78
|
return undefined;
|
|
96
79
|
}
|
|
@@ -110,13 +93,10 @@ function getMetaKey(descriptor: MetaDescriptor): string | undefined {
|
|
|
110
93
|
return `httpEquiv:${descriptor.httpEquiv}`;
|
|
111
94
|
}
|
|
112
95
|
if ("script:ld+json" in descriptor) {
|
|
113
|
-
// JSON-LD scripts can have multiple, don't dedupe by default
|
|
114
96
|
return undefined;
|
|
115
97
|
}
|
|
116
98
|
if ("tagName" in descriptor) {
|
|
117
|
-
// For link tags, dedupe by rel if present
|
|
118
99
|
if (descriptor.tagName === "link" && "rel" in descriptor) {
|
|
119
|
-
// Some link rels should be unique (canonical), others not (stylesheet)
|
|
120
100
|
const uniqueRels = ["canonical", "icon", "apple-touch-icon"];
|
|
121
101
|
if (uniqueRels.includes(descriptor.rel as string)) {
|
|
122
102
|
return `link:${descriptor.rel}`;
|
|
@@ -136,9 +116,6 @@ const defaultMetaDescriptors: MetaDescriptor[] = [
|
|
|
136
116
|
{ name: "viewport", content: "width=device-width, initial-scale=1" },
|
|
137
117
|
];
|
|
138
118
|
|
|
139
|
-
/**
|
|
140
|
-
* Helper to add or replace a descriptor in the result array
|
|
141
|
-
*/
|
|
142
119
|
function addOrReplace(
|
|
143
120
|
result: MetaDescriptor[],
|
|
144
121
|
keyToIndex: Map<string, number>,
|
|
@@ -155,9 +132,6 @@ function addOrReplace(
|
|
|
155
132
|
}
|
|
156
133
|
}
|
|
157
134
|
|
|
158
|
-
/**
|
|
159
|
-
* Helper to update indices after removing an element
|
|
160
|
-
*/
|
|
161
135
|
function updateIndicesAfterRemoval(
|
|
162
136
|
keyToIndex: Map<string, number>,
|
|
163
137
|
removedIndex: number,
|
|
@@ -169,17 +143,11 @@ function updateIndicesAfterRemoval(
|
|
|
169
143
|
}
|
|
170
144
|
}
|
|
171
145
|
|
|
172
|
-
/**
|
|
173
|
-
* Collect function for Meta handle.
|
|
174
|
-
* Includes default meta descriptors, then deduplicates by key with later routes overriding earlier ones.
|
|
175
|
-
* Supports title templates, absolute titles, and unset descriptors.
|
|
176
|
-
*/
|
|
177
146
|
function collectMeta(segments: MetaDescriptor[][]): MetaDescriptor[] {
|
|
178
147
|
const result: MetaDescriptor[] = [];
|
|
179
148
|
const keyToIndex = new Map<string, number>();
|
|
180
149
|
let titleTemplate: string | undefined;
|
|
181
150
|
|
|
182
|
-
// Add defaults first so they can be overridden
|
|
183
151
|
for (const descriptor of defaultMetaDescriptors) {
|
|
184
152
|
const key = getMetaKey(descriptor);
|
|
185
153
|
if (key !== undefined) {
|
|
@@ -190,7 +158,6 @@ function collectMeta(segments: MetaDescriptor[][]): MetaDescriptor[] {
|
|
|
190
158
|
|
|
191
159
|
for (const descriptors of segments) {
|
|
192
160
|
for (const descriptor of descriptors) {
|
|
193
|
-
// Handle unset descriptors
|
|
194
161
|
if (isUnsetDescriptor(descriptor)) {
|
|
195
162
|
const keyToRemove = descriptor.unset;
|
|
196
163
|
if (keyToIndex.has(keyToRemove)) {
|
|
@@ -202,14 +169,11 @@ function collectMeta(segments: MetaDescriptor[][]): MetaDescriptor[] {
|
|
|
202
169
|
continue;
|
|
203
170
|
}
|
|
204
171
|
|
|
205
|
-
// Handle title descriptors with template/absolute support
|
|
206
172
|
if (isTitleDescriptor(descriptor)) {
|
|
207
173
|
const titleValue = descriptor.title;
|
|
208
174
|
|
|
209
175
|
if (isTitleTemplate(titleValue)) {
|
|
210
|
-
// Store template for subsequent title descriptors in child segments
|
|
211
176
|
titleTemplate = titleValue.template;
|
|
212
|
-
// Set the default title
|
|
213
177
|
addOrReplace(
|
|
214
178
|
result,
|
|
215
179
|
keyToIndex,
|
|
@@ -220,7 +184,6 @@ function collectMeta(segments: MetaDescriptor[][]): MetaDescriptor[] {
|
|
|
220
184
|
}
|
|
221
185
|
|
|
222
186
|
if (isAbsoluteTitle(titleValue)) {
|
|
223
|
-
// Absolute title bypasses any template
|
|
224
187
|
addOrReplace(
|
|
225
188
|
result,
|
|
226
189
|
keyToIndex,
|
|
@@ -230,7 +193,6 @@ function collectMeta(segments: MetaDescriptor[][]): MetaDescriptor[] {
|
|
|
230
193
|
continue;
|
|
231
194
|
}
|
|
232
195
|
|
|
233
|
-
// String title - apply template if one exists
|
|
234
196
|
const finalTitle = titleTemplate
|
|
235
197
|
? titleTemplate.replace("%s", titleValue as string)
|
|
236
198
|
: titleValue;
|
|
@@ -243,7 +205,6 @@ function collectMeta(segments: MetaDescriptor[][]): MetaDescriptor[] {
|
|
|
243
205
|
continue;
|
|
244
206
|
}
|
|
245
207
|
|
|
246
|
-
// Handle all other descriptors
|
|
247
208
|
const key = getMetaKey(descriptor);
|
|
248
209
|
addOrReplace(result, keyToIndex, descriptor, key);
|
|
249
210
|
}
|
|
@@ -1,9 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Cookie Override Handler
|
|
3
|
-
*
|
|
4
|
-
* Manages cookie-based host override for development environments.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
1
|
import type { HostOverrideConfig } from "./types.js";
|
|
8
2
|
import type { RouterRequestInput } from "../router/router-interfaces.js";
|
|
9
3
|
import { matchPattern, parseRequest } from "./pattern-matcher.js";
|
|
@@ -13,9 +7,6 @@ import {
|
|
|
13
7
|
HostValidationError,
|
|
14
8
|
} from "./errors.js";
|
|
15
9
|
|
|
16
|
-
/**
|
|
17
|
-
* Parse cookies from request
|
|
18
|
-
*/
|
|
19
10
|
export function parseCookies(request: Request): Record<string, string> {
|
|
20
11
|
const cookieHeader = request.headers.get("cookie");
|
|
21
12
|
if (!cookieHeader) {
|
|
@@ -40,24 +31,15 @@ export function parseCookies(request: Request): Record<string, string> {
|
|
|
40
31
|
return cookies;
|
|
41
32
|
}
|
|
42
33
|
|
|
43
|
-
/**
|
|
44
|
-
* Get cookie value from request
|
|
45
|
-
*/
|
|
46
34
|
export function getCookie(request: Request, name: string): string | undefined {
|
|
47
35
|
const cookies = parseCookies(request);
|
|
48
36
|
return cookies[name];
|
|
49
37
|
}
|
|
50
38
|
|
|
51
|
-
/**
|
|
52
|
-
* Create Set-Cookie header to delete a cookie
|
|
53
|
-
*/
|
|
54
39
|
export function createDeleteCookieHeader(name: string): string {
|
|
55
40
|
return `${name}=; Max-Age=0; Path=/; Secure; HttpOnly`;
|
|
56
41
|
}
|
|
57
42
|
|
|
58
|
-
/**
|
|
59
|
-
* Create error response with cookie deletion
|
|
60
|
-
*/
|
|
61
43
|
export function createCookieErrorResponse(
|
|
62
44
|
cookieName: string,
|
|
63
45
|
message: string,
|
|
@@ -77,9 +59,6 @@ export function createCookieErrorResponse(
|
|
|
77
59
|
);
|
|
78
60
|
}
|
|
79
61
|
|
|
80
|
-
/**
|
|
81
|
-
* Check if current host is allowed to use override
|
|
82
|
-
*/
|
|
83
62
|
export function isHostAllowed(
|
|
84
63
|
request: Request,
|
|
85
64
|
allowedHosts: string[],
|
|
@@ -95,12 +74,6 @@ export function isHostAllowed(
|
|
|
95
74
|
return false;
|
|
96
75
|
}
|
|
97
76
|
|
|
98
|
-
/**
|
|
99
|
-
* Handle cookie override logic
|
|
100
|
-
*
|
|
101
|
-
* Returns overridden hostname if valid, original hostname if no override.
|
|
102
|
-
* Throws errors for invalid overrides.
|
|
103
|
-
*/
|
|
104
77
|
export function handleCookieOverride(
|
|
105
78
|
request: Request,
|
|
106
79
|
config: HostOverrideConfig | undefined,
|
|
@@ -115,46 +88,37 @@ export function handleCookieOverride(
|
|
|
115
88
|
const cookieValue = getCookie(request, cookieName);
|
|
116
89
|
const { hostname: originalHostname } = parseRequest(request);
|
|
117
90
|
|
|
118
|
-
// No cookie - return original hostname
|
|
119
91
|
if (!cookieValue) {
|
|
120
92
|
return originalHostname;
|
|
121
93
|
}
|
|
122
94
|
|
|
123
|
-
// Check if current host is allowed
|
|
124
95
|
const allowed = isHostAllowed(request, allowedHosts);
|
|
125
96
|
|
|
126
|
-
// If not allowed, throw error
|
|
127
97
|
if (!allowed) {
|
|
128
98
|
throw new HostOverrideNotAllowedError(originalHostname, cookieName, {
|
|
129
99
|
cause: { cookieValue, currentHost: originalHostname },
|
|
130
100
|
});
|
|
131
101
|
}
|
|
132
102
|
|
|
133
|
-
// If allowed and has custom validation, run it
|
|
134
103
|
if (validate) {
|
|
135
104
|
try {
|
|
136
105
|
const validatedHostname = validate(request, cookieValue, input);
|
|
137
106
|
return validatedHostname;
|
|
138
107
|
} catch (error) {
|
|
139
|
-
// Wrap in HostValidationError
|
|
140
108
|
const message = error instanceof Error ? error.message : String(error);
|
|
141
109
|
throw new HostValidationError(message, error);
|
|
142
110
|
}
|
|
143
111
|
}
|
|
144
112
|
|
|
145
|
-
// Default validation - verify it's a valid hostname using URL constructor
|
|
146
113
|
try {
|
|
147
|
-
// Try to construct a URL with the hostname to validate it
|
|
148
114
|
const testUrl = new URL(`https://${cookieValue}`);
|
|
149
115
|
|
|
150
|
-
// Ensure the hostname matches what we provided (URL constructor normalizes it)
|
|
151
116
|
if (testUrl.hostname !== cookieValue) {
|
|
152
117
|
throw new InvalidHostnameError(cookieValue, {
|
|
153
118
|
cause: { original: cookieValue, normalized: testUrl.hostname },
|
|
154
119
|
});
|
|
155
120
|
}
|
|
156
121
|
} catch (error) {
|
|
157
|
-
// If URL constructor failed, throw InvalidHostnameError with cause
|
|
158
122
|
if (error instanceof InvalidHostnameError) {
|
|
159
123
|
throw error;
|
|
160
124
|
}
|
package/src/host/errors.ts
CHANGED
|
@@ -4,16 +4,10 @@
|
|
|
4
4
|
* All host router errors extend HostRouterError for easy instance checking.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
/**
|
|
8
|
-
* Error options with cause
|
|
9
|
-
*/
|
|
10
7
|
interface ErrorOptions {
|
|
11
8
|
cause?: unknown;
|
|
12
9
|
}
|
|
13
10
|
|
|
14
|
-
/**
|
|
15
|
-
* Base error class for all host router errors
|
|
16
|
-
*/
|
|
17
11
|
export class HostRouterError extends Error {
|
|
18
12
|
cause?: unknown;
|
|
19
13
|
|
|
@@ -27,9 +21,6 @@ export class HostRouterError extends Error {
|
|
|
27
21
|
}
|
|
28
22
|
}
|
|
29
23
|
|
|
30
|
-
/**
|
|
31
|
-
* Error thrown when pattern validation fails
|
|
32
|
-
*/
|
|
33
24
|
export class InvalidPatternError extends HostRouterError {
|
|
34
25
|
constructor(pattern: string, reason: string, options?: ErrorOptions) {
|
|
35
26
|
super(`Invalid pattern "${pattern}": ${reason}`, options);
|
|
@@ -38,9 +29,6 @@ export class InvalidPatternError extends HostRouterError {
|
|
|
38
29
|
}
|
|
39
30
|
}
|
|
40
31
|
|
|
41
|
-
/**
|
|
42
|
-
* Error thrown when cookie override is not allowed
|
|
43
|
-
*/
|
|
44
32
|
export class HostOverrideNotAllowedError extends HostRouterError {
|
|
45
33
|
constructor(currentHost: string, cookieName: string, options?: ErrorOptions) {
|
|
46
34
|
super(
|
|
@@ -52,9 +40,6 @@ export class HostOverrideNotAllowedError extends HostRouterError {
|
|
|
52
40
|
}
|
|
53
41
|
}
|
|
54
42
|
|
|
55
|
-
/**
|
|
56
|
-
* Error thrown when cookie hostname is invalid
|
|
57
|
-
*/
|
|
58
43
|
export class InvalidHostnameError extends HostRouterError {
|
|
59
44
|
constructor(hostname: string, options?: ErrorOptions) {
|
|
60
45
|
super(`Invalid hostname format: "${hostname}"`, options);
|
|
@@ -63,9 +48,6 @@ export class InvalidHostnameError extends HostRouterError {
|
|
|
63
48
|
}
|
|
64
49
|
}
|
|
65
50
|
|
|
66
|
-
/**
|
|
67
|
-
* Error thrown when custom validation fails
|
|
68
|
-
*/
|
|
69
51
|
export class HostValidationError extends HostRouterError {
|
|
70
52
|
constructor(message: string, cause?: unknown) {
|
|
71
53
|
super(message, { cause });
|
|
@@ -74,9 +56,6 @@ export class HostValidationError extends HostRouterError {
|
|
|
74
56
|
}
|
|
75
57
|
}
|
|
76
58
|
|
|
77
|
-
/**
|
|
78
|
-
* Error thrown when no route matches
|
|
79
|
-
*/
|
|
80
59
|
export class NoRouteMatchError extends HostRouterError {
|
|
81
60
|
constructor(hostname: string, pathname: string, options?: ErrorOptions) {
|
|
82
61
|
super(`No route matched for ${hostname}${pathname}`, options);
|
|
@@ -85,9 +64,6 @@ export class NoRouteMatchError extends HostRouterError {
|
|
|
85
64
|
}
|
|
86
65
|
}
|
|
87
66
|
|
|
88
|
-
/**
|
|
89
|
-
* Error thrown when handler type is invalid
|
|
90
|
-
*/
|
|
91
67
|
export class InvalidHandlerError extends HostRouterError {
|
|
92
68
|
constructor(handler: unknown, options?: ErrorOptions) {
|
|
93
69
|
super(`Invalid handler type: ${typeof handler}`, options);
|
package/src/host/index.ts
CHANGED
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
*
|
|
12
12
|
* const router = createHostRouter();
|
|
13
13
|
*
|
|
14
|
-
* router.host(['.']).
|
|
15
|
-
* router.host(['admin.*']).
|
|
14
|
+
* router.host(['.']).lazy(() => import('./apps/main'));
|
|
15
|
+
* router.host(['admin.*']).lazy(() => import('./apps/admin'));
|
|
16
16
|
*
|
|
17
17
|
* export default {
|
|
18
18
|
* fetch(request) {
|
|
@@ -20,6 +20,12 @@
|
|
|
20
20
|
* }
|
|
21
21
|
* };
|
|
22
22
|
* ```
|
|
23
|
+
*
|
|
24
|
+
* The host surface (`Handler`, `Middleware`, `match`, `HostOverrideConfig.validate`)
|
|
25
|
+
* types `input` as `RouterRequestInput<any>` by design: a host router fans out to
|
|
26
|
+
* heterogeneous sub-apps with differing env/vars shapes, so there is no single
|
|
27
|
+
* `TEnv`/`TVars` to thread through. `input.env`/`input.vars` are therefore `any`
|
|
28
|
+
* here; the typed env shape lives on each sub-app's `createRouter<TEnv>()`.
|
|
23
29
|
*/
|
|
24
30
|
|
|
25
31
|
// Core router
|
|
@@ -12,15 +12,18 @@
|
|
|
12
12
|
* - `**.example.com` - any depth subdomain
|
|
13
13
|
* - `admin.*` - admin subdomain of any apex
|
|
14
14
|
* - `example.com/admin` - specific domain with path prefix
|
|
15
|
+
*
|
|
16
|
+
* Apex vs subdomain is classified purely by dot-part COUNT (apex == exactly 2
|
|
17
|
+
* parts) — there is no Public Suffix List. A registrable domain under a
|
|
18
|
+
* multi-label public suffix (example.co.uk, shop.com.au) has 3+ parts and is
|
|
19
|
+
* therefore treated as a SUBDOMAIN, not an apex: `.`/`*` will NOT match it and
|
|
20
|
+
* `*.` WILL. If registrable-domain accuracy matters for a host-router consumer,
|
|
21
|
+
* supply an explicit apex/host hint rather than relying on the part count.
|
|
15
22
|
*/
|
|
16
23
|
|
|
17
24
|
import { InvalidPatternError } from "./errors.js";
|
|
18
25
|
|
|
19
|
-
/**
|
|
20
|
-
* Normalize a pattern by removing trailing slashes from paths
|
|
21
|
-
*/
|
|
22
26
|
export function normalizePattern(pattern: string): string {
|
|
23
|
-
// If pattern has a path component, remove trailing slash
|
|
24
27
|
const slashIndex = pattern.indexOf("/");
|
|
25
28
|
if (slashIndex !== -1) {
|
|
26
29
|
const domain = pattern.slice(0, slashIndex);
|
|
@@ -30,9 +33,6 @@ export function normalizePattern(pattern: string): string {
|
|
|
30
33
|
return pattern;
|
|
31
34
|
}
|
|
32
35
|
|
|
33
|
-
/**
|
|
34
|
-
* Parse hostname and path from request URL
|
|
35
|
-
*/
|
|
36
36
|
export function parseRequest(request: Request): {
|
|
37
37
|
hostname: string;
|
|
38
38
|
pathname: string;
|
|
@@ -46,26 +46,14 @@ export function parseRequest(request: Request): {
|
|
|
46
46
|
return { hostname, pathname, parts };
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
/**
|
|
50
|
-
* Count subdomain levels (0 for apex, 1+ for subdomains)
|
|
51
|
-
*/
|
|
52
49
|
function getSubdomainLevel(parts: string[]): number {
|
|
53
|
-
// Apex domain has 2 parts (example.com)
|
|
54
|
-
// Single subdomain has 3 parts (www.example.com)
|
|
55
|
-
// Multi-level has 4+ parts (a.b.example.com)
|
|
56
50
|
return Math.max(0, parts.length - 2);
|
|
57
51
|
}
|
|
58
52
|
|
|
59
|
-
/**
|
|
60
|
-
* Check if hostname is an apex domain (no subdomains)
|
|
61
|
-
*/
|
|
62
53
|
function isApexDomain(parts: string[]): boolean {
|
|
63
54
|
return parts.length === 2;
|
|
64
55
|
}
|
|
65
56
|
|
|
66
|
-
/**
|
|
67
|
-
* Match a single pattern against hostname and path
|
|
68
|
-
*/
|
|
69
57
|
export function matchPattern(
|
|
70
58
|
pattern: string,
|
|
71
59
|
hostname: string,
|
|
@@ -74,19 +62,16 @@ export function matchPattern(
|
|
|
74
62
|
): boolean {
|
|
75
63
|
const normalized = normalizePattern(pattern);
|
|
76
64
|
|
|
77
|
-
// Check if pattern has path component
|
|
78
65
|
const slashIndex = normalized.indexOf("/");
|
|
79
66
|
const hasPath = slashIndex !== -1;
|
|
80
67
|
const domainPattern = hasPath ? normalized.slice(0, slashIndex) : normalized;
|
|
81
68
|
const pathPattern = hasPath ? normalized.slice(slashIndex) : null;
|
|
82
69
|
|
|
83
|
-
// First match domain
|
|
84
70
|
const domainMatch = matchDomainPattern(domainPattern, hostname, parts);
|
|
85
71
|
if (!domainMatch) {
|
|
86
72
|
return false;
|
|
87
73
|
}
|
|
88
74
|
|
|
89
|
-
// Then match path (prefix match)
|
|
90
75
|
if (pathPattern) {
|
|
91
76
|
return pathname === pathPattern || pathname.startsWith(pathPattern + "/");
|
|
92
77
|
}
|
|
@@ -94,81 +79,62 @@ export function matchPattern(
|
|
|
94
79
|
return true;
|
|
95
80
|
}
|
|
96
81
|
|
|
97
|
-
/**
|
|
98
|
-
* Match domain pattern against hostname
|
|
99
|
-
*/
|
|
100
82
|
function matchDomainPattern(
|
|
101
83
|
pattern: string,
|
|
102
84
|
hostname: string,
|
|
103
85
|
parts: string[],
|
|
104
86
|
): boolean {
|
|
105
|
-
// Exact match
|
|
106
87
|
if (pattern === hostname) {
|
|
107
88
|
return true;
|
|
108
89
|
}
|
|
109
90
|
|
|
110
|
-
// `.` or `*` - any apex domain
|
|
111
91
|
if (pattern === "." || pattern === "*") {
|
|
112
92
|
return isApexDomain(parts);
|
|
113
93
|
}
|
|
114
94
|
|
|
115
|
-
// `**` - any domain (apex + all subdomains)
|
|
116
95
|
if (pattern === "**") {
|
|
117
96
|
return true;
|
|
118
97
|
}
|
|
119
98
|
|
|
120
|
-
// `*.` - any single-level subdomain
|
|
121
99
|
if (pattern === "*.") {
|
|
122
100
|
return getSubdomainLevel(parts) === 1;
|
|
123
101
|
}
|
|
124
102
|
|
|
125
|
-
// `**.` - any multi-level subdomain (2+ levels)
|
|
126
103
|
if (pattern === "**.") {
|
|
127
104
|
return getSubdomainLevel(parts) >= 2;
|
|
128
105
|
}
|
|
129
106
|
|
|
130
|
-
// `*.tld` - any apex domain with specific TLD (e.g., *.com)
|
|
131
107
|
if (pattern.startsWith("*.") && !pattern.includes(".", 2)) {
|
|
132
108
|
const tld = pattern.slice(2);
|
|
133
109
|
return isApexDomain(parts) && hostname.endsWith("." + tld);
|
|
134
110
|
}
|
|
135
111
|
|
|
136
|
-
// `*.example.com` - single subdomain of specific domain
|
|
137
112
|
if (pattern.startsWith("*.")) {
|
|
138
113
|
const baseDomain = pattern.slice(2);
|
|
139
114
|
if (hostname.endsWith("." + baseDomain)) {
|
|
140
|
-
// Count parts: if pattern is *.example.com (3 parts),
|
|
141
|
-
// hostname should have exactly 4 parts (www.example.com)
|
|
142
115
|
const patternParts = baseDomain.split(".");
|
|
143
116
|
return parts.length === patternParts.length + 1;
|
|
144
117
|
}
|
|
145
118
|
return false;
|
|
146
119
|
}
|
|
147
120
|
|
|
148
|
-
// `**.example.com` - any depth subdomain of specific domain
|
|
149
121
|
if (pattern.startsWith("**.")) {
|
|
150
122
|
const baseDomain = pattern.slice(3);
|
|
151
123
|
if (hostname.endsWith("." + baseDomain)) {
|
|
152
124
|
const patternParts = baseDomain.split(".");
|
|
153
|
-
// Must have more parts than the base domain (i.e., has subdomains)
|
|
154
125
|
return parts.length > patternParts.length;
|
|
155
126
|
}
|
|
156
127
|
return false;
|
|
157
128
|
}
|
|
158
129
|
|
|
159
|
-
// `subdomain.*` - specific subdomain of any apex domain
|
|
160
|
-
// e.g., admin.* matches admin.example.com, admin.google.com
|
|
161
130
|
if (pattern.endsWith(".*")) {
|
|
162
131
|
const subdomain = pattern.slice(0, -2);
|
|
163
|
-
// Must be single-level subdomain (3 parts total)
|
|
164
132
|
if (parts.length === 3 && parts[0] === subdomain) {
|
|
165
133
|
return true;
|
|
166
134
|
}
|
|
167
135
|
return false;
|
|
168
136
|
}
|
|
169
137
|
|
|
170
|
-
// `subdomain.**` - specific subdomain of any domain (including multi-level)
|
|
171
|
-
// e.g., admin.** matches admin.example.com, admin.sub.example.com
|
|
172
138
|
if (pattern.endsWith(".**")) {
|
|
173
139
|
const subdomain = pattern.slice(0, -3);
|
|
174
140
|
if (parts.length >= 3 && parts[0] === subdomain) {
|
|
@@ -177,11 +143,8 @@ function matchDomainPattern(
|
|
|
177
143
|
return false;
|
|
178
144
|
}
|
|
179
145
|
|
|
180
|
-
// `subdomain.` - specific subdomain of any apex domain (no wildcard)
|
|
181
|
-
// e.g., admin. matches admin.example.com, admin.google.com
|
|
182
146
|
if (pattern.endsWith(".") && !pattern.includes("*")) {
|
|
183
147
|
const subdomain = pattern.slice(0, -1);
|
|
184
|
-
// Must be exactly 3 parts (subdomain.domain.tld)
|
|
185
148
|
if (parts.length === 3 && parts[0] === subdomain) {
|
|
186
149
|
return true;
|
|
187
150
|
}
|
|
@@ -191,9 +154,6 @@ function matchDomainPattern(
|
|
|
191
154
|
return false;
|
|
192
155
|
}
|
|
193
156
|
|
|
194
|
-
/**
|
|
195
|
-
* Validate pattern format
|
|
196
|
-
*/
|
|
197
157
|
export function validatePattern(pattern: string): void {
|
|
198
158
|
if (!pattern || typeof pattern !== "string") {
|
|
199
159
|
throw new InvalidPatternError(
|
|
@@ -203,12 +163,9 @@ export function validatePattern(pattern: string): void {
|
|
|
203
163
|
);
|
|
204
164
|
}
|
|
205
165
|
|
|
206
|
-
// Check for invalid characters (spaces, etc.)
|
|
207
166
|
if (/\s/.test(pattern)) {
|
|
208
167
|
throw new InvalidPatternError(pattern, "contains whitespace", {
|
|
209
168
|
cause: { pattern },
|
|
210
169
|
});
|
|
211
170
|
}
|
|
212
|
-
|
|
213
|
-
// Additional validation can be added here
|
|
214
171
|
}
|