@timber-js/app 0.2.0-alpha.4 → 0.2.0-alpha.41
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/LICENSE +8 -0
- package/dist/_chunks/{als-registry-B7DbZ2hS.js → als-registry-Ba7URUIn.js} +1 -1
- package/dist/_chunks/als-registry-Ba7URUIn.js.map +1 -0
- package/dist/_chunks/chunk-DYhsFzuS.js +33 -0
- package/dist/_chunks/debug-ECi_61pb.js +108 -0
- package/dist/_chunks/debug-ECi_61pb.js.map +1 -0
- package/dist/_chunks/define-cookie-BmKbSyp0.js +93 -0
- package/dist/_chunks/define-cookie-BmKbSyp0.js.map +1 -0
- package/dist/_chunks/error-boundary-BAN3751q.js +211 -0
- package/dist/_chunks/error-boundary-BAN3751q.js.map +1 -0
- package/dist/_chunks/{format-CwdaB0_2.js → format-cX7wzEp2.js} +2 -2
- package/dist/_chunks/{format-CwdaB0_2.js.map → format-cX7wzEp2.js.map} +1 -1
- package/dist/_chunks/{interception-BOoWmLUA.js → interception-D2djYaIm.js} +112 -77
- package/dist/_chunks/interception-D2djYaIm.js.map +1 -0
- package/dist/_chunks/{metadata-routes-Cjmvi3rQ.js → metadata-routes-BU684ls2.js} +1 -1
- package/dist/_chunks/{metadata-routes-Cjmvi3rQ.js.map → metadata-routes-BU684ls2.js.map} +1 -1
- package/dist/_chunks/{request-context-CZJi4CuK.js → request-context-BxYIJM24.js} +93 -69
- package/dist/_chunks/request-context-BxYIJM24.js.map +1 -0
- package/dist/_chunks/segment-context-C6byCyZU.js +69 -0
- package/dist/_chunks/segment-context-C6byCyZU.js.map +1 -0
- package/dist/_chunks/stale-reload-C0ValzG7.js +47 -0
- package/dist/_chunks/stale-reload-C0ValzG7.js.map +1 -0
- package/dist/_chunks/{tracing-Cwn7697K.js → tracing-CuXiCP5p.js} +17 -3
- package/dist/_chunks/{tracing-Cwn7697K.js.map → tracing-CuXiCP5p.js.map} +1 -1
- package/dist/_chunks/{use-query-states-D5KaffOK.js → use-query-states-BvW0TKDn.js} +1 -1
- package/dist/_chunks/{use-query-states-D5KaffOK.js.map → use-query-states-BvW0TKDn.js.map} +1 -1
- package/dist/_chunks/wrappers-C6J0nNji.js +331 -0
- package/dist/_chunks/wrappers-C6J0nNji.js.map +1 -0
- package/dist/adapters/compress-module.d.ts.map +1 -1
- package/dist/adapters/nitro.d.ts +17 -1
- package/dist/adapters/nitro.d.ts.map +1 -1
- package/dist/adapters/nitro.js +56 -13
- package/dist/adapters/nitro.js.map +1 -1
- package/dist/cache/fast-hash.d.ts +22 -0
- package/dist/cache/fast-hash.d.ts.map +1 -0
- package/dist/cache/index.d.ts +5 -2
- package/dist/cache/index.d.ts.map +1 -1
- package/dist/cache/index.js +88 -18
- package/dist/cache/index.js.map +1 -1
- package/dist/cache/register-cached-function.d.ts.map +1 -1
- package/dist/cache/singleflight.d.ts +18 -1
- package/dist/cache/singleflight.d.ts.map +1 -1
- package/dist/cache/timber-cache.d.ts.map +1 -1
- package/dist/client/error-boundary.d.ts +10 -1
- package/dist/client/error-boundary.d.ts.map +1 -1
- package/dist/client/error-boundary.js +1 -125
- package/dist/client/index.d.ts +3 -2
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +213 -93
- package/dist/client/index.js.map +1 -1
- package/dist/client/link.d.ts +22 -8
- package/dist/client/link.d.ts.map +1 -1
- package/dist/client/navigation-context.d.ts +2 -2
- package/dist/client/router.d.ts +25 -3
- package/dist/client/router.d.ts.map +1 -1
- package/dist/client/rsc-fetch.d.ts +23 -2
- package/dist/client/rsc-fetch.d.ts.map +1 -1
- package/dist/client/segment-cache.d.ts +1 -1
- package/dist/client/segment-cache.d.ts.map +1 -1
- package/dist/client/segment-context.d.ts +1 -1
- package/dist/client/segment-context.d.ts.map +1 -1
- package/dist/client/segment-merger.d.ts.map +1 -1
- package/dist/client/stale-reload.d.ts +15 -0
- package/dist/client/stale-reload.d.ts.map +1 -1
- package/dist/client/top-loader.d.ts +1 -1
- package/dist/client/top-loader.d.ts.map +1 -1
- package/dist/client/transition-root.d.ts +1 -1
- package/dist/client/transition-root.d.ts.map +1 -1
- package/dist/client/use-params.d.ts +2 -2
- package/dist/client/use-params.d.ts.map +1 -1
- package/dist/client/use-query-states.d.ts +1 -1
- package/dist/codec.d.ts +21 -0
- package/dist/codec.d.ts.map +1 -0
- package/dist/cookies/define-cookie.d.ts +33 -12
- package/dist/cookies/define-cookie.d.ts.map +1 -1
- package/dist/cookies/index.js +1 -83
- package/dist/fonts/css.d.ts +1 -0
- package/dist/fonts/css.d.ts.map +1 -1
- package/dist/fonts/local.d.ts +4 -2
- package/dist/fonts/local.d.ts.map +1 -1
- package/dist/index.d.ts +112 -35
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +635 -233
- package/dist/index.js.map +1 -1
- package/dist/params/define.d.ts +76 -0
- package/dist/params/define.d.ts.map +1 -0
- package/dist/params/index.d.ts +8 -0
- package/dist/params/index.d.ts.map +1 -0
- package/dist/params/index.js +104 -0
- package/dist/params/index.js.map +1 -0
- package/dist/plugins/adapter-build.d.ts.map +1 -1
- package/dist/plugins/build-manifest.d.ts.map +1 -1
- package/dist/plugins/client-chunks.d.ts +32 -0
- package/dist/plugins/client-chunks.d.ts.map +1 -0
- package/dist/plugins/dev-error-overlay.d.ts +26 -1
- package/dist/plugins/dev-error-overlay.d.ts.map +1 -1
- package/dist/plugins/entries.d.ts +7 -0
- package/dist/plugins/entries.d.ts.map +1 -1
- package/dist/plugins/fonts.d.ts +9 -1
- package/dist/plugins/fonts.d.ts.map +1 -1
- package/dist/plugins/mdx.d.ts +6 -0
- package/dist/plugins/mdx.d.ts.map +1 -1
- package/dist/plugins/routing.d.ts.map +1 -1
- package/dist/plugins/server-bundle.d.ts.map +1 -1
- package/dist/plugins/static-build.d.ts.map +1 -1
- package/dist/routing/codegen.d.ts +2 -2
- package/dist/routing/codegen.d.ts.map +1 -1
- package/dist/routing/index.js +1 -1
- package/dist/routing/scanner.d.ts.map +1 -1
- package/dist/routing/status-file-lint.d.ts +2 -1
- package/dist/routing/status-file-lint.d.ts.map +1 -1
- package/dist/routing/types.d.ts +6 -4
- package/dist/routing/types.d.ts.map +1 -1
- package/dist/rsc-runtime/rsc.d.ts +1 -1
- package/dist/rsc-runtime/rsc.d.ts.map +1 -1
- package/dist/rsc-runtime/ssr.d.ts +12 -0
- package/dist/rsc-runtime/ssr.d.ts.map +1 -1
- package/dist/search-params/codecs.d.ts +1 -1
- package/dist/search-params/define.d.ts +153 -0
- package/dist/search-params/define.d.ts.map +1 -0
- package/dist/search-params/index.d.ts +4 -5
- package/dist/search-params/index.d.ts.map +1 -1
- package/dist/search-params/index.js +3 -474
- package/dist/search-params/registry.d.ts +1 -1
- package/dist/search-params/wrappers.d.ts +53 -0
- package/dist/search-params/wrappers.d.ts.map +1 -0
- package/dist/server/access-gate.d.ts +4 -0
- package/dist/server/access-gate.d.ts.map +1 -1
- package/dist/server/action-client.d.ts.map +1 -1
- package/dist/server/action-encryption.d.ts +76 -0
- package/dist/server/action-encryption.d.ts.map +1 -0
- package/dist/server/action-handler.d.ts.map +1 -1
- package/dist/server/als-registry.d.ts +18 -4
- package/dist/server/als-registry.d.ts.map +1 -1
- package/dist/server/build-manifest.d.ts +2 -2
- package/dist/server/debug.d.ts +46 -15
- package/dist/server/debug.d.ts.map +1 -1
- package/dist/server/default-logger.d.ts +22 -0
- package/dist/server/default-logger.d.ts.map +1 -0
- package/dist/server/deny-renderer.d.ts.map +1 -1
- package/dist/server/early-hints.d.ts +13 -5
- package/dist/server/early-hints.d.ts.map +1 -1
- package/dist/server/error-boundary-wrapper.d.ts +4 -0
- package/dist/server/error-boundary-wrapper.d.ts.map +1 -1
- package/dist/server/flight-injection-state.d.ts +78 -0
- package/dist/server/flight-injection-state.d.ts.map +1 -0
- package/dist/server/flight-scripts.d.ts +39 -0
- package/dist/server/flight-scripts.d.ts.map +1 -0
- package/dist/server/flush.d.ts.map +1 -1
- package/dist/server/form-data.d.ts +29 -0
- package/dist/server/form-data.d.ts.map +1 -1
- package/dist/server/html-injectors.d.ts +5 -11
- package/dist/server/html-injectors.d.ts.map +1 -1
- package/dist/server/index.d.ts +4 -2
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +1975 -1649
- package/dist/server/index.js.map +1 -1
- package/dist/server/logger.d.ts +24 -7
- package/dist/server/logger.d.ts.map +1 -1
- package/dist/server/node-stream-transforms.d.ts +77 -0
- package/dist/server/node-stream-transforms.d.ts.map +1 -0
- package/dist/server/pipeline.d.ts +7 -4
- package/dist/server/pipeline.d.ts.map +1 -1
- package/dist/server/primitives.d.ts +30 -3
- package/dist/server/primitives.d.ts.map +1 -1
- package/dist/server/render-timeout.d.ts +51 -0
- package/dist/server/render-timeout.d.ts.map +1 -0
- package/dist/server/request-context.d.ts +65 -38
- package/dist/server/request-context.d.ts.map +1 -1
- package/dist/server/route-element-builder.d.ts +7 -0
- package/dist/server/route-element-builder.d.ts.map +1 -1
- package/dist/server/route-handler.d.ts.map +1 -1
- package/dist/server/route-matcher.d.ts +2 -2
- package/dist/server/route-matcher.d.ts.map +1 -1
- package/dist/server/rsc-entry/error-renderer.d.ts.map +1 -1
- package/dist/server/rsc-entry/helpers.d.ts +46 -3
- package/dist/server/rsc-entry/helpers.d.ts.map +1 -1
- package/dist/server/rsc-entry/index.d.ts +6 -1
- package/dist/server/rsc-entry/index.d.ts.map +1 -1
- package/dist/server/rsc-entry/rsc-payload.d.ts.map +1 -1
- package/dist/server/rsc-entry/rsc-stream.d.ts +9 -0
- package/dist/server/rsc-entry/rsc-stream.d.ts.map +1 -1
- package/dist/server/rsc-entry/ssr-renderer.d.ts.map +1 -1
- package/dist/server/slot-resolver.d.ts +1 -1
- package/dist/server/slot-resolver.d.ts.map +1 -1
- package/dist/server/ssr-entry.d.ts +22 -0
- package/dist/server/ssr-entry.d.ts.map +1 -1
- package/dist/server/ssr-render.d.ts +39 -21
- package/dist/server/ssr-render.d.ts.map +1 -1
- package/dist/server/tracing.d.ts +10 -0
- package/dist/server/tracing.d.ts.map +1 -1
- package/dist/server/tree-builder.d.ts +19 -12
- package/dist/server/tree-builder.d.ts.map +1 -1
- package/dist/server/types.d.ts +1 -3
- package/dist/server/types.d.ts.map +1 -1
- package/dist/server/version-skew.d.ts +61 -0
- package/dist/server/version-skew.d.ts.map +1 -0
- package/dist/server/waituntil-bridge.d.ts.map +1 -1
- package/dist/shared/merge-search-params.d.ts +22 -0
- package/dist/shared/merge-search-params.d.ts.map +1 -0
- package/dist/shims/navigation-client.d.ts +1 -1
- package/dist/shims/navigation-client.d.ts.map +1 -1
- package/dist/shims/navigation.d.ts +1 -1
- package/dist/shims/navigation.d.ts.map +1 -1
- package/dist/utils/state-machine.d.ts +80 -0
- package/dist/utils/state-machine.d.ts.map +1 -0
- package/package.json +17 -14
- package/src/adapters/compress-module.ts +24 -4
- package/src/adapters/nitro.ts +58 -9
- package/src/cache/fast-hash.ts +34 -0
- package/src/cache/index.ts +5 -2
- package/src/cache/register-cached-function.ts +7 -3
- package/src/cache/singleflight.ts +62 -4
- package/src/cache/timber-cache.ts +34 -26
- package/src/cli.ts +0 -0
- package/src/client/browser-entry.ts +94 -90
- package/src/client/error-boundary.tsx +18 -1
- package/src/client/index.ts +10 -1
- package/src/client/link.tsx +78 -19
- package/src/client/navigation-context.ts +2 -2
- package/src/client/router.ts +105 -60
- package/src/client/rsc-fetch.ts +63 -2
- package/src/client/segment-cache.ts +1 -1
- package/src/client/segment-context.ts +6 -1
- package/src/client/segment-merger.ts +2 -8
- package/src/client/stale-reload.ts +32 -6
- package/src/client/top-loader.tsx +10 -9
- package/src/client/transition-root.tsx +7 -1
- package/src/client/use-params.ts +3 -3
- package/src/client/use-query-states.ts +1 -1
- package/src/codec.ts +21 -0
- package/src/cookies/define-cookie.ts +69 -18
- package/src/fonts/css.ts +2 -1
- package/src/fonts/local.ts +7 -3
- package/src/index.ts +280 -85
- package/src/params/define.ts +260 -0
- package/src/params/index.ts +28 -0
- package/src/plugins/adapter-build.ts +6 -0
- package/src/plugins/build-manifest.ts +11 -0
- package/src/plugins/client-chunks.ts +65 -0
- package/src/plugins/dev-error-overlay.ts +70 -1
- package/src/plugins/dev-server.ts +38 -4
- package/src/plugins/entries.ts +12 -11
- package/src/plugins/fonts.ts +171 -19
- package/src/plugins/mdx.ts +9 -5
- package/src/plugins/routing.ts +40 -14
- package/src/plugins/server-bundle.ts +32 -1
- package/src/plugins/shims.ts +1 -1
- package/src/plugins/static-build.ts +8 -4
- package/src/routing/codegen.ts +109 -88
- package/src/routing/scanner.ts +55 -6
- package/src/routing/status-file-lint.ts +2 -1
- package/src/routing/types.ts +7 -4
- package/src/rsc-runtime/rsc.ts +2 -0
- package/src/rsc-runtime/ssr.ts +50 -0
- package/src/rsc-runtime/vendor-types.d.ts +7 -0
- package/src/search-params/codecs.ts +1 -1
- package/src/search-params/define.ts +504 -0
- package/src/search-params/index.ts +12 -18
- package/src/search-params/registry.ts +1 -1
- package/src/search-params/wrappers.ts +85 -0
- package/src/server/access-gate.tsx +40 -9
- package/src/server/action-client.ts +14 -5
- package/src/server/action-encryption.ts +144 -0
- package/src/server/action-handler.ts +19 -2
- package/src/server/als-registry.ts +18 -4
- package/src/server/build-manifest.ts +4 -4
- package/src/server/compress.ts +25 -7
- package/src/server/debug.ts +55 -17
- package/src/server/default-logger.ts +98 -0
- package/src/server/deny-renderer.ts +2 -1
- package/src/server/early-hints.ts +36 -15
- package/src/server/error-boundary-wrapper.ts +57 -14
- package/src/server/flight-injection-state.ts +152 -0
- package/src/server/flight-scripts.ts +59 -0
- package/src/server/flush.ts +2 -1
- package/src/server/form-data.ts +76 -0
- package/src/server/html-injectors.ts +103 -66
- package/src/server/index.ts +9 -4
- package/src/server/logger.ts +38 -35
- package/src/server/node-stream-transforms.ts +381 -0
- package/src/server/pipeline.ts +131 -39
- package/src/server/primitives.ts +47 -5
- package/src/server/render-timeout.ts +108 -0
- package/src/server/request-context.ts +112 -119
- package/src/server/route-element-builder.ts +106 -114
- package/src/server/route-handler.ts +2 -1
- package/src/server/route-matcher.ts +2 -2
- package/src/server/rsc-entry/error-renderer.ts +5 -3
- package/src/server/rsc-entry/helpers.ts +122 -3
- package/src/server/rsc-entry/index.ts +125 -49
- package/src/server/rsc-entry/rsc-payload.ts +52 -12
- package/src/server/rsc-entry/rsc-stream.ts +33 -8
- package/src/server/rsc-entry/ssr-renderer.ts +40 -13
- package/src/server/slot-resolver.ts +199 -210
- package/src/server/ssr-entry.ts +168 -22
- package/src/server/ssr-render.ts +289 -67
- package/src/server/tracing.ts +23 -0
- package/src/server/tree-builder.ts +91 -57
- package/src/server/types.ts +1 -3
- package/src/server/version-skew.ts +104 -0
- package/src/server/waituntil-bridge.ts +4 -1
- package/src/shared/merge-search-params.ts +48 -0
- package/src/shims/navigation-client.ts +1 -1
- package/src/shims/navigation.ts +1 -1
- package/src/utils/state-machine.ts +111 -0
- package/dist/_chunks/als-registry-B7DbZ2hS.js.map +0 -1
- package/dist/_chunks/debug-B4WUeqJ-.js +0 -75
- package/dist/_chunks/debug-B4WUeqJ-.js.map +0 -1
- package/dist/_chunks/interception-BOoWmLUA.js.map +0 -1
- package/dist/_chunks/request-context-CZJi4CuK.js.map +0 -1
- package/dist/_chunks/ssr-data-MjmprTmO.js +0 -88
- package/dist/_chunks/ssr-data-MjmprTmO.js.map +0 -1
- package/dist/_chunks/use-cookie-DX-l1_5E.js +0 -91
- package/dist/_chunks/use-cookie-DX-l1_5E.js.map +0 -1
- package/dist/client/error-boundary.js.map +0 -1
- package/dist/cookies/index.js.map +0 -1
- package/dist/plugins/dynamic-transform.d.ts +0 -72
- package/dist/plugins/dynamic-transform.d.ts.map +0 -1
- package/dist/search-params/analyze.d.ts +0 -54
- package/dist/search-params/analyze.d.ts.map +0 -1
- package/dist/search-params/builtin-codecs.d.ts +0 -105
- package/dist/search-params/builtin-codecs.d.ts.map +0 -1
- package/dist/search-params/create.d.ts +0 -106
- package/dist/search-params/create.d.ts.map +0 -1
- package/dist/search-params/index.js.map +0 -1
- package/dist/server/prerender.d.ts +0 -77
- package/dist/server/prerender.d.ts.map +0 -1
- package/dist/server/response-cache.d.ts +0 -53
- package/dist/server/response-cache.d.ts.map +0 -1
- package/src/plugins/dynamic-transform.ts +0 -161
- package/src/search-params/analyze.ts +0 -192
- package/src/search-params/builtin-codecs.ts +0 -228
- package/src/search-params/create.ts +0 -321
- package/src/server/prerender.ts +0 -139
- package/src/server/response-cache.ts +0 -277
|
@@ -26,6 +26,118 @@ import type { ManifestSegmentNode } from './route-matcher.js';
|
|
|
26
26
|
|
|
27
27
|
type CreateElementFn = (...args: unknown[]) => React.ReactElement;
|
|
28
28
|
|
|
29
|
+
// ─── Module Loading Helpers ─────────────────────────────────────────────────
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Load a module and extract its default export as a component function.
|
|
33
|
+
* Returns undefined if no default export exists.
|
|
34
|
+
*/
|
|
35
|
+
async function loadComponent(loader: {
|
|
36
|
+
load: () => Promise<unknown>;
|
|
37
|
+
}): Promise<((...args: unknown[]) => unknown) | undefined> {
|
|
38
|
+
const mod = (await loader.load()) as Record<string, unknown>;
|
|
39
|
+
return mod.default as ((...args: unknown[]) => unknown) | undefined;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Load and render the default.tsx fallback for a slot node.
|
|
44
|
+
* Returns null if the slot has no default.tsx or it has no default export.
|
|
45
|
+
*/
|
|
46
|
+
async function renderDefaultFallback(
|
|
47
|
+
slotNode: ManifestSegmentNode,
|
|
48
|
+
h: CreateElementFn
|
|
49
|
+
): Promise<React.ReactElement | null> {
|
|
50
|
+
if (!slotNode.default) return null;
|
|
51
|
+
const DefaultComp = await loadComponent(slotNode.default);
|
|
52
|
+
if (!DefaultComp) return null;
|
|
53
|
+
return h(DefaultComp, {});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// ─── Segment Tree Matching ──────────────────────────────────────────────────
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Find a matching child node for a URL segment name.
|
|
60
|
+
*
|
|
61
|
+
* Tries matches in priority order:
|
|
62
|
+
* 1. Static segment (exact name match)
|
|
63
|
+
* 2. Dynamic segment ([param])
|
|
64
|
+
* 3. Catch-all or optional-catch-all ([...param] / [[...param]])
|
|
65
|
+
* 4. Group children (transparent wrappers)
|
|
66
|
+
*
|
|
67
|
+
* Returns `{ node, consumesRest }` where `consumesRest` is true for catch-all
|
|
68
|
+
* segments that consume all remaining URL parts.
|
|
69
|
+
*/
|
|
70
|
+
function findMatchingChild(
|
|
71
|
+
children: ManifestSegmentNode[],
|
|
72
|
+
segmentName: string
|
|
73
|
+
): { node: ManifestSegmentNode; consumesRest: boolean } | null {
|
|
74
|
+
// 1. Static match
|
|
75
|
+
for (const child of children) {
|
|
76
|
+
if (child.segmentType === 'static' && child.segmentName === segmentName) {
|
|
77
|
+
return { node: child, consumesRest: false };
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// 2. Dynamic match
|
|
82
|
+
for (const child of children) {
|
|
83
|
+
if (child.segmentType === 'dynamic') {
|
|
84
|
+
return { node: child, consumesRest: false };
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// 3. Catch-all match — consumes all remaining segments
|
|
89
|
+
for (const child of children) {
|
|
90
|
+
if (child.segmentType === 'catch-all' || child.segmentType === 'optional-catch-all') {
|
|
91
|
+
return { node: child, consumesRest: true };
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// 4. Group children (transparent)
|
|
96
|
+
for (const child of children) {
|
|
97
|
+
if (child.segmentType === 'group') {
|
|
98
|
+
for (const groupChild of child.children ?? []) {
|
|
99
|
+
if (groupChild.segmentName === segmentName) {
|
|
100
|
+
return { node: groupChild, consumesRest: false };
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Walk a segment tree from `startNode`, matching each part in `parts` against
|
|
111
|
+
* child nodes. Returns the chain of matched nodes (including startNode) and the
|
|
112
|
+
* final node, or null if any part fails to match.
|
|
113
|
+
*/
|
|
114
|
+
function walkSegmentTree(
|
|
115
|
+
startNode: ManifestSegmentNode,
|
|
116
|
+
parts: { segmentName: string }[] | string[],
|
|
117
|
+
initialChain: ManifestSegmentNode[] = [startNode]
|
|
118
|
+
): { chain: ManifestSegmentNode[]; leaf: ManifestSegmentNode } | null {
|
|
119
|
+
const chain = [...initialChain];
|
|
120
|
+
let currentNode = startNode;
|
|
121
|
+
|
|
122
|
+
for (const part of parts) {
|
|
123
|
+
const segName = typeof part === 'string' ? part : part.segmentName;
|
|
124
|
+
const directChildren = currentNode.children ?? [];
|
|
125
|
+
const match = findMatchingChild(directChildren, segName);
|
|
126
|
+
|
|
127
|
+
if (!match) return null;
|
|
128
|
+
|
|
129
|
+
chain.push(match.node);
|
|
130
|
+
currentNode = match.node;
|
|
131
|
+
|
|
132
|
+
// Catch-all segments consume all remaining parts
|
|
133
|
+
if (match.consumesRest) break;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return { chain, leaf: currentNode };
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// ─── Slot Element Resolution ────────────────────────────────────────────────
|
|
140
|
+
|
|
29
141
|
/**
|
|
30
142
|
* Resolve the element for a parallel slot.
|
|
31
143
|
*
|
|
@@ -40,7 +152,6 @@ type CreateElementFn = (...args: unknown[]) => React.ReactElement;
|
|
|
40
152
|
export async function resolveSlotElement(
|
|
41
153
|
slotNode: ManifestSegmentNode,
|
|
42
154
|
match: RouteMatch,
|
|
43
|
-
paramsPromise: Promise<Record<string, string | string[]>>,
|
|
44
155
|
h: CreateElementFn,
|
|
45
156
|
interception?: InterceptionContext
|
|
46
157
|
): Promise<React.ReactElement | null> {
|
|
@@ -54,125 +165,46 @@ export async function resolveSlotElement(
|
|
|
54
165
|
: findSlotMatch(slotNode, match);
|
|
55
166
|
|
|
56
167
|
if (slotMatch) {
|
|
57
|
-
const
|
|
58
|
-
if (
|
|
59
|
-
const SlotPage = mod.default as (...args: unknown[]) => unknown;
|
|
60
|
-
|
|
168
|
+
const SlotPage = await loadComponent(slotMatch.page);
|
|
169
|
+
if (SlotPage) {
|
|
61
170
|
// Load default.tsx fallback for notFound() handling in the slot page.
|
|
62
171
|
// When a slot page calls notFound() or deny(), it should gracefully
|
|
63
172
|
// degrade to default.tsx or null — not crash the page. This matches
|
|
64
173
|
// Next.js behavior. See design/02-rendering-pipeline.md
|
|
65
174
|
// §"Slot Access Failure = Graceful Degradation"
|
|
66
|
-
|
|
67
|
-
if (slotNode.default) {
|
|
68
|
-
const defaultMod = (await slotNode.default.load()) as Record<string, unknown>;
|
|
69
|
-
const DefaultComp = defaultMod.default as ((...args: unknown[]) => unknown) | undefined;
|
|
70
|
-
if (DefaultComp) {
|
|
71
|
-
denyFallback = h(DefaultComp, { params: paramsPromise, searchParams: {} });
|
|
72
|
-
}
|
|
73
|
-
}
|
|
175
|
+
const denyFallback = await renderDefaultFallback(slotNode, h);
|
|
74
176
|
|
|
75
177
|
// Wrap the slot page to catch DenySignal (from notFound() or deny())
|
|
76
178
|
// at the component level. This prevents the signal from reaching the
|
|
77
179
|
// RSC onError callback and being tracked as a page-level denial, which
|
|
78
180
|
// would cause the pipeline to replace the entire successful SSR response
|
|
79
181
|
// with a deny page. Instead, the slot gracefully degrades.
|
|
80
|
-
const denyFallbackCapture = denyFallback;
|
|
81
182
|
const SafeSlotPage = async (props: Record<string, unknown>) => {
|
|
82
183
|
try {
|
|
83
184
|
return await (SlotPage as (props: Record<string, unknown>) => unknown)(props);
|
|
84
185
|
} catch (error) {
|
|
85
186
|
if (error instanceof DenySignal) {
|
|
86
|
-
return
|
|
187
|
+
return denyFallback;
|
|
87
188
|
}
|
|
88
189
|
throw error;
|
|
89
190
|
}
|
|
90
191
|
};
|
|
91
192
|
|
|
92
|
-
let element: React.ReactElement = h(SafeSlotPage, {
|
|
93
|
-
params: paramsPromise,
|
|
94
|
-
searchParams: {},
|
|
95
|
-
});
|
|
193
|
+
let element: React.ReactElement = h(SafeSlotPage, {});
|
|
96
194
|
|
|
97
195
|
// Wrap with error boundaries and layouts from intermediate slot segments
|
|
98
196
|
// (everything between slot root and leaf). Process innermost-first, same
|
|
99
197
|
// order as route-element-builder.ts handles main segments. The slot root
|
|
100
198
|
// (index 0) is handled separately after the access gate below.
|
|
101
|
-
|
|
102
|
-
const seg = slotMatch.chain[i];
|
|
103
|
-
|
|
104
|
-
// Error boundaries from this segment
|
|
105
|
-
element = await wrapSegmentWithErrorBoundaries(seg, element, h);
|
|
106
|
-
|
|
107
|
-
// Layout from this segment
|
|
108
|
-
if (seg.layout) {
|
|
109
|
-
const layoutMod = (await seg.layout.load()) as Record<string, unknown>;
|
|
110
|
-
if (layoutMod.default) {
|
|
111
|
-
const Layout = layoutMod.default as (...args: unknown[]) => unknown;
|
|
112
|
-
element = h(Layout, {
|
|
113
|
-
params: paramsPromise,
|
|
114
|
-
searchParams: {},
|
|
115
|
-
children: element,
|
|
116
|
-
});
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
}
|
|
199
|
+
element = await wrapWithIntermediateSegments(slotMatch.chain, element, h);
|
|
120
200
|
|
|
121
201
|
// Wrap in SlotAccessGate if slot root has access.ts.
|
|
122
202
|
// On denial: denied.tsx → default.tsx → null (graceful degradation).
|
|
123
203
|
// See design/04-authorization.md §"Slot-Level Auth".
|
|
124
|
-
|
|
125
|
-
const accessMod = (await slotNode.access.load()) as Record<string, unknown>;
|
|
126
|
-
const accessFn = accessMod.default as
|
|
127
|
-
| ((ctx: { params: Record<string, string | string[]>; searchParams: unknown }) => unknown)
|
|
128
|
-
| undefined;
|
|
129
|
-
if (accessFn) {
|
|
130
|
-
// Load denied.tsx fallback
|
|
131
|
-
let deniedFallback: React.ReactElement | null = null;
|
|
132
|
-
if (slotNode.denied) {
|
|
133
|
-
const deniedMod = (await slotNode.denied.load()) as Record<string, unknown>;
|
|
134
|
-
const DeniedComponent = deniedMod.default as
|
|
135
|
-
| ((...args: unknown[]) => unknown)
|
|
136
|
-
| undefined;
|
|
137
|
-
if (DeniedComponent) {
|
|
138
|
-
deniedFallback = h(DeniedComponent, {});
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
// Load default.tsx fallback
|
|
143
|
-
let defaultFallback: React.ReactElement | null = null;
|
|
144
|
-
if (slotNode.default) {
|
|
145
|
-
const defaultMod = (await slotNode.default.load()) as Record<string, unknown>;
|
|
146
|
-
const DefaultComp = defaultMod.default as ((...args: unknown[]) => unknown) | undefined;
|
|
147
|
-
if (DefaultComp) {
|
|
148
|
-
defaultFallback = h(DefaultComp, { params: paramsPromise, searchParams: {} });
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
const params = await paramsPromise;
|
|
153
|
-
element = h(SlotAccessGate, {
|
|
154
|
-
accessFn,
|
|
155
|
-
params,
|
|
156
|
-
searchParams: {},
|
|
157
|
-
deniedFallback,
|
|
158
|
-
defaultFallback,
|
|
159
|
-
children: element,
|
|
160
|
-
});
|
|
161
|
-
}
|
|
162
|
-
}
|
|
204
|
+
element = await wrapWithAccessGate(slotNode, element, h);
|
|
163
205
|
|
|
164
206
|
// Wrap with slot root's layout (outermost, outside access gate)
|
|
165
|
-
|
|
166
|
-
const layoutMod = (await slotNode.layout.load()) as Record<string, unknown>;
|
|
167
|
-
if (layoutMod.default) {
|
|
168
|
-
const Layout = layoutMod.default as (...args: unknown[]) => unknown;
|
|
169
|
-
element = h(Layout, {
|
|
170
|
-
params: paramsPromise,
|
|
171
|
-
searchParams: {},
|
|
172
|
-
children: element,
|
|
173
|
-
});
|
|
174
|
-
}
|
|
175
|
-
}
|
|
207
|
+
element = await wrapWithLayout(slotNode, element, h);
|
|
176
208
|
|
|
177
209
|
// Wrap with slot root's error boundaries (outermost)
|
|
178
210
|
element = await wrapSegmentWithErrorBoundaries(slotNode, element, h);
|
|
@@ -195,18 +227,81 @@ export async function resolveSlotElement(
|
|
|
195
227
|
}
|
|
196
228
|
|
|
197
229
|
// No matching page — render default.tsx fallback
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
230
|
+
return renderDefaultFallback(slotNode, h);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// ─── Element Wrapping Helpers ───────────────────────────────────────────────
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Wrap an element with error boundaries and layouts from intermediate
|
|
237
|
+
* slot segments (indices 1..n, skipping the slot root at index 0).
|
|
238
|
+
* Processes innermost-first to match route-element-builder.ts ordering.
|
|
239
|
+
*/
|
|
240
|
+
async function wrapWithIntermediateSegments(
|
|
241
|
+
chain: ManifestSegmentNode[],
|
|
242
|
+
element: React.ReactElement,
|
|
243
|
+
h: CreateElementFn
|
|
244
|
+
): Promise<React.ReactElement> {
|
|
245
|
+
for (let i = chain.length - 1; i > 0; i--) {
|
|
246
|
+
const seg = chain[i];
|
|
247
|
+
element = await wrapSegmentWithErrorBoundaries(seg, element, h);
|
|
248
|
+
element = await wrapWithLayout(seg, element, h);
|
|
204
249
|
}
|
|
250
|
+
return element;
|
|
251
|
+
}
|
|
205
252
|
|
|
206
|
-
|
|
207
|
-
|
|
253
|
+
/**
|
|
254
|
+
* Wrap an element with a segment's layout component, if present.
|
|
255
|
+
*/
|
|
256
|
+
async function wrapWithLayout(
|
|
257
|
+
node: ManifestSegmentNode,
|
|
258
|
+
element: React.ReactElement,
|
|
259
|
+
h: CreateElementFn
|
|
260
|
+
): Promise<React.ReactElement> {
|
|
261
|
+
if (!node.layout) return element;
|
|
262
|
+
const Layout = await loadComponent(node.layout);
|
|
263
|
+
if (!Layout) return element;
|
|
264
|
+
return h(Layout, { children: element });
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Wrap an element with a SlotAccessGate if the node has access.ts.
|
|
269
|
+
* On denial: denied.tsx → default.tsx → null (graceful degradation).
|
|
270
|
+
*/
|
|
271
|
+
async function wrapWithAccessGate(
|
|
272
|
+
slotNode: ManifestSegmentNode,
|
|
273
|
+
element: React.ReactElement,
|
|
274
|
+
h: CreateElementFn
|
|
275
|
+
): Promise<React.ReactElement> {
|
|
276
|
+
if (!slotNode.access) return element;
|
|
277
|
+
|
|
278
|
+
const accessFn = await loadComponent(slotNode.access);
|
|
279
|
+
if (!accessFn) return element;
|
|
280
|
+
|
|
281
|
+
// Pass the component (not pre-built element) so SlotAccessGate can
|
|
282
|
+
// forward DenySignal.data as dangerouslyPassData dynamically. See TIM-488.
|
|
283
|
+
let DeniedComponent: ((...args: unknown[]) => unknown) | null = null;
|
|
284
|
+
if (slotNode.denied) {
|
|
285
|
+
DeniedComponent = (await loadComponent(slotNode.denied)) ?? null;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// Extract slot name from the directory name (strip @ prefix)
|
|
289
|
+
const slotName = slotNode.segmentName?.replace(/^@/, '') ?? '';
|
|
290
|
+
|
|
291
|
+
const defaultFallback = await renderDefaultFallback(slotNode, h);
|
|
292
|
+
|
|
293
|
+
return h(SlotAccessGate, {
|
|
294
|
+
accessFn,
|
|
295
|
+
DeniedComponent,
|
|
296
|
+
slotName,
|
|
297
|
+
createElement: h,
|
|
298
|
+
defaultFallback,
|
|
299
|
+
children: element,
|
|
300
|
+
});
|
|
208
301
|
}
|
|
209
302
|
|
|
303
|
+
// ─── Slot Matching ──────────────────────────────────────────────────────────
|
|
304
|
+
|
|
210
305
|
/** Result of matching a slot's sub-tree against the current route. */
|
|
211
306
|
interface SlotMatchResult {
|
|
212
307
|
/** The page file at the matched leaf. */
|
|
@@ -267,74 +362,10 @@ function findSlotMatch(slotNode: ManifestSegmentNode, match: RouteMatch): SlotMa
|
|
|
267
362
|
return null;
|
|
268
363
|
}
|
|
269
364
|
|
|
270
|
-
// Walk the slot's children to match remaining URL segments
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
for (const seg of remainingSegments) {
|
|
275
|
-
const childName = seg.segmentName;
|
|
276
|
-
const directChildren = currentNode.children ?? [];
|
|
277
|
-
|
|
278
|
-
let found: ManifestSegmentNode | null = null;
|
|
279
|
-
for (const child of directChildren) {
|
|
280
|
-
// Exact static match
|
|
281
|
-
if (child.segmentType === 'static' && child.segmentName === childName) {
|
|
282
|
-
found = child;
|
|
283
|
-
break;
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
// Try dynamic segments if no static match
|
|
288
|
-
if (!found) {
|
|
289
|
-
for (const child of directChildren) {
|
|
290
|
-
if (child.segmentType === 'dynamic') {
|
|
291
|
-
found = child;
|
|
292
|
-
break;
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
// Try catch-all segments — these consume ALL remaining URL segments,
|
|
298
|
-
// so we break out of the outer loop immediately.
|
|
299
|
-
if (!found) {
|
|
300
|
-
for (const child of directChildren) {
|
|
301
|
-
if (child.segmentType === 'catch-all' || child.segmentType === 'optional-catch-all') {
|
|
302
|
-
found = child;
|
|
303
|
-
break;
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
if (found) {
|
|
307
|
-
chain.push(found);
|
|
308
|
-
currentNode = found;
|
|
309
|
-
break;
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
// Try group children (transparent)
|
|
314
|
-
if (!found) {
|
|
315
|
-
for (const child of directChildren) {
|
|
316
|
-
if (child.segmentType === 'group') {
|
|
317
|
-
for (const groupChild of child.children ?? []) {
|
|
318
|
-
if (groupChild.segmentName === childName) {
|
|
319
|
-
found = groupChild;
|
|
320
|
-
break;
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
if (found) break;
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
if (!found) {
|
|
329
|
-
// No matching child in slot tree — slot doesn't match this URL
|
|
330
|
-
return null;
|
|
331
|
-
}
|
|
332
|
-
chain.push(found);
|
|
333
|
-
currentNode = found;
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
if (currentNode.page) {
|
|
337
|
-
return { page: currentNode.page, chain };
|
|
365
|
+
// Walk the slot's children to match remaining URL segments
|
|
366
|
+
const result = walkSegmentTree(slotNode, remainingSegments);
|
|
367
|
+
if (result && result.leaf.page) {
|
|
368
|
+
return { page: result.leaf.page, chain: result.chain };
|
|
338
369
|
}
|
|
339
370
|
return null;
|
|
340
371
|
}
|
|
@@ -374,59 +405,17 @@ function findInterceptingMatch(
|
|
|
374
405
|
|
|
375
406
|
// Walk the intercepting child's sub-tree to match remaining target parts
|
|
376
407
|
const remaining = targetParts.slice(matchIdx + 1);
|
|
377
|
-
const chain: ManifestSegmentNode[] = [slotNode, child];
|
|
378
408
|
|
|
379
409
|
if (remaining.length === 0) {
|
|
380
410
|
if (child.page) {
|
|
381
|
-
return { page: child.page, chain };
|
|
411
|
+
return { page: child.page, chain: [slotNode, child] };
|
|
382
412
|
}
|
|
383
413
|
continue;
|
|
384
414
|
}
|
|
385
415
|
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
const children = currentNode.children ?? [];
|
|
390
|
-
let found: ManifestSegmentNode | null = null;
|
|
391
|
-
|
|
392
|
-
// Static match
|
|
393
|
-
for (const c of children) {
|
|
394
|
-
if (c.segmentType === 'static' && c.segmentName === part) {
|
|
395
|
-
found = c;
|
|
396
|
-
break;
|
|
397
|
-
}
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
// Dynamic match
|
|
401
|
-
if (!found) {
|
|
402
|
-
for (const c of children) {
|
|
403
|
-
if (c.segmentType === 'dynamic') {
|
|
404
|
-
found = c;
|
|
405
|
-
break;
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
// Catch-all match
|
|
411
|
-
if (!found) {
|
|
412
|
-
for (const c of children) {
|
|
413
|
-
if (c.segmentType === 'catch-all' || c.segmentType === 'optional-catch-all') {
|
|
414
|
-
found = c;
|
|
415
|
-
break;
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
if (!found) {
|
|
421
|
-
matched = false;
|
|
422
|
-
break;
|
|
423
|
-
}
|
|
424
|
-
chain.push(found);
|
|
425
|
-
currentNode = found;
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
if (matched && currentNode.page) {
|
|
429
|
-
return { page: currentNode.page, chain };
|
|
416
|
+
const result = walkSegmentTree(child, remaining, [slotNode, child]);
|
|
417
|
+
if (result && result.leaf.page) {
|
|
418
|
+
return { page: result.leaf.page, chain: result.chain };
|
|
430
419
|
}
|
|
431
420
|
}
|
|
432
421
|
|