@rangojs/router 0.0.0-experimental.124 → 0.0.0-experimental.126
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/README.md +6 -4
- package/dist/bin/rango.js +3 -4
- package/dist/vite/index.js +315 -68
- package/package.json +19 -18
- package/skills/breadcrumbs/SKILL.md +60 -0
- package/skills/hooks/SKILL.md +2 -2
- package/skills/route/SKILL.md +6 -0
- package/skills/server-actions/SKILL.md +25 -1
- package/skills/testing/SKILL.md +17 -17
- package/skills/testing/cache-prerender.md +29 -3
- package/skills/testing/flight.md +13 -10
- package/skills/testing/render-handler.md +3 -0
- package/skills/testing/server-tree.md +1 -1
- package/skills/testing/setup.md +1 -1
- package/src/__internal.ts +0 -65
- package/src/browser/action-coordinator.ts +1 -1
- package/src/browser/action-fence.ts +10 -0
- package/src/browser/event-controller.ts +1 -83
- package/src/browser/navigation-store-handle.ts +3 -4
- package/src/browser/navigation-store.ts +0 -39
- package/src/browser/navigation-transaction.ts +0 -32
- package/src/browser/partial-update.ts +23 -84
- package/src/browser/prefetch/cache.ts +6 -45
- package/src/browser/prefetch/queue.ts +6 -3
- package/src/browser/rango-state.ts +2 -23
- package/src/browser/react/Link.tsx +0 -2
- package/src/browser/react/NavigationProvider.tsx +2 -1
- package/src/browser/react/ScrollRestoration.tsx +10 -6
- package/src/browser/react/filter-segment-order.ts +0 -2
- package/src/browser/react/index.ts +0 -45
- package/src/browser/react/location-state-shared.ts +0 -13
- package/src/browser/react/location-state.ts +0 -1
- package/src/browser/react/use-action.ts +6 -15
- package/src/browser/react/use-handle.ts +0 -5
- package/src/browser/react/use-link-status.ts +0 -4
- package/src/browser/react/use-navigation.ts +0 -3
- package/src/browser/react/use-params.ts +0 -2
- package/src/browser/react/use-router.ts +2 -1
- package/src/browser/react/use-search-params.ts +0 -5
- package/src/browser/react/use-segments.ts +0 -13
- package/src/browser/rsc-router.tsx +10 -3
- package/src/browser/server-action-bridge.ts +51 -3
- package/src/browser/types.ts +23 -5
- package/src/browser/validate-redirect-origin.ts +43 -16
- package/src/build/index.ts +8 -9
- package/src/build/route-trie.ts +46 -11
- package/src/build/route-types/param-extraction.ts +6 -3
- package/src/build/route-types/router-processing.ts +0 -8
- package/src/cache/cache-policy.ts +0 -54
- package/src/cache/cache-runtime.ts +48 -24
- package/src/cache/cache-scope.ts +0 -27
- package/src/cache/cache-tag.ts +0 -37
- package/src/cache/cf/cf-cache-store.ts +72 -45
- package/src/cache/cf/index.ts +0 -24
- package/src/cache/document-cache.ts +10 -36
- package/src/cache/handle-snapshot.ts +0 -40
- package/src/cache/index.ts +0 -27
- package/src/cache/memory-segment-store.ts +0 -52
- 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/types.ts +0 -98
- package/src/client.rsc.tsx +4 -22
- package/src/client.tsx +19 -32
- package/src/context-var.ts +12 -0
- package/src/defer.ts +196 -0
- package/src/deps/ssr.ts +0 -1
- package/src/handle.ts +2 -12
- 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 +6 -0
- package/src/host/pattern-matcher.ts +7 -50
- package/src/host/router.ts +1 -65
- package/src/host/testing.ts +0 -16
- package/src/host/types.ts +6 -2
- package/src/href-client.ts +0 -4
- package/src/index.rsc.ts +27 -2
- package/src/index.ts +7 -0
- package/src/internal-debug.ts +2 -4
- package/src/loader.rsc.ts +4 -15
- package/src/loader.ts +3 -9
- package/src/network-error-thrower.tsx +1 -6
- package/src/outlet-provider.tsx +1 -5
- package/src/prerender/param-hash.ts +10 -11
- package/src/prerender/store.ts +23 -30
- package/src/prerender.ts +34 -0
- package/src/redirect-origin.ts +100 -0
- package/src/root-error-boundary.tsx +1 -19
- package/src/route-content-wrapper.tsx +1 -44
- package/src/route-definition/dsl-helpers.ts +7 -19
- package/src/route-definition/helpers-types.ts +3 -3
- package/src/route-definition/redirect.ts +43 -9
- package/src/route-definition/resolve-handler-use.ts +6 -0
- package/src/route-map-builder.ts +0 -16
- package/src/router/content-negotiation.ts +0 -13
- package/src/router/error-handling.ts +12 -16
- package/src/router/find-match.ts +4 -31
- package/src/router/intercept-resolution.ts +10 -1
- package/src/router/lazy-includes.ts +1 -57
- package/src/router/loader-resolution.ts +25 -23
- package/src/router/logging.ts +0 -6
- package/src/router/manifest.ts +1 -25
- package/src/router/match-api.ts +0 -20
- package/src/router/match-context.ts +0 -22
- package/src/router/match-handlers.ts +0 -43
- package/src/router/match-middleware/background-revalidation.ts +0 -7
- package/src/router/match-middleware/cache-lookup.ts +96 -179
- package/src/router/match-middleware/cache-store.ts +0 -31
- package/src/router/match-middleware/intercept-resolution.ts +0 -22
- package/src/router/match-middleware/segment-resolution.ts +0 -22
- package/src/router/match-pipelines.ts +1 -42
- package/src/router/match-result.ts +1 -52
- package/src/router/metrics.ts +0 -34
- package/src/router/middleware-types.ts +0 -116
- package/src/router/middleware.ts +77 -60
- package/src/router/navigation-snapshot.ts +0 -51
- package/src/router/params-util.ts +23 -0
- package/src/router/pattern-matching.ts +5 -56
- package/src/router/prerender-match.ts +56 -51
- package/src/router/request-classification.ts +1 -38
- package/src/router/revalidation.ts +14 -62
- package/src/router/route-snapshot.ts +0 -1
- package/src/router/router-context.ts +0 -27
- package/src/router/router-interfaces.ts +10 -0
- package/src/router/segment-resolution/fresh.ts +25 -57
- package/src/router/segment-resolution/helpers.ts +34 -0
- package/src/router/segment-resolution/loader-cache.ts +35 -23
- package/src/router/segment-resolution/revalidation.ts +188 -283
- package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
- package/src/router/segment-resolution.ts +4 -1
- package/src/router/segment-wrappers.ts +0 -3
- package/src/router/telemetry-otel.ts +0 -20
- package/src/router/telemetry.ts +0 -22
- package/src/router/timeout.ts +0 -20
- package/src/router/trie-matching.ts +66 -45
- package/src/router/types.ts +1 -63
- package/src/router/url-params.ts +0 -5
- package/src/router.ts +8 -11
- package/src/rsc/handler-context.ts +1 -0
- package/src/rsc/handler.ts +20 -4
- package/src/rsc/helpers.ts +71 -3
- package/src/rsc/json-route-result.ts +38 -0
- package/src/rsc/origin-guard.ts +9 -15
- package/src/rsc/progressive-enhancement.ts +10 -1
- package/src/rsc/redirect-guard.ts +99 -0
- package/src/rsc/response-route-handler.ts +23 -18
- package/src/rsc/rsc-rendering.ts +2 -7
- package/src/rsc/runtime-warnings.ts +14 -0
- package/src/rsc/server-action.ts +34 -29
- package/src/rsc/types.ts +6 -3
- package/src/search-params.ts +0 -16
- package/src/segment-loader-promise.ts +14 -2
- package/src/segment-system.tsx +79 -88
- package/src/server/handle-store.ts +7 -24
- package/src/server/loader-registry.ts +5 -24
- package/src/server/request-context.ts +29 -92
- package/src/ssr/index.tsx +14 -14
- package/src/static-handler.ts +2 -27
- package/src/testing/cache-status.ts +44 -48
- package/src/testing/collect-handle.ts +1 -24
- package/src/testing/dispatch.ts +43 -6
- package/src/testing/e2e/index.ts +1 -22
- package/src/testing/e2e/matchers.ts +0 -16
- package/src/testing/flight-matchers.ts +0 -13
- package/src/testing/flight-normalize.ts +3 -30
- package/src/testing/flight.ts +46 -48
- package/src/testing/generated-routes.ts +1 -41
- package/src/testing/index.ts +1 -21
- package/src/testing/internal/context.ts +3 -45
- package/src/testing/internal/seed-vars.ts +0 -26
- package/src/testing/render-handler.ts +31 -61
- package/src/testing/render-route.tsx +75 -103
- package/src/testing/run-loader.ts +0 -96
- package/src/testing/run-middleware.ts +0 -26
- 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/error-types.ts +25 -89
- package/src/types/global-namespace.ts +4 -14
- package/src/types/handler-context.ts +28 -9
- package/src/types/index.ts +0 -10
- package/src/types/request-scope.ts +0 -19
- package/src/types/route-config.ts +6 -50
- package/src/types/route-entry.ts +0 -6
- package/src/types/segments.ts +0 -13
- package/src/urls/include-helper.ts +0 -4
- package/src/urls/index.ts +0 -6
- package/src/urls/path-helper-types.ts +2 -2
- package/src/urls/path-helper.ts +0 -54
- package/src/urls/urls-function.ts +0 -13
- package/src/use-loader.tsx +0 -186
- package/src/vite/discovery/bundle-postprocess.ts +2 -1
- package/src/vite/discovery/discover-routers.ts +28 -18
- package/src/vite/discovery/prerender-collection.ts +2 -4
- package/src/vite/discovery/state.ts +5 -0
- package/src/vite/discovery/virtual-module-codegen.ts +1 -11
- package/src/vite/plugin-types.ts +35 -9
- package/src/vite/plugins/cjs-to-esm.ts +0 -11
- package/src/vite/plugins/client-ref-dedup.ts +0 -11
- package/src/vite/plugins/client-ref-hashing.ts +0 -10
- package/src/vite/plugins/cloudflare-protocol-stub.ts +0 -20
- package/src/vite/plugins/expose-action-id.ts +2 -73
- package/src/vite/plugins/expose-id-utils.ts +0 -55
- package/src/vite/plugins/expose-ids/export-analysis.ts +0 -38
- package/src/vite/plugins/expose-ids/handler-transform.ts +0 -15
- package/src/vite/plugins/expose-ids/loader-transform.ts +0 -15
- package/src/vite/plugins/expose-ids/router-transform.ts +0 -13
- package/src/vite/plugins/expose-internal-ids.ts +10 -0
- package/src/vite/plugins/performance-tracks.ts +0 -3
- package/src/vite/plugins/refresh-cmd.ts +1 -1
- package/src/vite/plugins/use-cache-transform.ts +21 -46
- package/src/vite/plugins/version-injector.ts +0 -20
- package/src/vite/plugins/version-plugin.ts +1 -49
- package/src/vite/plugins/virtual-entries.ts +0 -15
- package/src/vite/rango.ts +2 -108
- package/src/vite/router-discovery.ts +9 -1
- package/src/vite/utils/ast-handler-extract.ts +0 -16
- package/src/vite/utils/bundle-analysis.ts +6 -13
- package/src/vite/utils/client-chunks.ts +0 -6
- package/src/vite/utils/forward-user-plugins.ts +0 -22
- package/src/vite/utils/manifest-utils.ts +0 -4
- package/src/vite/utils/package-resolution.ts +1 -73
- package/src/vite/utils/prerender-utils.ts +0 -35
- package/src/vite/utils/shared-utils.ts +3 -35
- package/src/browser/shallow.ts +0 -40
- package/src/handles/index.ts +0 -7
- package/src/router/middleware-cookies.ts +0 -55
package/src/defer.ts
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deferred handle values — "decide synchronously, resolve late".
|
|
3
|
+
*
|
|
4
|
+
* A handle is pushed from code that holds `ctx` (a route/layout handler), so the
|
|
5
|
+
* decision to push lands before the handles stream seals. But the value often
|
|
6
|
+
* isn't known there — it may come from a deep async component far from the
|
|
7
|
+
* handler. `ctx.use(Handle).defer()` reserves the handle's slot now (synchronous,
|
|
8
|
+
* so ordering and the pre-seal timing hold) and returns a resolver with the SAME
|
|
9
|
+
* signature as the push: you call it later, anywhere in the render, with the same
|
|
10
|
+
* value you would have passed to the push.
|
|
11
|
+
*
|
|
12
|
+
* const breadcrumb = ctx.use(Breadcrumbs); // (item) => void & .defer()
|
|
13
|
+
* const resolve = breadcrumb.defer({ timeoutMs: 5000, else: null });
|
|
14
|
+
* // deep async component, far from ctx:
|
|
15
|
+
* resolve({ label, href, content }); // identical call, just deferred
|
|
16
|
+
*
|
|
17
|
+
* Under the hood the reserved slot is a Promise the renderer `use()`s; RSC Flight
|
|
18
|
+
* streams it as a late row, so a deferred-aware consumer reading the handle
|
|
19
|
+
* (`useHandle`) sees that entry as a `Promise` until it resolves (see
|
|
20
|
+
* {@link DeferredHandleEntry}). The hazard that guards against bugs: a deferred
|
|
21
|
+
* slot whose resolver is never called would keep the Flight stream — and the HTTP
|
|
22
|
+
* response — open forever. So a deferred auto-resolves to `else` after `timeoutMs`
|
|
23
|
+
* (default {@link DEFAULT_DEFER_TIMEOUT_MS}) if the resolver is never called,
|
|
24
|
+
* degrading gracefully (and warning in dev) instead of hanging the request.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
/** Default auto-resolve window. Long enough for genuine deep async work, short
|
|
28
|
+
* enough that a forgotten resolve does not hang the response indefinitely. */
|
|
29
|
+
export const DEFAULT_DEFER_TIMEOUT_MS = 10_000;
|
|
30
|
+
|
|
31
|
+
/** Options for `ctx.use(Handle).defer()`. */
|
|
32
|
+
export interface DeferOptions<TData> {
|
|
33
|
+
/**
|
|
34
|
+
* Auto-resolve to `else` after this many ms if the resolver is never called,
|
|
35
|
+
* so a forgotten resolve cannot hold the Flight stream — and thus the HTTP
|
|
36
|
+
* response — open. Defaults to {@link DEFAULT_DEFER_TIMEOUT_MS}. `0` or
|
|
37
|
+
* `Infinity` disable the timeout intentionally (not recommended on a request
|
|
38
|
+
* path). Any other non-finite or negative value is treated as a mistake and
|
|
39
|
+
* falls back to the default rather than silently disabling the safety net.
|
|
40
|
+
* Named `timeoutMs` to match the router's `*Ms` duration convention.
|
|
41
|
+
*/
|
|
42
|
+
timeoutMs?: number;
|
|
43
|
+
/**
|
|
44
|
+
* Value the slot resolves to if the timeout fires before the resolver is
|
|
45
|
+
* called. Defaults to `undefined` (the deferred item is skipped/empty). For
|
|
46
|
+
* renderable handle content, `null` is the usual graceful fallback, so the
|
|
47
|
+
* type admits `null` even when `TData` does not.
|
|
48
|
+
*/
|
|
49
|
+
else?: TData | null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* The call signature shared by a handle push and the resolver returned by
|
|
54
|
+
* `.defer()`: a concrete value, a `Promise` of the value (Flight streams it as a
|
|
55
|
+
* late row), or a thunk returning a `Promise` (called immediately).
|
|
56
|
+
*/
|
|
57
|
+
export type HandlePushFn<TData> = (
|
|
58
|
+
data: TData | Promise<TData> | (() => Promise<TData>),
|
|
59
|
+
) => void;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* The push function returned by `ctx.use(Handle)`. Call it to push a value now,
|
|
63
|
+
* or call `.defer()` to reserve the slot now and resolve the value later (e.g.
|
|
64
|
+
* from a deep async component) with a timeout safety net.
|
|
65
|
+
*/
|
|
66
|
+
export type HandlePush<TData> = HandlePushFn<TData> & {
|
|
67
|
+
/**
|
|
68
|
+
* Reserve this handle's slot synchronously and return a resolver that is
|
|
69
|
+
* push-equal: it takes the same argument shapes as the push (value, Promise, or
|
|
70
|
+
* thunk) and behaves identically. Two things the resolver adds over a direct
|
|
71
|
+
* push: a timeout (if the resolver is never called, the slot auto-resolves to
|
|
72
|
+
* `options.else` after `options.timeoutMs`; calling the resolver cancels it),
|
|
73
|
+
* and — on the action/revalidation path only — a thunk it runs does NOT
|
|
74
|
+
* re-enter the deadlock-guard push-callback scope a direct push thunk gets,
|
|
75
|
+
* because a deferred resolver fires after the handler phase has closed.
|
|
76
|
+
*
|
|
77
|
+
* The reserved slot appears in the accumulated handle data as a pending
|
|
78
|
+
* `Promise` until it resolves (see {@link DeferredHandleEntry}); a
|
|
79
|
+
* deferred-aware consumer narrows thenable entries (`use()`/`await` + null
|
|
80
|
+
* check) before dereferencing.
|
|
81
|
+
*/
|
|
82
|
+
defer(options?: DeferOptions<TData>): HandlePushFn<TData>;
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* A handle entry a deferred-aware consumer may read from `useHandle`: either a
|
|
87
|
+
* resolved value, or a pending `Promise` that resolves to the value, to `else`,
|
|
88
|
+
* or (when no `else` was given) `undefined` on timeout. Reading code should treat
|
|
89
|
+
* thenable entries as such and narrow before dereferencing.
|
|
90
|
+
*/
|
|
91
|
+
export type DeferredHandleEntry<TData> =
|
|
92
|
+
| TData
|
|
93
|
+
| Promise<TData | null | undefined>;
|
|
94
|
+
|
|
95
|
+
// Internal: a timeout-bounded { promise, resolve }. Not part of the public API
|
|
96
|
+
// (the public surface is `ctx.use(Handle).defer()`); exported for `withDefer`
|
|
97
|
+
// and unit tests only. Resolves to `T`, the `else` fallback, or `undefined`.
|
|
98
|
+
export function createDeferred<T>(options?: {
|
|
99
|
+
timeoutMs?: number;
|
|
100
|
+
fallback?: T | null;
|
|
101
|
+
}): {
|
|
102
|
+
promise: Promise<T | null | undefined>;
|
|
103
|
+
resolve: (value: T | null | undefined) => void;
|
|
104
|
+
} {
|
|
105
|
+
let resolveInner!: (value: T | null | undefined) => void;
|
|
106
|
+
let settled = false;
|
|
107
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
108
|
+
|
|
109
|
+
const promise = new Promise<T | null | undefined>((resolve) => {
|
|
110
|
+
resolveInner = resolve;
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
const finish = (value: T | null | undefined): void => {
|
|
114
|
+
if (settled) return;
|
|
115
|
+
settled = true;
|
|
116
|
+
if (timer !== undefined) {
|
|
117
|
+
clearTimeout(timer);
|
|
118
|
+
timer = undefined;
|
|
119
|
+
}
|
|
120
|
+
resolveInner(value);
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
// 0 and Infinity are documented intentional disables. Any other non-finite or
|
|
124
|
+
// negative value (NaN, -1, a bad parsed config/env) is a mistake — fall back to
|
|
125
|
+
// the default rather than SILENTLY disabling the safety net, which would let a
|
|
126
|
+
// forgotten resolve hang the Flight stream and the response forever.
|
|
127
|
+
const requested = options?.timeoutMs ?? DEFAULT_DEFER_TIMEOUT_MS;
|
|
128
|
+
let ms: number;
|
|
129
|
+
if (requested === 0 || requested === Infinity) {
|
|
130
|
+
ms = requested;
|
|
131
|
+
} else if (Number.isFinite(requested) && requested > 0) {
|
|
132
|
+
ms = requested;
|
|
133
|
+
} else {
|
|
134
|
+
if (process.env.NODE_ENV !== "production") {
|
|
135
|
+
console.warn(
|
|
136
|
+
`[rango] defer(): invalid timeout ${String(requested)}; using the ` +
|
|
137
|
+
`${DEFAULT_DEFER_TIMEOUT_MS}ms default so the safety net stays on. ` +
|
|
138
|
+
`Use 0 or Infinity to disable the timeout intentionally.`,
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
ms = DEFAULT_DEFER_TIMEOUT_MS;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (ms > 0 && ms !== Infinity) {
|
|
145
|
+
timer = setTimeout(() => {
|
|
146
|
+
if (process.env.NODE_ENV !== "production") {
|
|
147
|
+
console.warn(
|
|
148
|
+
`[rango] A deferred handle value was not resolved within ${ms}ms; ` +
|
|
149
|
+
`resolving to the fallback so the response can flush. Call the ` +
|
|
150
|
+
`resolver from the component that produces the value, or raise timeoutMs.`,
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
finish(options?.fallback);
|
|
154
|
+
}, ms);
|
|
155
|
+
// Don't let a pending timer alone keep a Node process alive (no-op on workerd).
|
|
156
|
+
(timer as { unref?: () => void }).unref?.();
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return { promise, resolve: finish };
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Attach `.defer()` to a handle push function. The deferred slot is reserved by
|
|
164
|
+
* pushing the deferred promise through the same push (so ordering, sealing, and
|
|
165
|
+
* Flight streaming all reuse the existing path); the returned resolver settles it.
|
|
166
|
+
*/
|
|
167
|
+
export function withDefer<TData>(push: HandlePushFn<TData>): HandlePush<TData> {
|
|
168
|
+
const handlePush = push as HandlePush<TData>;
|
|
169
|
+
// Safe to mutate push in place: each ctx.use(Handle) call (request-context.ts,
|
|
170
|
+
// loader-resolution.ts) builds a fresh closure, so .defer never leaks across
|
|
171
|
+
// handles or requests.
|
|
172
|
+
handlePush.defer = (options) => {
|
|
173
|
+
const deferred = createDeferred<TData>({
|
|
174
|
+
timeoutMs: options?.timeoutMs,
|
|
175
|
+
fallback: options?.else,
|
|
176
|
+
});
|
|
177
|
+
// Reserve the slot now by pushing the pending promise (the renderer use()s it).
|
|
178
|
+
push(deferred.promise as Promise<TData>);
|
|
179
|
+
// The resolver is push-equal: a thunk is invoked immediately (as push does)
|
|
180
|
+
// and a Promise is adopted by the reserved slot. Calling it settles the slot
|
|
181
|
+
// and cancels the timeout — the timeout only fires if it is never called.
|
|
182
|
+
const resolveSlot = deferred.resolve as (
|
|
183
|
+
value: TData | Promise<TData>,
|
|
184
|
+
) => void;
|
|
185
|
+
return (data) => {
|
|
186
|
+
// The thunk runs without re-entering the push-callback scope a direct push
|
|
187
|
+
// thunk gets on the action/revalidation path (loader-resolution.ts): a
|
|
188
|
+
// deferred resolver fires from a deep component after the handler phase has
|
|
189
|
+
// closed, so there is no live deadlock-guard window to exempt.
|
|
190
|
+
resolveSlot(
|
|
191
|
+
typeof data === "function" ? (data as () => Promise<TData>)() : data,
|
|
192
|
+
);
|
|
193
|
+
};
|
|
194
|
+
};
|
|
195
|
+
return handlePush;
|
|
196
|
+
}
|
package/src/deps/ssr.ts
CHANGED
package/src/handle.ts
CHANGED
|
@@ -46,15 +46,10 @@ function defaultCollect<T>(segments: T[][]): T[] {
|
|
|
46
46
|
// Used by useHandle() to recover collect when handle is deserialized from RSC prop.
|
|
47
47
|
const collectRegistry = new Map<string, (segments: unknown[][]) => unknown>();
|
|
48
48
|
|
|
49
|
-
// Monotonic counter for runtime fallback ids (see createHandle).
|
|
50
|
-
//
|
|
51
|
-
// the process. Only used when no build id was injected (a bare unit test).
|
|
49
|
+
// Monotonic counter for runtime fallback ids (see createHandle). Only used
|
|
50
|
+
// when no build id was injected (a bare unit test).
|
|
52
51
|
let runtimeHandleIdCounter = 0;
|
|
53
52
|
|
|
54
|
-
/**
|
|
55
|
-
* Look up a collect function from the registry by handle $$id.
|
|
56
|
-
* Returns undefined if not registered (falls back to defaultCollect in useHandle).
|
|
57
|
-
*/
|
|
58
53
|
export function getCollectFn(
|
|
59
54
|
id: string,
|
|
60
55
|
): ((segments: unknown[][]) => unknown) | undefined {
|
|
@@ -127,8 +122,6 @@ export function createHandle<TData, TAccumulated = TData[]>(
|
|
|
127
122
|
collect ??
|
|
128
123
|
(defaultCollect as unknown as (segments: TData[][]) => TAccumulated);
|
|
129
124
|
|
|
130
|
-
// Register collect in module-level registry so useHandle() can recover it
|
|
131
|
-
// when the handle is deserialized from RSC props (toJSON strips collect).
|
|
132
125
|
collectRegistry.set(
|
|
133
126
|
handleId,
|
|
134
127
|
collectFn as (segments: unknown[][]) => unknown,
|
|
@@ -140,9 +133,6 @@ export function createHandle<TData, TAccumulated = TData[]>(
|
|
|
140
133
|
};
|
|
141
134
|
}
|
|
142
135
|
|
|
143
|
-
/**
|
|
144
|
-
* Type guard to check if a value is a Handle.
|
|
145
|
-
*/
|
|
146
136
|
export function isHandle(value: unknown): value is Handle<unknown, unknown> {
|
|
147
137
|
return (
|
|
148
138
|
typeof value === "object" &&
|
package/src/handles/MetaTags.tsx
CHANGED
|
@@ -97,24 +97,18 @@ function isPromise(value: unknown): value is Promise<unknown> {
|
|
|
97
97
|
return value !== null && typeof value === "object" && "then" in value;
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
-
/**
|
|
101
|
-
* Render a single meta descriptor as a React element.
|
|
102
|
-
*/
|
|
103
100
|
function renderMetaDescriptor(
|
|
104
101
|
descriptor: MetaDescriptorBase,
|
|
105
102
|
index: number,
|
|
106
103
|
): React.ReactNode {
|
|
107
|
-
// charset
|
|
108
104
|
if (hasCharSet(descriptor)) {
|
|
109
105
|
return <meta key="charSet" charSet={descriptor.charSet} />;
|
|
110
106
|
}
|
|
111
107
|
|
|
112
|
-
// title
|
|
113
108
|
if (hasTitle(descriptor)) {
|
|
114
109
|
return <title key="title">{descriptor.title}</title>;
|
|
115
110
|
}
|
|
116
111
|
|
|
117
|
-
// name + content (description, viewport, etc.)
|
|
118
112
|
if (hasNameContent(descriptor)) {
|
|
119
113
|
return (
|
|
120
114
|
<meta
|
|
@@ -125,7 +119,6 @@ function renderMetaDescriptor(
|
|
|
125
119
|
);
|
|
126
120
|
}
|
|
127
121
|
|
|
128
|
-
// property + content (Open Graph, etc.)
|
|
129
122
|
if (hasPropertyContent(descriptor)) {
|
|
130
123
|
return (
|
|
131
124
|
<meta
|
|
@@ -136,7 +129,6 @@ function renderMetaDescriptor(
|
|
|
136
129
|
);
|
|
137
130
|
}
|
|
138
131
|
|
|
139
|
-
// http-equiv + content
|
|
140
132
|
if (hasHttpEquivContent(descriptor)) {
|
|
141
133
|
return (
|
|
142
134
|
<meta
|
|
@@ -147,7 +139,6 @@ function renderMetaDescriptor(
|
|
|
147
139
|
);
|
|
148
140
|
}
|
|
149
141
|
|
|
150
|
-
// JSON-LD structured data
|
|
151
142
|
if (hasScriptLdJson(descriptor)) {
|
|
152
143
|
const json = JSON.stringify(descriptor["script:ld+json"]);
|
|
153
144
|
return (
|
|
@@ -159,7 +150,6 @@ function renderMetaDescriptor(
|
|
|
159
150
|
);
|
|
160
151
|
}
|
|
161
152
|
|
|
162
|
-
// Custom tagName (meta or link with arbitrary attributes)
|
|
163
153
|
if (hasTagName(descriptor)) {
|
|
164
154
|
const { tagName, ...rest } = descriptor;
|
|
165
155
|
if (tagName === "link") {
|
|
@@ -180,7 +170,6 @@ function renderMetaDescriptor(
|
|
|
180
170
|
}
|
|
181
171
|
}
|
|
182
172
|
|
|
183
|
-
// Fallback: treat as meta attributes
|
|
184
173
|
return (
|
|
185
174
|
<meta
|
|
186
175
|
key={`meta-fallback-${index}`}
|
|
@@ -189,9 +178,6 @@ function renderMetaDescriptor(
|
|
|
189
178
|
);
|
|
190
179
|
}
|
|
191
180
|
|
|
192
|
-
/**
|
|
193
|
-
* Wrapper component to resolve a Promise<MetaDescriptorBase> using use().
|
|
194
|
-
*/
|
|
195
181
|
function AsyncMetaTag({
|
|
196
182
|
promise,
|
|
197
183
|
index,
|
|
@@ -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
|
@@ -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
|