@rangojs/router 0.0.0-experimental.e9c0b2f2 → 0.0.0-experimental.ea9f40f2
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 +6 -10
- package/README.md +289 -938
- package/dist/bin/rango.js +271 -46
- package/dist/vite/index.js +673 -193
- package/package.json +10 -8
- package/skills/api-client/SKILL.md +1 -1
- package/skills/breadcrumbs/SKILL.md +31 -14
- package/skills/cache-guide/SKILL.md +5 -2
- package/skills/caching/SKILL.md +59 -4
- package/skills/catalog.json +271 -0
- package/skills/comparison/SKILL.md +50 -0
- package/skills/comparison/agents/openai.yaml +4 -0
- package/skills/comparison/references/framework-comparison.md +837 -0
- package/skills/composability/SKILL.md +83 -2
- package/skills/debug-manifest/SKILL.md +1 -1
- package/skills/defer-hydration/SKILL.md +235 -0
- package/skills/document-cache/SKILL.md +9 -1
- package/skills/fonts/SKILL.md +1 -1
- package/skills/handler-use/SKILL.md +8 -8
- package/skills/hooks/SKILL.md +54 -892
- package/skills/hooks/data.md +273 -0
- package/skills/hooks/handle-and-actions.md +103 -0
- package/skills/hooks/navigation.md +110 -0
- package/skills/hooks/outlets.md +41 -0
- package/skills/hooks/state.md +228 -0
- package/skills/hooks/urls.md +135 -0
- package/skills/host-router/SKILL.md +4 -4
- package/skills/i18n/SKILL.md +1 -1
- package/skills/intercept/SKILL.md +46 -14
- package/skills/layout/SKILL.md +27 -10
- package/skills/links/SKILL.md +1 -1
- package/skills/loader/SKILL.md +23 -1
- package/skills/middleware/SKILL.md +7 -3
- package/skills/migrate-nextjs/SKILL.md +167 -6
- package/skills/migrate-react-router/SKILL.md +59 -677
- package/skills/migrate-react-router/cloudflare-workers.md +129 -0
- package/skills/migrate-react-router/component-migration.md +196 -0
- package/skills/migrate-react-router/data-and-actions.md +225 -0
- package/skills/migrate-react-router/route-mapping.md +271 -0
- package/skills/mime-routes/SKILL.md +1 -1
- package/skills/observability/SKILL.md +9 -1
- package/skills/parallel/SKILL.md +23 -4
- package/skills/ppr/SKILL.md +622 -0
- package/skills/prerender/SKILL.md +28 -18
- package/skills/rango/SKILL.md +84 -25
- package/skills/response-routes/SKILL.md +15 -1
- package/skills/route/SKILL.md +71 -4
- package/skills/router-setup/SKILL.md +14 -3
- package/skills/scripts/SKILL.md +1 -1
- package/skills/server-actions/SKILL.md +3 -2
- package/skills/shell-manifest/SKILL.md +185 -0
- package/skills/streams-and-websockets/SKILL.md +1 -1
- package/skills/tailwind/SKILL.md +1 -1
- package/skills/testing/SKILL.md +2 -1
- package/skills/testing/handles.md +4 -2
- package/skills/testing/render-handler.md +15 -14
- package/skills/testing/reverse-and-types.md +8 -7
- package/skills/theme/SKILL.md +1 -1
- package/skills/typesafety/SKILL.md +45 -919
- package/skills/typesafety/env-and-bindings.md +254 -0
- package/skills/typesafety/generated-files-and-cli.md +335 -0
- package/skills/typesafety/params-and-search.md +153 -0
- package/skills/typesafety/route-types.md +209 -0
- package/skills/use-cache/SKILL.md +30 -3
- package/skills/vercel/SKILL.md +1 -1
- package/skills/view-transitions/SKILL.md +44 -1
- package/src/browser/event-controller.ts +62 -10
- package/src/browser/logging.ts +28 -0
- package/src/browser/merge-segment-loaders.ts +6 -4
- package/src/browser/navigation-bridge.ts +65 -16
- package/src/browser/navigation-client.ts +32 -2
- package/src/browser/navigation-store.ts +128 -14
- package/src/browser/network-error-handler.ts +34 -7
- package/src/browser/partial-update.ts +76 -17
- package/src/browser/prefetch/cache.ts +51 -11
- package/src/browser/prefetch/fetch.ts +59 -21
- package/src/browser/prefetch/queue.ts +19 -4
- package/src/browser/react/Link.tsx +13 -3
- package/src/browser/react/NavigationProvider.tsx +108 -4
- package/src/browser/response-adapter.ts +38 -9
- package/src/browser/rsc-router.tsx +54 -4
- package/src/browser/scroll-restoration.ts +7 -5
- package/src/browser/segment-reconciler.ts +31 -21
- package/src/browser/server-action-bridge.ts +22 -10
- package/src/browser/types.ts +54 -1
- package/src/build/generate-manifest.ts +155 -131
- package/src/build/index.ts +3 -1
- package/src/build/route-trie.ts +35 -7
- package/src/build/route-types/include-resolution.ts +347 -47
- package/src/build/runtime-discovery.ts +4 -1
- package/src/cache/cache-key-utils.ts +29 -0
- package/src/cache/cache-runtime.ts +262 -71
- package/src/cache/cache-scope.ts +2 -17
- package/src/cache/cache-tag.ts +60 -14
- package/src/cache/cf/cf-cache-store.ts +243 -20
- package/src/cache/document-cache.ts +54 -21
- package/src/cache/index.ts +1 -0
- package/src/cache/memory-segment-store.ts +110 -3
- package/src/cache/profile-registry.ts +15 -0
- package/src/cache/read-through-swr.ts +15 -1
- package/src/cache/segment-codec.ts +4 -4
- package/src/cache/shell-snapshot.ts +417 -0
- package/src/cache/types.ts +158 -0
- package/src/cache/vercel/vercel-cache-store.ts +401 -124
- package/src/client.rsc.tsx +0 -3
- package/src/client.tsx +0 -3
- package/src/cloudflare/tracing.ts +7 -8
- package/src/defer.ts +11 -22
- package/src/handle.ts +37 -15
- package/src/handles/MetaTags.tsx +16 -82
- package/src/handles/breadcrumbs.ts +12 -14
- package/src/handles/deferred-resolution.ts +127 -0
- package/src/handles/is-thenable.ts +7 -8
- package/src/handles/meta.ts +7 -44
- package/src/host/errors.ts +15 -0
- package/src/host/index.ts +1 -0
- package/src/index.rsc.ts +8 -2
- package/src/index.ts +19 -13
- package/src/internal-debug.ts +11 -8
- package/src/prerender.ts +17 -4
- package/src/redirect-origin.ts +14 -0
- package/src/render-error-thrower.tsx +20 -0
- package/src/route-content-wrapper.tsx +12 -5
- package/src/route-definition/dsl-helpers.ts +21 -32
- package/src/route-definition/helper-factories.ts +0 -2
- package/src/route-definition/helpers-types.ts +43 -43
- package/src/route-definition/index.ts +1 -2
- package/src/route-definition/resolve-handler-use.ts +0 -1
- package/src/route-definition/use-item-types.ts +3 -6
- package/src/route-map-builder.ts +41 -4
- package/src/route-types.ts +0 -5
- package/src/router/find-match.ts +86 -8
- package/src/router/instrument.ts +9 -4
- package/src/router/lazy-includes.ts +72 -12
- package/src/router/loader-resolution.ts +14 -2
- package/src/router/manifest.ts +56 -11
- package/src/router/match-api.ts +76 -32
- package/src/router/match-handlers.ts +181 -135
- package/src/router/match-middleware/background-revalidation.ts +40 -23
- package/src/router/match-middleware/cache-store.ts +39 -24
- package/src/router/match-result.ts +35 -15
- package/src/router/middleware.ts +64 -38
- package/src/router/navigation-snapshot.ts +7 -5
- package/src/router/parse-pattern.ts +115 -0
- package/src/router/pattern-matching.ts +53 -64
- package/src/router/prefetch-limits.ts +37 -0
- package/src/router/prerender-match.ts +11 -5
- package/src/router/preview-match.ts +3 -1
- package/src/router/request-classification.ts +23 -8
- package/src/router/route-snapshot.ts +14 -2
- package/src/router/router-context.ts +3 -1
- package/src/router/router-interfaces.ts +32 -1
- package/src/router/router-options.ts +30 -0
- package/src/router/segment-resolution/fresh.ts +39 -3
- package/src/router/segment-resolution/loader-cache.ts +93 -2
- package/src/router/segment-resolution/loader-mask.ts +60 -0
- package/src/router/segment-resolution/loader-snapshot.ts +259 -0
- package/src/router/segment-resolution/mask-nested.ts +83 -0
- package/src/router/segment-resolution/revalidation.ts +3 -0
- package/src/router/segment-resolution/view-transition-default.ts +35 -15
- package/src/router/substitute-pattern-params.ts +54 -35
- package/src/router/telemetry-otel.ts +6 -8
- package/src/router/telemetry.ts +9 -1
- package/src/router/tracing.ts +14 -5
- package/src/router/trie-matching.ts +19 -11
- package/src/router/url-params.ts +13 -0
- package/src/router.ts +47 -16
- package/src/rsc/full-payload.ts +70 -0
- package/src/rsc/handler.ts +60 -33
- package/src/rsc/manifest-init.ts +1 -1
- package/src/rsc/nonce.ts +10 -1
- package/src/rsc/progressive-enhancement.ts +61 -4
- package/src/rsc/redirect-guard.ts +2 -1
- package/src/rsc/rsc-rendering.ts +429 -37
- package/src/rsc/server-action.ts +25 -2
- package/src/rsc/shell-capture.ts +1190 -0
- package/src/rsc/shell-serve.ts +181 -0
- package/src/rsc/transition-gate.ts +89 -0
- package/src/rsc/types.ts +30 -0
- package/src/segment-loader-promise.ts +18 -0
- package/src/segment-system.tsx +149 -14
- package/src/server/context.ts +67 -9
- package/src/server/cookie-store.ts +73 -1
- package/src/server/loader-registry.ts +13 -1
- package/src/server/request-context.ts +169 -10
- package/src/ssr/index.tsx +462 -178
- package/src/ssr/inject-rsc-eager.ts +167 -0
- package/src/ssr/ssr-root.tsx +228 -0
- package/src/testing/collect-handle.ts +14 -8
- package/src/testing/dispatch.ts +152 -40
- package/src/testing/generated-routes.ts +27 -11
- package/src/testing/index.ts +6 -0
- package/src/testing/render-handler.ts +14 -0
- package/src/testing/render-route.tsx +13 -10
- package/src/testing/run-transition-when.ts +164 -0
- package/src/theme/ThemeProvider.tsx +36 -26
- package/src/types/handler-context.ts +1 -1
- package/src/types/index.ts +2 -0
- package/src/types/route-config.ts +19 -7
- package/src/types/segments.ts +100 -0
- package/src/urls/include-helper.ts +10 -8
- package/src/urls/include-provider.ts +71 -0
- package/src/urls/index.ts +1 -0
- package/src/urls/path-helper-types.ts +44 -12
- package/src/urls/path-helper.ts +5 -0
- package/src/urls/pattern-types.ts +36 -0
- package/src/urls/type-extraction.ts +43 -18
- package/src/urls/urls-function.ts +0 -1
- package/src/vercel/tracing.ts +7 -7
- package/src/vite/discovery/dev-prerender-cache.ts +117 -0
- package/src/vite/discovery/discover-routers.ts +1 -1
- package/src/vite/discovery/discovery-errors.ts +61 -0
- package/src/vite/index.ts +7 -0
- package/src/vite/inject-client-debug.ts +88 -0
- package/src/vite/plugins/vercel-output.ts +114 -25
- package/src/vite/plugins/version-injector.ts +22 -7
- package/src/vite/plugins/virtual-entries.ts +80 -22
- package/src/vite/rango.ts +29 -19
- package/src/vite/router-discovery.ts +171 -43
- package/src/vite/utils/prerender-utils.ts +17 -4
- package/src/vite/utils/shared-utils.ts +47 -0
- package/src/network-error-thrower.tsx +0 -18
|
@@ -17,6 +17,11 @@ import type { TrailingSlashMode } from "../types.js";
|
|
|
17
17
|
import { createRouteHelpers } from "../route-definition.js";
|
|
18
18
|
import MapRootLayout from "../server/root-layout.js";
|
|
19
19
|
import { collectFallbackClientRefs } from "./collect-fallback-refs.js";
|
|
20
|
+
import {
|
|
21
|
+
isIncludeProvider,
|
|
22
|
+
resolveIncludeModule,
|
|
23
|
+
type IncludeProvider,
|
|
24
|
+
} from "../urls/include-provider.js";
|
|
20
25
|
|
|
21
26
|
/**
|
|
22
27
|
* Node in the prefix tree
|
|
@@ -61,13 +66,32 @@ export interface GeneratedManifest {
|
|
|
61
66
|
// Merge tracked nested includes into `target`. Multiple includes can share a
|
|
62
67
|
// fullPrefix (e.g. include("/", a), include("/", b)) — concat their routes and
|
|
63
68
|
// Object.assign children rather than overwrite.
|
|
64
|
-
function mergeIncludeNodes(
|
|
69
|
+
async function mergeIncludeNodes(
|
|
65
70
|
target: Record<string, PrefixTreeNode>,
|
|
66
71
|
includes: TrackedInclude[],
|
|
67
|
-
buildChild: (include: TrackedInclude) => PrefixTreeNode
|
|
68
|
-
): void {
|
|
72
|
+
buildChild: (include: TrackedInclude) => Promise<PrefixTreeNode>,
|
|
73
|
+
): Promise<void> {
|
|
69
74
|
for (const include of includes) {
|
|
70
|
-
|
|
75
|
+
let node: PrefixTreeNode;
|
|
76
|
+
try {
|
|
77
|
+
node = await buildChild(include);
|
|
78
|
+
} catch (err) {
|
|
79
|
+
// Discovery (build-time, and the dev trie-rebuild) populates the
|
|
80
|
+
// manifest / trie / generated types for the WHOLE app. A failing async
|
|
81
|
+
// include provider here — a broken import, a module that throws at eval —
|
|
82
|
+
// must HARD-FAIL, not be swallowed: swallowing produces a green build with
|
|
83
|
+
// the entire route group silently absent from the manifest/trie/types, so
|
|
84
|
+
// CI passes, the deploy ships, and every one of that group's URLs then
|
|
85
|
+
// 404s/500s in production. On main an eager include that threw failed the
|
|
86
|
+
// build loudly; the async form must keep that contract. Rethrow with the
|
|
87
|
+
// offending prefix so the failure is actionable. (Sibling isolation
|
|
88
|
+
// belongs at PER-REQUEST runtime — see find-match.ts — not at discovery.)
|
|
89
|
+
throw new Error(
|
|
90
|
+
`[@rangojs/router] Failed to resolve include at prefix "${include.fullPrefix}" ` +
|
|
91
|
+
`during route discovery: ${(err as Error)?.message ?? String(err)}`,
|
|
92
|
+
{ cause: err },
|
|
93
|
+
);
|
|
94
|
+
}
|
|
71
95
|
const existing = target[include.fullPrefix];
|
|
72
96
|
if (existing) {
|
|
73
97
|
existing.routes.push(...node.routes);
|
|
@@ -78,10 +102,10 @@ function mergeIncludeNodes(
|
|
|
78
102
|
}
|
|
79
103
|
}
|
|
80
104
|
|
|
81
|
-
function buildPrefixTreeNode(
|
|
105
|
+
async function buildPrefixTreeNode(
|
|
82
106
|
urlPrefix: string,
|
|
83
107
|
namePrefix: string | undefined,
|
|
84
|
-
|
|
108
|
+
patternsOrProvider: UrlPatterns<any> | IncludeProvider<any>,
|
|
85
109
|
routeManifest: Record<string, string>,
|
|
86
110
|
routeAncestry: Record<string, string[]>, // internal: feeds trie building, not exported
|
|
87
111
|
mountIndex: number,
|
|
@@ -92,7 +116,14 @@ function buildPrefixTreeNode(
|
|
|
92
116
|
passthroughRoutes?: string[],
|
|
93
117
|
responseTypeRoutes?: Record<string, string>,
|
|
94
118
|
routeSearchSchemas?: Record<string, Record<string, string>>,
|
|
95
|
-
): PrefixTreeNode {
|
|
119
|
+
): Promise<PrefixTreeNode> {
|
|
120
|
+
// Resolve an async include provider (`() => import("./routes")`) so its routes
|
|
121
|
+
// are walked into the build-time manifest/types/href. Runtime matching still
|
|
122
|
+
// defers the import via lazy-includes; this only runs during build/dev
|
|
123
|
+
// discovery, which is async.
|
|
124
|
+
const patterns: UrlPatterns<any> = isIncludeProvider(patternsOrProvider)
|
|
125
|
+
? resolveIncludeModule(await patternsOrProvider(), urlPrefix)
|
|
126
|
+
: patternsOrProvider;
|
|
96
127
|
if (visited.has(patterns)) {
|
|
97
128
|
console.warn(
|
|
98
129
|
`[@rangojs/router] Circular include detected at prefix "${urlPrefix}". Skipping.`,
|
|
@@ -106,118 +137,117 @@ function buildPrefixTreeNode(
|
|
|
106
137
|
};
|
|
107
138
|
}
|
|
108
139
|
visited.add(patterns);
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
140
|
+
try {
|
|
141
|
+
// Create context for running patterns with include tracking
|
|
142
|
+
const manifest = new Map<string, EntryData>();
|
|
143
|
+
const patternsMap = new Map<string, string>();
|
|
144
|
+
const patternsByPrefix = new Map<string, Map<string, string>>();
|
|
145
|
+
const trailingSlashMap = new Map<string, TrailingSlashMode>();
|
|
146
|
+
const searchSchemasMap = new Map<string, Record<string, string>>();
|
|
147
|
+
const trackedIncludes: TrackedInclude[] = [];
|
|
116
148
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
149
|
+
RangoContext.run(
|
|
150
|
+
{
|
|
151
|
+
manifest,
|
|
152
|
+
patterns: patternsMap,
|
|
153
|
+
patternsByPrefix,
|
|
154
|
+
trailingSlash: trailingSlashMap,
|
|
155
|
+
searchSchemas: searchSchemasMap,
|
|
156
|
+
namespace: "build",
|
|
157
|
+
parent: null,
|
|
158
|
+
counters: {},
|
|
159
|
+
mountIndex,
|
|
160
|
+
trackedIncludes, // Enable nested include tracking
|
|
161
|
+
},
|
|
162
|
+
() => {
|
|
163
|
+
const helpers = createRouteHelpers();
|
|
164
|
+
// Wrap in root layout for correct parent hierarchy (matches runtime)
|
|
165
|
+
helpers.layout(MapRootLayout, () => {
|
|
166
|
+
if (urlPrefix || namePrefix) {
|
|
167
|
+
return runWithPrefixes(urlPrefix, namePrefix, () => {
|
|
168
|
+
return patterns.handler() as AllUseItems[];
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
return patterns.handler() as AllUseItems[];
|
|
172
|
+
});
|
|
173
|
+
},
|
|
174
|
+
);
|
|
143
175
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
routes.push(name);
|
|
148
|
-
routeManifest[name] = pattern;
|
|
149
|
-
}
|
|
176
|
+
// Collect route names defined in this include (routes have prefixes applied)
|
|
177
|
+
const routes = [...patternsMap.keys()];
|
|
178
|
+
Object.assign(routeManifest, Object.fromEntries(patternsMap));
|
|
150
179
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
180
|
+
// Collect trailing slash config
|
|
181
|
+
if (routeTrailingSlash) {
|
|
182
|
+
for (const [name, mode] of trailingSlashMap.entries()) {
|
|
183
|
+
routeTrailingSlash[name] = mode;
|
|
184
|
+
}
|
|
155
185
|
}
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
186
|
+
if (routeSearchSchemas) {
|
|
187
|
+
for (const [name, schema] of searchSchemasMap.entries()) {
|
|
188
|
+
routeSearchSchemas[name] = schema;
|
|
189
|
+
}
|
|
160
190
|
}
|
|
161
|
-
}
|
|
162
191
|
|
|
163
|
-
|
|
164
|
-
|
|
192
|
+
// Capture ancestry from manifest entries' parent chains
|
|
193
|
+
captureAncestry(manifest, routeAncestry);
|
|
165
194
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
195
|
+
// Collect prerender route names and handler definitions from manifest entries
|
|
196
|
+
if (prerenderRoutes) {
|
|
197
|
+
for (const [name, entry] of manifest) {
|
|
198
|
+
if (entry.type === "route" && entry.isPrerender) {
|
|
199
|
+
prerenderRoutes.push(name);
|
|
200
|
+
if (prerenderDefs && entry.prerenderDef) {
|
|
201
|
+
prerenderDefs[name] = entry.prerenderDef;
|
|
202
|
+
}
|
|
203
|
+
if (passthroughRoutes && entry.isPassthrough === true) {
|
|
204
|
+
passthroughRoutes.push(name);
|
|
205
|
+
}
|
|
176
206
|
}
|
|
177
207
|
}
|
|
178
208
|
}
|
|
179
|
-
}
|
|
180
209
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
210
|
+
// Collect response type routes from manifest entries
|
|
211
|
+
if (responseTypeRoutes) {
|
|
212
|
+
for (const [name, entry] of manifest) {
|
|
213
|
+
if (entry.type === "route" && entry.responseType) {
|
|
214
|
+
responseTypeRoutes[name] = entry.responseType;
|
|
215
|
+
}
|
|
186
216
|
}
|
|
187
217
|
}
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
const children: Record<string, PrefixTreeNode> = {};
|
|
191
|
-
mergeIncludeNodes(children, trackedIncludes, (include) =>
|
|
192
|
-
buildPrefixTreeNode(
|
|
193
|
-
include.fullPrefix,
|
|
194
|
-
include.namePrefix,
|
|
195
|
-
include.patterns as UrlPatterns<any>,
|
|
196
|
-
routeManifest,
|
|
197
|
-
routeAncestry,
|
|
198
|
-
mountIndex,
|
|
199
|
-
visited,
|
|
200
|
-
routeTrailingSlash,
|
|
201
|
-
prerenderRoutes,
|
|
202
|
-
prerenderDefs,
|
|
203
|
-
passthroughRoutes,
|
|
204
|
-
responseTypeRoutes,
|
|
205
|
-
routeSearchSchemas,
|
|
206
|
-
),
|
|
207
|
-
);
|
|
208
218
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
219
|
+
const children: Record<string, PrefixTreeNode> = {};
|
|
220
|
+
await mergeIncludeNodes(children, trackedIncludes, (include) =>
|
|
221
|
+
buildPrefixTreeNode(
|
|
222
|
+
include.fullPrefix,
|
|
223
|
+
include.namePrefix,
|
|
224
|
+
include.patterns as UrlPatterns<any> | IncludeProvider<any>,
|
|
225
|
+
routeManifest,
|
|
226
|
+
routeAncestry,
|
|
227
|
+
mountIndex,
|
|
228
|
+
visited,
|
|
229
|
+
routeTrailingSlash,
|
|
230
|
+
prerenderRoutes,
|
|
231
|
+
prerenderDefs,
|
|
232
|
+
passthroughRoutes,
|
|
233
|
+
responseTypeRoutes,
|
|
234
|
+
routeSearchSchemas,
|
|
235
|
+
),
|
|
236
|
+
);
|
|
213
237
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
238
|
+
return {
|
|
239
|
+
staticPrefix: extractStaticPrefix(urlPrefix),
|
|
240
|
+
fullPrefix: urlPrefix,
|
|
241
|
+
namePrefix: namePrefix || undefined,
|
|
242
|
+
children,
|
|
243
|
+
routes,
|
|
244
|
+
};
|
|
245
|
+
} finally {
|
|
246
|
+
// Remove from visited so sibling branches can reuse the same patterns without
|
|
247
|
+
// false circular-include detection — and so a throwing handler (caught by the
|
|
248
|
+
// parent mergeIncludeNodes) does not leak this entry into the shared set.
|
|
249
|
+
visited.delete(patterns);
|
|
250
|
+
}
|
|
221
251
|
}
|
|
222
252
|
|
|
223
253
|
/**
|
|
@@ -261,7 +291,8 @@ export interface FullManifest extends GeneratedManifest {
|
|
|
261
291
|
* import { generateManifest } from "@rangojs/router/build";
|
|
262
292
|
* import { urlpatterns } from "./urls";
|
|
263
293
|
*
|
|
264
|
-
*
|
|
294
|
+
* // Async: awaits async include() providers (`() => import("./routes")`).
|
|
295
|
+
* const manifest = await generateManifest(urlpatterns);
|
|
265
296
|
* // Write to file for runtime use
|
|
266
297
|
* fs.writeFileSync(
|
|
267
298
|
* "src/generated/route-manifest.json",
|
|
@@ -269,15 +300,12 @@ export interface FullManifest extends GeneratedManifest {
|
|
|
269
300
|
* );
|
|
270
301
|
* ```
|
|
271
302
|
*/
|
|
272
|
-
export function generateManifest<TEnv>(
|
|
303
|
+
export async function generateManifest<TEnv>(
|
|
273
304
|
urlpatterns: UrlPatterns<TEnv, any>,
|
|
274
305
|
mountIndex: number = 0,
|
|
275
|
-
): GeneratedManifest {
|
|
276
|
-
const {
|
|
277
|
-
|
|
278
|
-
_prerenderDefs: __,
|
|
279
|
-
...publicManifest
|
|
280
|
-
} = generateManifestFull(urlpatterns, mountIndex);
|
|
306
|
+
): Promise<GeneratedManifest> {
|
|
307
|
+
const { _routeAncestry, _prerenderDefs, ...publicManifest } =
|
|
308
|
+
await generateManifestFull(urlpatterns, mountIndex);
|
|
281
309
|
return publicManifest;
|
|
282
310
|
}
|
|
283
311
|
|
|
@@ -288,7 +316,7 @@ export function generateManifest<TEnv>(
|
|
|
288
316
|
* @rangojs/router/build), manifest-init (direct import), and trie
|
|
289
317
|
* building. Not intended for external use.
|
|
290
318
|
*/
|
|
291
|
-
export function generateManifestFull<TEnv>(
|
|
319
|
+
export async function generateManifestFull<TEnv>(
|
|
292
320
|
urlpatterns: UrlPatterns<TEnv, any>,
|
|
293
321
|
mountIndex: number = 0,
|
|
294
322
|
options?: {
|
|
@@ -302,7 +330,7 @@ export function generateManifestFull<TEnv>(
|
|
|
302
330
|
*/
|
|
303
331
|
collectClientFallbackRef?: (refKey: string) => void;
|
|
304
332
|
},
|
|
305
|
-
): FullManifest {
|
|
333
|
+
): Promise<FullManifest> {
|
|
306
334
|
const routeManifest: Record<string, string> = {};
|
|
307
335
|
const routeAncestry: Record<string, string[]> = {};
|
|
308
336
|
const prefixTree: Record<string, PrefixTreeNode> = {};
|
|
@@ -356,17 +384,13 @@ export function generateManifestFull<TEnv>(
|
|
|
356
384
|
}
|
|
357
385
|
|
|
358
386
|
// Collect root-level routes and trailing slash config
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
const routeSearchSchemas: Record<string, Record<string, string>> = {};
|
|
367
|
-
for (const [name, schema] of searchSchemasMap.entries()) {
|
|
368
|
-
routeSearchSchemas[name] = schema;
|
|
369
|
-
}
|
|
387
|
+
Object.assign(routeManifest, Object.fromEntries(patternsMap));
|
|
388
|
+
const routeTrailingSlash: Record<string, string> =
|
|
389
|
+
Object.fromEntries(trailingSlashMap);
|
|
390
|
+
const routeSearchSchemas: Record<
|
|
391
|
+
string,
|
|
392
|
+
Record<string, string>
|
|
393
|
+
> = Object.fromEntries(searchSchemasMap);
|
|
370
394
|
|
|
371
395
|
// Capture ancestry from manifest entries' parent chains
|
|
372
396
|
captureAncestry(manifest, routeAncestry);
|
|
@@ -393,11 +417,11 @@ export function generateManifestFull<TEnv>(
|
|
|
393
417
|
|
|
394
418
|
// Shared visited set for cycle detection across all root-level includes.
|
|
395
419
|
const visited = new Set<unknown>();
|
|
396
|
-
mergeIncludeNodes(prefixTree, trackedIncludes, (include) =>
|
|
420
|
+
await mergeIncludeNodes(prefixTree, trackedIncludes, (include) =>
|
|
397
421
|
buildPrefixTreeNode(
|
|
398
422
|
include.fullPrefix,
|
|
399
423
|
include.namePrefix,
|
|
400
|
-
include.patterns as UrlPatterns<any>,
|
|
424
|
+
include.patterns as UrlPatterns<any> | IncludeProvider<any>,
|
|
401
425
|
routeManifest,
|
|
402
426
|
routeAncestry,
|
|
403
427
|
mountIndex,
|
|
@@ -441,14 +465,14 @@ export function generateManifestFull<TEnv>(
|
|
|
441
465
|
*
|
|
442
466
|
* @example
|
|
443
467
|
* ```typescript
|
|
444
|
-
* const code = generateManifestCode(urlpatterns);
|
|
468
|
+
* const code = await generateManifestCode(urlpatterns);
|
|
445
469
|
* fs.writeFileSync("src/generated/route-manifest.ts", code);
|
|
446
470
|
* ```
|
|
447
471
|
*/
|
|
448
|
-
export function generateManifestCode<TEnv>(
|
|
472
|
+
export async function generateManifestCode<TEnv>(
|
|
449
473
|
urlpatterns: UrlPatterns<TEnv, any>,
|
|
450
|
-
): string {
|
|
451
|
-
const manifest = generateManifest(urlpatterns);
|
|
474
|
+
): Promise<string> {
|
|
475
|
+
const manifest = await generateManifest(urlpatterns);
|
|
452
476
|
|
|
453
477
|
return `/**
|
|
454
478
|
* Auto-generated route manifest
|
package/src/build/index.ts
CHANGED
|
@@ -10,7 +10,9 @@
|
|
|
10
10
|
* import { generateManifest } from "@rangojs/router/build";
|
|
11
11
|
* import { urlpatterns } from "./urls";
|
|
12
12
|
*
|
|
13
|
-
*
|
|
13
|
+
* // Async: generateManifest awaits any async include() providers
|
|
14
|
+
* // (`() => import("./routes")`) so the split groups appear in the manifest.
|
|
15
|
+
* const manifest = await generateManifest(urlpatterns);
|
|
14
16
|
* console.log(manifest.routeManifest); // { home: "/", about: "/about", ... }
|
|
15
17
|
* ```
|
|
16
18
|
*/
|
package/src/build/route-trie.ts
CHANGED
|
@@ -62,8 +62,13 @@ export interface TrieNode {
|
|
|
62
62
|
p?: { n: string; c: TrieNode };
|
|
63
63
|
/** Suffix-param children keyed by suffix (e.g., ".html" → { n: "productId", c: ... }) */
|
|
64
64
|
xp?: Record<string, { n: string; c: TrieNode }>;
|
|
65
|
-
/**
|
|
66
|
-
|
|
65
|
+
/**
|
|
66
|
+
* Wildcard terminal: leaf + paramName (`pn`). `pn` is "*" for the bare `/*`
|
|
67
|
+
* form and the param name for a named catch-all (`:name+`/`:name*`). `w1`
|
|
68
|
+
* marks a one-or-more catch-all (`:name+`): the runtime walker then rejects
|
|
69
|
+
* the zero-segment/empty-remainder case. Absent `w1` is zero-or-more.
|
|
70
|
+
*/
|
|
71
|
+
w?: TrieLeaf & { pn: string; w1?: true };
|
|
67
72
|
}
|
|
68
73
|
|
|
69
74
|
/**
|
|
@@ -383,12 +388,35 @@ function insertSegments(
|
|
|
383
388
|
} else if (segment.type === "wildcard") {
|
|
384
389
|
// Wildcard consumes all remaining segments. Carry any params bound before
|
|
385
390
|
// the wildcard in pa so they zip correctly against paramValues at match.
|
|
386
|
-
|
|
391
|
+
// `pn` is "*" for the bare `/*` and the param name for a named catch-all;
|
|
392
|
+
// `w1` marks the one-or-more variant (`:name+`) so the walker rejects the
|
|
393
|
+
// empty-remainder case.
|
|
394
|
+
const wildLeaf: TrieLeaf & { pn: string; w1?: true } = {
|
|
387
395
|
...buildLeaf(leafBase, paramNames),
|
|
388
|
-
pn:
|
|
396
|
+
pn: segment.value,
|
|
397
|
+
...(segment.oneOrMore ? { w1: true as const } : {}),
|
|
389
398
|
};
|
|
390
|
-
const existing = node.w
|
|
391
|
-
|
|
392
|
-
|
|
399
|
+
const existing = node.w;
|
|
400
|
+
// Merge when there's no existing wildcard, when this is a response-type
|
|
401
|
+
// content-negotiation variant of the same catch-all (one side carries `rt`),
|
|
402
|
+
// or when it's the SAME catch-all identity (same param name + arity).
|
|
403
|
+
// Otherwise two DISTINCT catch-all forms (`/x/*` vs `/x/:p+`) would collide on
|
|
404
|
+
// the single wildcard slot with no non-lossy merge — so keep the first-declared
|
|
405
|
+
// (matching the regex matcher's declaration-order tiebreak) rather than let
|
|
406
|
+
// mergeLeaves' last-wins overwrite silently drop its `pn`/`w1` identity (which
|
|
407
|
+
// stranded the first route and fell through to a corrupt regex-fallback redirect).
|
|
408
|
+
const canMerge =
|
|
409
|
+
existing === undefined ||
|
|
410
|
+
Boolean(existing.rt) ||
|
|
411
|
+
Boolean(wildLeaf.rt) ||
|
|
412
|
+
(existing.pn === wildLeaf.pn &&
|
|
413
|
+
Boolean(existing.w1) === Boolean(wildLeaf.w1));
|
|
414
|
+
if (canMerge) {
|
|
415
|
+
const merged = mergeLeaves(
|
|
416
|
+
existing ? ({ ...existing } as TrieLeaf) : undefined,
|
|
417
|
+
wildLeaf,
|
|
418
|
+
);
|
|
419
|
+
node.w = merged as TrieLeaf & { pn: string; w1?: true };
|
|
420
|
+
}
|
|
393
421
|
}
|
|
394
422
|
}
|