@timber-js/app 0.2.0-alpha.98 → 0.2.0-alpha.99
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/actions-CQ8Z8VGL.js +1061 -0
- package/dist/_chunks/actions-CQ8Z8VGL.js.map +1 -0
- package/dist/_chunks/build-output-helper-DXnW0qjz.js +61 -0
- package/dist/_chunks/build-output-helper-DXnW0qjz.js.map +1 -0
- package/dist/_chunks/{define-Itxvcd7F.js → define-B-Q_UMOD.js} +19 -23
- package/dist/_chunks/define-B-Q_UMOD.js.map +1 -0
- package/dist/_chunks/{define-C77ScO0m.js → define-CfBPoJb0.js} +24 -7
- package/dist/_chunks/define-CfBPoJb0.js.map +1 -0
- package/dist/_chunks/define-cookie-BjpIt4UC.js +194 -0
- package/dist/_chunks/define-cookie-BjpIt4UC.js.map +1 -0
- package/dist/_chunks/{format-CYBGxKtc.js → format-Bcn-Iv1x.js} +1 -1
- package/dist/_chunks/{format-CYBGxKtc.js.map → format-Bcn-Iv1x.js.map} +1 -1
- package/dist/_chunks/handler-store-B-lqaGyh.js +54 -0
- package/dist/_chunks/handler-store-B-lqaGyh.js.map +1 -0
- package/dist/_chunks/logger-0m8MsKdc.js +291 -0
- package/dist/_chunks/logger-0m8MsKdc.js.map +1 -0
- package/dist/_chunks/merge-search-params-BphMdht_.js +122 -0
- package/dist/_chunks/merge-search-params-BphMdht_.js.map +1 -0
- package/dist/_chunks/navigation-root-BCYczjml.js +96 -0
- package/dist/_chunks/navigation-root-BCYczjml.js.map +1 -0
- package/dist/_chunks/registry-I2ss-lvy.js +20 -0
- package/dist/_chunks/registry-I2ss-lvy.js.map +1 -0
- package/dist/_chunks/router-ref-h3-UaCQv.js +28 -0
- package/dist/_chunks/router-ref-h3-UaCQv.js.map +1 -0
- package/dist/_chunks/{schema-bridge-C3xl_vfb.js → schema-bridge-Cxu4l-7p.js} +1 -1
- package/dist/_chunks/{schema-bridge-C3xl_vfb.js.map → schema-bridge-Cxu4l-7p.js.map} +1 -1
- package/dist/_chunks/{segment-context-fHFLF1PE.js → segment-context-Dx_OizxD.js} +1 -1
- package/dist/_chunks/{segment-context-fHFLF1PE.js.map → segment-context-Dx_OizxD.js.map} +1 -1
- package/dist/_chunks/{router-ref-C8OCm7g7.js → ssr-data-B4CdH7rE.js} +2 -26
- package/dist/_chunks/ssr-data-B4CdH7rE.js.map +1 -0
- package/dist/_chunks/{stale-reload-BX5gL1r-.js → stale-reload-Bab885FO.js} +1 -1
- package/dist/_chunks/{stale-reload-BX5gL1r-.js.map → stale-reload-Bab885FO.js.map} +1 -1
- package/dist/_chunks/tracing-C8V-YGsP.js +329 -0
- package/dist/_chunks/tracing-C8V-YGsP.js.map +1 -0
- package/dist/_chunks/{use-query-states-BiV5GJgm.js → use-query-states-B2XTqxDR.js} +3 -19
- package/dist/_chunks/use-query-states-B2XTqxDR.js.map +1 -0
- package/dist/_chunks/{use-params-IOPu7E8t.js → use-segment-params-BkpKAQ7D.js} +9 -95
- package/dist/_chunks/use-segment-params-BkpKAQ7D.js.map +1 -0
- package/dist/_chunks/{walkers-VOXgavMF.js → walkers-Tg0Alwcg.js} +6 -3
- package/dist/_chunks/walkers-Tg0Alwcg.js.map +1 -0
- package/dist/_chunks/{dev-warnings-DpGRGoDi.js → warnings-Cg47l5sk.js} +3 -3
- package/dist/_chunks/warnings-Cg47l5sk.js.map +1 -0
- package/dist/adapters/build-output-helper.d.ts +28 -0
- package/dist/adapters/build-output-helper.d.ts.map +1 -0
- package/dist/adapters/cloudflare.d.ts.map +1 -1
- package/dist/adapters/cloudflare.js +8 -28
- package/dist/adapters/cloudflare.js.map +1 -1
- package/dist/adapters/nitro.d.ts.map +1 -1
- package/dist/adapters/nitro.js +8 -26
- package/dist/adapters/nitro.js.map +1 -1
- package/dist/adapters/shared.d.ts +16 -0
- package/dist/adapters/shared.d.ts.map +1 -0
- package/dist/cache/index.js +9 -2
- package/dist/cache/index.js.map +1 -1
- package/dist/cache/timber-cache.d.ts.map +1 -1
- package/dist/client/error-boundary.js +2 -1
- package/dist/client/error-boundary.js.map +1 -1
- package/dist/client/form.d.ts +10 -24
- package/dist/client/form.d.ts.map +1 -1
- package/dist/client/index.d.ts +1 -5
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +40 -90
- package/dist/client/index.js.map +1 -1
- package/dist/client/internal.d.ts +2 -1
- package/dist/client/internal.d.ts.map +1 -1
- package/dist/client/internal.js +81 -7
- package/dist/client/internal.js.map +1 -1
- package/dist/client/rsc-fetch.d.ts.map +1 -1
- package/dist/client/state.d.ts +1 -1
- package/dist/client/use-cookie.d.ts +8 -0
- package/dist/client/use-cookie.d.ts.map +1 -1
- package/dist/client/{use-params.d.ts → use-segment-params.d.ts} +1 -1
- package/dist/client/use-segment-params.d.ts.map +1 -0
- package/dist/codec.d.ts +1 -1
- package/dist/codec.d.ts.map +1 -1
- package/dist/codec.js +2 -2
- package/dist/config-types.d.ts +28 -0
- package/dist/config-types.d.ts.map +1 -1
- package/dist/cookies/define-cookie.d.ts +87 -35
- package/dist/cookies/define-cookie.d.ts.map +1 -1
- package/dist/cookies/index.d.ts +2 -1
- package/dist/cookies/index.d.ts.map +1 -1
- package/dist/cookies/index.js +48 -2
- package/dist/cookies/index.js.map +1 -0
- package/dist/cookies/json-cookie.d.ts +64 -0
- package/dist/cookies/json-cookie.d.ts.map +1 -0
- package/dist/cookies/validation.d.ts +46 -0
- package/dist/cookies/validation.d.ts.map +1 -0
- package/dist/{plugins/dev-404-page.d.ts → dev-tools/404-page.d.ts} +1 -1
- package/dist/dev-tools/404-page.d.ts.map +1 -0
- package/dist/{plugins/dev-browser-logs.d.ts → dev-tools/browser-logs.d.ts} +1 -1
- package/dist/dev-tools/browser-logs.d.ts.map +1 -0
- package/dist/{plugins/dev-error-page.d.ts → dev-tools/error-page.d.ts} +2 -2
- package/dist/dev-tools/error-page.d.ts.map +1 -0
- package/dist/{server/dev-holding-server.d.ts → dev-tools/holding-server.d.ts} +1 -1
- package/dist/dev-tools/holding-server.d.ts.map +1 -0
- package/dist/dev-tools/index.d.ts +31 -0
- package/dist/dev-tools/index.d.ts.map +1 -0
- package/dist/{server/dev-span-processor.d.ts → dev-tools/instrumentation.d.ts} +26 -6
- package/dist/dev-tools/instrumentation.d.ts.map +1 -0
- package/dist/{server/dev-logger.d.ts → dev-tools/logger.d.ts} +1 -1
- package/dist/dev-tools/logger.d.ts.map +1 -0
- package/dist/{plugins/dev-logs.d.ts → dev-tools/logs.d.ts} +1 -1
- package/dist/dev-tools/logs.d.ts.map +1 -0
- package/dist/{plugins/dev-error-overlay.d.ts → dev-tools/overlay.d.ts} +3 -12
- package/dist/dev-tools/overlay.d.ts.map +1 -0
- package/dist/dev-tools/stack-classifier.d.ts +34 -0
- package/dist/dev-tools/stack-classifier.d.ts.map +1 -0
- package/dist/{plugins/dev-terminal-error.d.ts → dev-tools/terminal.d.ts} +2 -2
- package/dist/dev-tools/terminal.d.ts.map +1 -0
- package/dist/{server/dev-warnings.d.ts → dev-tools/warnings.d.ts} +1 -1
- package/dist/dev-tools/warnings.d.ts.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +97 -72
- package/dist/index.js.map +1 -1
- package/dist/plugin-context.d.ts +1 -1
- package/dist/plugin-context.d.ts.map +1 -1
- package/dist/plugins/adapter-build.d.ts.map +1 -1
- package/dist/routing/convention-lint.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.map +1 -1
- package/dist/search-params/define.d.ts +25 -7
- package/dist/search-params/define.d.ts.map +1 -1
- package/dist/search-params/index.js +5 -3
- package/dist/search-params/index.js.map +1 -1
- package/dist/search-params/wrappers.d.ts +2 -2
- package/dist/search-params/wrappers.d.ts.map +1 -1
- package/dist/segment-params/define.d.ts +23 -6
- package/dist/segment-params/define.d.ts.map +1 -1
- package/dist/segment-params/index.js +1 -1
- package/dist/server/access-gate.d.ts +4 -3
- package/dist/server/access-gate.d.ts.map +1 -1
- package/dist/server/action-handler.d.ts +15 -6
- package/dist/server/action-handler.d.ts.map +1 -1
- package/dist/server/als-registry.d.ts +5 -5
- package/dist/server/als-registry.d.ts.map +1 -1
- package/dist/server/asset-headers.d.ts +1 -15
- package/dist/server/asset-headers.d.ts.map +1 -1
- package/dist/server/cookie-context.d.ts +170 -0
- package/dist/server/cookie-context.d.ts.map +1 -0
- package/dist/server/cookie-parsing.d.ts +51 -0
- package/dist/server/cookie-parsing.d.ts.map +1 -0
- package/dist/server/deny-boundary.d.ts +90 -0
- package/dist/server/deny-boundary.d.ts.map +1 -0
- package/dist/server/deny-renderer.d.ts.map +1 -1
- package/dist/server/early-hints-sender.d.ts.map +1 -1
- package/dist/server/index.d.ts +5 -4
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +4 -149
- package/dist/server/index.js.map +1 -1
- package/dist/server/internal.d.ts +6 -4
- package/dist/server/internal.d.ts.map +1 -1
- package/dist/server/internal.js +261 -408
- package/dist/server/internal.js.map +1 -1
- package/dist/server/logger.d.ts +14 -0
- package/dist/server/logger.d.ts.map +1 -1
- package/dist/server/middleware-runner.d.ts +17 -0
- package/dist/server/middleware-runner.d.ts.map +1 -1
- package/dist/server/param-coercion.d.ts +26 -0
- package/dist/server/param-coercion.d.ts.map +1 -0
- package/dist/server/pipeline-helpers.d.ts +14 -7
- package/dist/server/pipeline-helpers.d.ts.map +1 -1
- package/dist/server/pipeline-outcome.d.ts +49 -0
- package/dist/server/pipeline-outcome.d.ts.map +1 -0
- package/dist/server/pipeline-phases.d.ts +4 -49
- package/dist/server/pipeline-phases.d.ts.map +1 -1
- package/dist/server/pipeline.d.ts +0 -2
- package/dist/server/pipeline.d.ts.map +1 -1
- package/dist/server/request-context.d.ts +22 -159
- package/dist/server/request-context.d.ts.map +1 -1
- package/dist/server/route-element-builder.d.ts.map +1 -1
- package/dist/server/rsc-entry/action-middleware-runner.d.ts +66 -0
- package/dist/server/rsc-entry/action-middleware-runner.d.ts.map +1 -0
- package/dist/server/rsc-entry/helpers.d.ts +1 -1
- package/dist/server/rsc-entry/helpers.d.ts.map +1 -1
- package/dist/server/rsc-entry/index.d.ts.map +1 -1
- package/dist/server/rsc-entry/render-route.d.ts +50 -0
- package/dist/server/rsc-entry/render-route.d.ts.map +1 -0
- package/dist/server/rsc-entry/wrap-action-dispatch.d.ts +59 -14
- package/dist/server/rsc-entry/wrap-action-dispatch.d.ts.map +1 -1
- package/dist/server/state-tree-diff.d.ts.map +1 -1
- package/dist/server/tracing.d.ts +1 -1
- package/dist/server/tracing.d.ts.map +1 -1
- package/dist/server/tree-builder.d.ts +45 -16
- package/dist/server/tree-builder.d.ts.map +1 -1
- package/dist/server/types.d.ts +48 -0
- package/dist/server/types.d.ts.map +1 -1
- package/dist/server/utils/escape-html.d.ts +14 -0
- package/dist/server/utils/escape-html.d.ts.map +1 -0
- package/dist/shims/headers.d.ts +2 -2
- package/dist/shims/headers.d.ts.map +1 -1
- package/dist/shims/navigation-client.d.ts +3 -1
- package/dist/shims/navigation-client.d.ts.map +1 -1
- package/dist/shims/navigation.d.ts +9 -4
- package/dist/shims/navigation.d.ts.map +1 -1
- package/package.json +6 -7
- package/src/adapters/build-output-helper.ts +77 -0
- package/src/adapters/cloudflare.ts +10 -50
- package/src/adapters/nitro.ts +11 -45
- package/src/adapters/shared.ts +40 -0
- package/src/cache/timber-cache.ts +3 -2
- package/src/cli.ts +0 -0
- package/src/client/form.tsx +17 -25
- package/src/client/index.ts +16 -9
- package/src/client/internal.ts +3 -2
- package/src/client/router.ts +1 -1
- package/src/client/rsc-fetch.ts +15 -0
- package/src/client/state.ts +2 -2
- package/src/client/use-cookie.ts +29 -0
- package/src/codec.ts +3 -7
- package/src/config-types.ts +28 -0
- package/src/cookies/define-cookie.ts +271 -78
- package/src/cookies/index.ts +11 -8
- package/src/cookies/json-cookie.ts +105 -0
- package/src/cookies/validation.ts +134 -0
- package/src/{plugins/dev-404-page.ts → dev-tools/404-page.ts} +2 -7
- package/src/{plugins/dev-error-page.ts → dev-tools/error-page.ts} +5 -32
- package/src/dev-tools/index.ts +90 -0
- package/src/dev-tools/instrumentation.ts +176 -0
- package/src/{plugins/dev-logs.ts → dev-tools/logs.ts} +2 -2
- package/src/{plugins/dev-error-overlay.ts → dev-tools/overlay.ts} +5 -23
- package/src/dev-tools/stack-classifier.ts +75 -0
- package/src/{plugins/dev-terminal-error.ts → dev-tools/terminal.ts} +4 -38
- package/src/{server/dev-warnings.ts → dev-tools/warnings.ts} +1 -1
- package/src/index.ts +11 -3
- package/src/plugin-context.ts +1 -1
- package/src/plugins/adapter-build.ts +3 -1
- package/src/plugins/dev-server.ts +3 -3
- package/src/plugins/shims.ts +1 -1
- package/src/plugins/static-build.ts +1 -1
- package/src/routing/convention-lint.ts +5 -4
- package/src/routing/scanner.ts +5 -2
- package/src/routing/status-file-lint.ts +4 -2
- package/src/search-params/define.ts +71 -15
- package/src/search-params/wrappers.ts +9 -2
- package/src/segment-params/define.ts +66 -13
- package/src/server/access-gate.tsx +9 -8
- package/src/server/action-handler.ts +28 -38
- package/src/server/als-registry.ts +5 -5
- package/src/server/asset-headers.ts +8 -34
- package/src/server/cookie-context.ts +468 -0
- package/src/server/cookie-parsing.ts +135 -0
- package/src/server/{deny-page-resolver.ts → deny-boundary.ts} +78 -14
- package/src/server/deny-renderer.ts +2 -7
- package/src/server/early-hints-sender.ts +3 -2
- package/src/server/fallback-error.ts +1 -1
- package/src/server/index.ts +13 -14
- package/src/server/internal.ts +10 -3
- package/src/server/logger.ts +23 -0
- package/src/server/middleware-runner.ts +44 -0
- package/src/server/param-coercion.ts +76 -0
- package/src/server/pipeline-helpers.ts +37 -13
- package/src/server/pipeline-outcome.ts +167 -0
- package/src/server/pipeline-phases.ts +27 -209
- package/src/server/pipeline.ts +2 -9
- package/src/server/request-context.ts +46 -451
- package/src/server/route-element-builder.ts +7 -3
- package/src/server/rsc-entry/action-middleware-runner.ts +167 -0
- package/src/server/rsc-entry/error-renderer.ts +1 -1
- package/src/server/rsc-entry/helpers.ts +2 -7
- package/src/server/rsc-entry/index.ts +34 -273
- package/src/server/rsc-entry/render-route.ts +304 -0
- package/src/server/rsc-entry/rsc-payload.ts +1 -1
- package/src/server/rsc-entry/ssr-renderer.ts +2 -2
- package/src/server/rsc-entry/wrap-action-dispatch.ts +316 -23
- package/src/server/ssr-entry.ts +1 -1
- package/src/server/state-tree-diff.ts +4 -1
- package/src/server/tracing.ts +3 -3
- package/src/server/tree-builder.ts +128 -52
- package/src/server/types.ts +52 -0
- package/src/server/utils/escape-html.ts +20 -0
- package/src/shims/headers.ts +3 -3
- package/src/shims/navigation-client.ts +4 -3
- package/src/shims/navigation.ts +9 -7
- package/dist/_chunks/actions-DLnUaR65.js +0 -421
- package/dist/_chunks/actions-DLnUaR65.js.map +0 -1
- package/dist/_chunks/als-registry-HS0LGUl2.js +0 -41
- package/dist/_chunks/als-registry-HS0LGUl2.js.map +0 -1
- package/dist/_chunks/debug-ECi_61pb.js +0 -108
- package/dist/_chunks/debug-ECi_61pb.js.map +0 -1
- package/dist/_chunks/define-C77ScO0m.js.map +0 -1
- package/dist/_chunks/define-Itxvcd7F.js.map +0 -1
- package/dist/_chunks/define-cookie-BowvzoP0.js +0 -94
- package/dist/_chunks/define-cookie-BowvzoP0.js.map +0 -1
- package/dist/_chunks/dev-warnings-DpGRGoDi.js.map +0 -1
- package/dist/_chunks/merge-search-params-Cm_KIWDX.js +0 -41
- package/dist/_chunks/merge-search-params-Cm_KIWDX.js.map +0 -1
- package/dist/_chunks/request-context-CK5tZqIP.js +0 -478
- package/dist/_chunks/request-context-CK5tZqIP.js.map +0 -1
- package/dist/_chunks/router-ref-C8OCm7g7.js.map +0 -1
- package/dist/_chunks/tracing-CCYbKn5n.js +0 -238
- package/dist/_chunks/tracing-CCYbKn5n.js.map +0 -1
- package/dist/_chunks/use-params-IOPu7E8t.js.map +0 -1
- package/dist/_chunks/use-query-states-BiV5GJgm.js.map +0 -1
- package/dist/_chunks/walkers-VOXgavMF.js.map +0 -1
- package/dist/client/use-params.d.ts.map +0 -1
- package/dist/plugins/dev-404-page.d.ts.map +0 -1
- package/dist/plugins/dev-browser-logs.d.ts.map +0 -1
- package/dist/plugins/dev-error-overlay.d.ts.map +0 -1
- package/dist/plugins/dev-error-page.d.ts.map +0 -1
- package/dist/plugins/dev-logs.d.ts.map +0 -1
- package/dist/plugins/dev-terminal-error.d.ts.map +0 -1
- package/dist/server/deny-page-resolver.d.ts +0 -52
- package/dist/server/deny-page-resolver.d.ts.map +0 -1
- package/dist/server/dev-fetch-instrumentation.d.ts +0 -22
- package/dist/server/dev-fetch-instrumentation.d.ts.map +0 -1
- package/dist/server/dev-holding-server.d.ts.map +0 -1
- package/dist/server/dev-logger.d.ts.map +0 -1
- package/dist/server/dev-span-processor.d.ts.map +0 -1
- package/dist/server/dev-warnings.d.ts.map +0 -1
- package/dist/server/page-deny-boundary.d.ts +0 -31
- package/dist/server/page-deny-boundary.d.ts.map +0 -1
- package/src/server/dev-fetch-instrumentation.ts +0 -96
- package/src/server/dev-span-processor.ts +0 -78
- package/src/server/page-deny-boundary.tsx +0 -56
- /package/src/client/{use-params.ts → use-segment-params.ts} +0 -0
- /package/src/{plugins/dev-browser-logs.ts → dev-tools/browser-logs.ts} +0 -0
- /package/src/{server/dev-holding-server.ts → dev-tools/holding-server.ts} +0 -0
- /package/src/{server/dev-logger.ts → dev-tools/logger.ts} +0 -0
|
@@ -16,13 +16,12 @@
|
|
|
16
16
|
|
|
17
17
|
import { pathToFileURL } from 'node:url';
|
|
18
18
|
import {
|
|
19
|
-
classifyFrame,
|
|
20
19
|
extractComponentStack,
|
|
21
20
|
parseFirstAppFrame,
|
|
22
21
|
PHASE_LABELS,
|
|
23
22
|
type ErrorPhase,
|
|
24
|
-
|
|
25
|
-
} from './
|
|
23
|
+
} from './overlay.js';
|
|
24
|
+
import { classifyStack } from './stack-classifier.js';
|
|
26
25
|
|
|
27
26
|
// ─── ANSI Codes ─────────────────────────────────────────────────────────────
|
|
28
27
|
|
|
@@ -93,40 +92,7 @@ function box(lines: string[], borderColor: string, width = 80): string {
|
|
|
93
92
|
return output.join('\n');
|
|
94
93
|
}
|
|
95
94
|
|
|
96
|
-
//
|
|
97
|
-
|
|
98
|
-
interface ClassifiedFrame {
|
|
99
|
-
raw: string;
|
|
100
|
-
type: FrameType;
|
|
101
|
-
file?: string;
|
|
102
|
-
line?: number;
|
|
103
|
-
col?: number;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/** Parse file/line/col from a stack frame line. */
|
|
107
|
-
function parseFrame(frameLine: string): { file?: string; line?: number; col?: number } {
|
|
108
|
-
const parenMatch = /\(([^)]+):(\d+):(\d+)\)/.exec(frameLine);
|
|
109
|
-
if (parenMatch) {
|
|
110
|
-
return { file: parenMatch[1], line: Number(parenMatch[2]), col: Number(parenMatch[3]) };
|
|
111
|
-
}
|
|
112
|
-
const bareMatch = /at (\/[^:]+):(\d+):(\d+)/.exec(frameLine);
|
|
113
|
-
if (bareMatch) {
|
|
114
|
-
return { file: bareMatch[1], line: Number(bareMatch[2]), col: Number(bareMatch[3]) };
|
|
115
|
-
}
|
|
116
|
-
return {};
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
function classifyFrames(stack: string, projectRoot: string): ClassifiedFrame[] {
|
|
120
|
-
return stack
|
|
121
|
-
.split('\n')
|
|
122
|
-
.slice(1)
|
|
123
|
-
.filter((l) => l.trim().startsWith('at '))
|
|
124
|
-
.map((raw) => {
|
|
125
|
-
const type = classifyFrame(raw, projectRoot);
|
|
126
|
-
const { file, line, col } = parseFrame(raw);
|
|
127
|
-
return { raw, type, file, line, col };
|
|
128
|
-
});
|
|
129
|
-
}
|
|
95
|
+
// Frame classification and parsing provided by stack-classifier.ts
|
|
130
96
|
|
|
131
97
|
// ─── Public API ─────────────────────────────────────────────────────────────
|
|
132
98
|
|
|
@@ -144,7 +110,7 @@ export function formatTerminalError(error: Error, phase: ErrorPhase, projectRoot
|
|
|
144
110
|
const sections: string[] = [];
|
|
145
111
|
const componentStack = extractComponentStack(error);
|
|
146
112
|
const loc = parseFirstAppFrame(error.stack ?? '', projectRoot);
|
|
147
|
-
const frames = error.stack ?
|
|
113
|
+
const frames = error.stack ? classifyStack(error.stack, projectRoot) : [];
|
|
148
114
|
const appFrames = frames.filter((f) => f.type === 'app');
|
|
149
115
|
const internalCount = frames.filter((f) => f.type !== 'app').length;
|
|
150
116
|
|
package/src/index.ts
CHANGED
|
@@ -24,8 +24,8 @@ import { timberFonts } from './plugins/fonts';
|
|
|
24
24
|
import { timberStaticBuild } from './plugins/static-build';
|
|
25
25
|
import { timberServerActionExports } from './plugins/server-action-exports';
|
|
26
26
|
import { timberBuildManifest } from './plugins/build-manifest';
|
|
27
|
-
import { timberDevLogs } from './
|
|
28
|
-
import { timberDevBrowserLogs } from './
|
|
27
|
+
import { timberDevLogs } from './dev-tools/logs';
|
|
28
|
+
import { timberDevBrowserLogs } from './dev-tools/browser-logs';
|
|
29
29
|
import { timberReactProd } from './plugins/react-prod';
|
|
30
30
|
import { timberChunks } from './plugins/chunks';
|
|
31
31
|
import { clientChunkGroup } from './plugins/client-chunks';
|
|
@@ -34,7 +34,7 @@ import { timberAdapterBuild } from './plugins/adapter-build';
|
|
|
34
34
|
import { timberBuildReport } from './plugins/build-report';
|
|
35
35
|
import { createNoopTimer } from './utils/startup-timer';
|
|
36
36
|
import { resolveEncryptionKeyExpression, shouldEnableEncryption } from './server/action-encryption';
|
|
37
|
-
import { createHoldingServer } from './
|
|
37
|
+
import { createHoldingServer } from './dev-tools/holding-server.js';
|
|
38
38
|
import { resolveStartPort, startDevServerPort } from './server/port-resolution.js';
|
|
39
39
|
import type { TimberUserConfig } from './config-types.js';
|
|
40
40
|
import type { PluginContext } from './plugin-context.js';
|
|
@@ -52,6 +52,14 @@ import {
|
|
|
52
52
|
|
|
53
53
|
export type { TimberUserConfig } from './config-types.js';
|
|
54
54
|
|
|
55
|
+
// Metadata route handler types — re-exported so user-authored metadata
|
|
56
|
+
// route files (sitemap.ts, robots.ts, manifest.ts, icon.tsx, etc.) can
|
|
57
|
+
// import them from the package root without reaching into ./server.
|
|
58
|
+
// The canonical location for these types is '@timber-js/app/server'; this
|
|
59
|
+
// root re-export is a convenience for typed metadata route handlers.
|
|
60
|
+
// See design/16-metadata.md §"Metadata Routes".
|
|
61
|
+
export type { Metadata, MetadataRoute, MetadataHandler, MetadataResult } from './server/types.js';
|
|
62
|
+
|
|
55
63
|
/**
|
|
56
64
|
* Route map interface — augmented by the generated timber-routes.d.ts.
|
|
57
65
|
*
|
package/src/plugin-context.ts
CHANGED
|
@@ -16,7 +16,7 @@ import type { BuildManifest } from './server/build-manifest';
|
|
|
16
16
|
import type { StartupTimer } from './utils/startup-timer';
|
|
17
17
|
import { createStartupTimer } from './utils/startup-timer';
|
|
18
18
|
import type { TimberUserConfig, ClientJavascriptConfig } from './config-types.js';
|
|
19
|
-
import type { HoldingServer } from './
|
|
19
|
+
import type { HoldingServer } from './dev-tools/holding-server.js';
|
|
20
20
|
|
|
21
21
|
// Re-export for sub-plugin convenience — they import from plugin-context.ts
|
|
22
22
|
export type { TimberUserConfig, ClientJavascriptConfig } from './config-types.js';
|
|
@@ -18,6 +18,7 @@ import { join } from 'node:path';
|
|
|
18
18
|
import { readFile, writeFile } from 'node:fs/promises';
|
|
19
19
|
import type { PluginContext } from '../plugin-context.js';
|
|
20
20
|
import type { TimberPlatformAdapter, TimberConfig } from '../adapters/types.js';
|
|
21
|
+
import { swallow } from '../server/logger.js';
|
|
21
22
|
|
|
22
23
|
export function timberAdapterBuild(ctx: PluginContext): Plugin {
|
|
23
24
|
return {
|
|
@@ -103,7 +104,8 @@ async function stripJsFromRscAssetsManifests(buildDir: string): Promise<void> {
|
|
|
103
104
|
let manifest: Record<string, unknown>;
|
|
104
105
|
try {
|
|
105
106
|
manifest = JSON.parse(jsonStr);
|
|
106
|
-
} catch {
|
|
107
|
+
} catch (err) {
|
|
108
|
+
swallow(err, `corrupted RSC assets manifest: ${path}`, { level: 'warn' });
|
|
107
109
|
continue;
|
|
108
110
|
}
|
|
109
111
|
|
|
@@ -16,19 +16,19 @@ import type { Plugin, ViteDevServer, DevEnvironment } from 'vite';
|
|
|
16
16
|
import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
17
17
|
import { join } from 'node:path';
|
|
18
18
|
import type { PluginContext } from '../plugin-context.js';
|
|
19
|
-
import { setViteServer } from '../
|
|
19
|
+
import { setViteServer } from '../dev-tools/warnings.js';
|
|
20
20
|
import {
|
|
21
21
|
sendErrorToOverlay,
|
|
22
22
|
classifyErrorPhase,
|
|
23
23
|
fixErrorStacktrace,
|
|
24
24
|
parseFirstAppFrame,
|
|
25
25
|
type ErrorPhase,
|
|
26
|
-
} from '
|
|
26
|
+
} from '../dev-tools/overlay.js';
|
|
27
27
|
import {
|
|
28
28
|
generateDevErrorPage,
|
|
29
29
|
extractHmrOptions,
|
|
30
30
|
type DevErrorHmrOptions,
|
|
31
|
-
} from '
|
|
31
|
+
} from '../dev-tools/error-page.js';
|
|
32
32
|
import { addVirtualModuleContext } from '../config-validation.js';
|
|
33
33
|
import { compressResponse } from '../server/compress.js';
|
|
34
34
|
|
package/src/plugins/shims.ts
CHANGED
|
@@ -299,7 +299,7 @@ export const headers = stub;
|
|
|
299
299
|
export const cookies = stub;
|
|
300
300
|
export const getHeaders = stub;
|
|
301
301
|
export const getHeader = stub;
|
|
302
|
-
export const
|
|
302
|
+
export const getCookieJar = stub;
|
|
303
303
|
export const getCookie = stub;
|
|
304
304
|
export const getSearchParams = stub;
|
|
305
305
|
export const getSegmentParams = stub;
|
|
@@ -39,7 +39,7 @@ export interface StaticOptions {
|
|
|
39
39
|
* We detect both import-level and call-level usage.
|
|
40
40
|
*/
|
|
41
41
|
const DYNAMIC_API_PATTERNS: Array<{ pattern: RegExp; name: string }> = [
|
|
42
|
-
{ pattern: /\
|
|
42
|
+
{ pattern: /\bgetCookieJar\s*\(/, name: 'getCookieJar()' },
|
|
43
43
|
{ pattern: /\bgetHeaders\s*\(/, name: 'getHeaders()' },
|
|
44
44
|
{ pattern: /\bcookies\s*\(/, name: 'cookies()' },
|
|
45
45
|
{ pattern: /\bheaders\s*\(/, name: 'headers()' },
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
|
|
14
14
|
import { readFileSync, existsSync } from 'node:fs';
|
|
15
15
|
import type { RouteTree, SegmentNode } from './types.js';
|
|
16
|
+
import { swallow } from '../server/logger.js';
|
|
16
17
|
|
|
17
18
|
// ─── Types ──────────────────────────────────────────────────────────────────
|
|
18
19
|
|
|
@@ -164,8 +165,8 @@ function checkRouteExports(node: SegmentNode, warnings: ConventionWarning[]): vo
|
|
|
164
165
|
level: 'warn',
|
|
165
166
|
});
|
|
166
167
|
}
|
|
167
|
-
} catch {
|
|
168
|
-
|
|
168
|
+
} catch (err) {
|
|
169
|
+
swallow(err, `convention-lint: unreadable route.ts ${filePath}`);
|
|
169
170
|
}
|
|
170
171
|
}
|
|
171
172
|
|
|
@@ -280,8 +281,8 @@ function checkFileDefaultExport(
|
|
|
280
281
|
level: 'warn',
|
|
281
282
|
});
|
|
282
283
|
}
|
|
283
|
-
} catch {
|
|
284
|
-
|
|
284
|
+
} catch (err) {
|
|
285
|
+
swallow(err, `convention-lint: unreadable ${fileType} file ${filePath}`);
|
|
285
286
|
}
|
|
286
287
|
}
|
|
287
288
|
|
package/src/routing/scanner.ts
CHANGED
|
@@ -21,6 +21,7 @@ import type {
|
|
|
21
21
|
import { classifySegment } from './segment-classify.js';
|
|
22
22
|
import { DEFAULT_PAGE_EXTENSIONS } from './types.js';
|
|
23
23
|
import { classifyMetadataRoute, isDynamicMetadataExtension } from '../server/metadata-routes.js';
|
|
24
|
+
import { swallow } from '../server/logger.js';
|
|
24
25
|
|
|
25
26
|
/**
|
|
26
27
|
* Pattern matching encoded path delimiters that must be rejected during route discovery.
|
|
@@ -152,7 +153,8 @@ function scanSegmentFiles(dirPath: string, node: SegmentNode, extSet: Set<string
|
|
|
152
153
|
let entries: string[];
|
|
153
154
|
try {
|
|
154
155
|
entries = readdirSync(dirPath);
|
|
155
|
-
} catch {
|
|
156
|
+
} catch (err) {
|
|
157
|
+
swallow(err, `scanSegmentFiles: unreadable directory ${dirPath}`, { level: 'warn' });
|
|
156
158
|
return;
|
|
157
159
|
}
|
|
158
160
|
|
|
@@ -282,7 +284,8 @@ function scanChildren(dirPath: string, parentNode: SegmentNode, extSet: Set<stri
|
|
|
282
284
|
let entries: string[];
|
|
283
285
|
try {
|
|
284
286
|
entries = readdirSync(dirPath);
|
|
285
|
-
} catch {
|
|
287
|
+
} catch (err) {
|
|
288
|
+
swallow(err, `scanChildren: unreadable directory ${dirPath}`, { level: 'warn' });
|
|
286
289
|
return;
|
|
287
290
|
}
|
|
288
291
|
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
import { readFileSync } from 'node:fs';
|
|
17
17
|
import type { RouteTree, SegmentNode } from './types.js';
|
|
18
18
|
import { detectFileDirective } from '../utils/directive-parser.js';
|
|
19
|
+
import { swallow } from '../server/logger.js';
|
|
19
20
|
|
|
20
21
|
/** Extensions that require 'use client' (component files, not MDX/JSON). */
|
|
21
22
|
const CLIENT_REQUIRED_EXTENSIONS = new Set(['tsx', 'jsx', 'ts', 'js']);
|
|
@@ -80,8 +81,9 @@ function checkFile(
|
|
|
80
81
|
let code: string;
|
|
81
82
|
try {
|
|
82
83
|
code = readFileSync(filePath, 'utf-8');
|
|
83
|
-
} catch {
|
|
84
|
-
|
|
84
|
+
} catch (err) {
|
|
85
|
+
swallow(err, `status-file-lint: unreadable file ${filePath}`);
|
|
86
|
+
return;
|
|
85
87
|
}
|
|
86
88
|
|
|
87
89
|
const directive = detectFileDirective(code, ['use client']);
|
|
@@ -23,18 +23,18 @@ import type { Codec } from '../codec.js';
|
|
|
23
23
|
// dynamic `await import()` at call time because the async microtask from the
|
|
24
24
|
// dynamic import loses AsyncLocalStorage context in React's RSC Flight renderer,
|
|
25
25
|
// breaking getSearchParams() in parallel slot pages. See TIM-523.
|
|
26
|
-
let _getSearchParamsFn: (() =>
|
|
26
|
+
let _getSearchParamsFn: (() => URLSearchParams) | undefined;
|
|
27
27
|
|
|
28
28
|
/**
|
|
29
29
|
* Register the getSearchParams function. Called once at module load time
|
|
30
30
|
* from request-context.ts to avoid dynamic import at call time.
|
|
31
31
|
* @internal
|
|
32
32
|
*/
|
|
33
|
-
export function _setGetSearchParamsFn(fn: () =>
|
|
33
|
+
export function _setGetSearchParamsFn(fn: () => URLSearchParams): void {
|
|
34
34
|
_getSearchParamsFn = fn;
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
function getSearchParamsFromAls():
|
|
37
|
+
function getSearchParamsFromAls(): URLSearchParams {
|
|
38
38
|
if (!_getSearchParamsFn) {
|
|
39
39
|
throw new Error(
|
|
40
40
|
'[timber] searchParams.get() is only available on the server. ' +
|
|
@@ -111,19 +111,18 @@ export interface SearchParamsDefinition<T extends Record<string, unknown>> {
|
|
|
111
111
|
/**
|
|
112
112
|
* Get typed search params from the current request context (ALS-backed).
|
|
113
113
|
*
|
|
114
|
-
* Server-only
|
|
115
|
-
* Throws on client.
|
|
116
|
-
* export and the server helper.
|
|
114
|
+
* Server-only, sync. Reads getSearchParams() from ALS and parses through codecs.
|
|
115
|
+
* Throws on client.
|
|
117
116
|
*
|
|
118
117
|
* ```tsx
|
|
119
118
|
* // app/products/page.tsx
|
|
120
119
|
* import { searchParams } from './params'
|
|
121
|
-
* export default
|
|
122
|
-
* const { page, category } =
|
|
120
|
+
* export default function Page() {
|
|
121
|
+
* const { page, category } = searchParams.get()
|
|
123
122
|
* }
|
|
124
123
|
* ```
|
|
125
124
|
*/
|
|
126
|
-
get():
|
|
125
|
+
get(): T;
|
|
127
126
|
|
|
128
127
|
/** Client hook — reads current URL params and returns typed values + setter. */
|
|
129
128
|
useQueryStates(options?: QueryStatesOptions): [T, SetParams<T>];
|
|
@@ -271,11 +270,68 @@ function validateDefaults(codecMap: Record<string, SearchParamCodec<unknown>>):
|
|
|
271
270
|
* })
|
|
272
271
|
* ```
|
|
273
272
|
*/
|
|
273
|
+
/**
|
|
274
|
+
* Overload: accept a Standard Schema object schema (e.g., z.object({...})).
|
|
275
|
+
*
|
|
276
|
+
* The schema must have a `.shape` property whose values are themselves
|
|
277
|
+
* Standard Schema objects. Each shape property becomes a field codec
|
|
278
|
+
* via fromSchema().
|
|
279
|
+
*
|
|
280
|
+
* ```ts
|
|
281
|
+
* const searchParams = defineSearchParams(
|
|
282
|
+
* z.object({ page: z.coerce.number().default(1), q: z.string().optional() })
|
|
283
|
+
* )
|
|
284
|
+
* ```
|
|
285
|
+
*/
|
|
286
|
+
export function defineSearchParams<T extends Record<string, unknown>>(
|
|
287
|
+
schema: StandardSchemaV1<T> & { shape: Record<string, StandardSchemaV1<unknown>> }
|
|
288
|
+
): SearchParamsDefinition<T>;
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Overload: accept a map of codecs and/or Standard Schema objects.
|
|
292
|
+
*/
|
|
274
293
|
export function defineSearchParams<C extends Record<string, SearchParamField>>(
|
|
275
294
|
codecs: C
|
|
276
|
-
): SearchParamsDefinition<{ [K in keyof C]: InferField<C[K]> }
|
|
277
|
-
|
|
295
|
+
): SearchParamsDefinition<{ [K in keyof C]: InferField<C[K]> }>;
|
|
296
|
+
|
|
297
|
+
export function defineSearchParams(
|
|
298
|
+
codecsOrSchema:
|
|
299
|
+
| Record<string, SearchParamField>
|
|
300
|
+
| (StandardSchemaV1<unknown> & { shape: Record<string, StandardSchemaV1<unknown>> })
|
|
301
|
+
): SearchParamsDefinition<Record<string, unknown>> {
|
|
302
|
+
// Detect Standard Schema object with .shape (e.g., z.object(...))
|
|
303
|
+
if (isStandardSchema(codecsOrSchema) && hasShape(codecsOrSchema)) {
|
|
304
|
+
const fieldCodecs: Record<string, SearchParamField> = {};
|
|
305
|
+
for (const [key, fieldSchema] of Object.entries(codecsOrSchema.shape)) {
|
|
306
|
+
if (isStandardSchema(fieldSchema)) {
|
|
307
|
+
fieldCodecs[key] = fieldSchema;
|
|
308
|
+
} else {
|
|
309
|
+
throw new Error(
|
|
310
|
+
`[timber] defineSearchParams: field '${key}' in schema.shape is not a Standard Schema. ` +
|
|
311
|
+
`All shape properties must be Standard Schema objects (Zod, Valibot, ArkType).`
|
|
312
|
+
);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
return defineSearchParamsFromMap(fieldCodecs);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
return defineSearchParamsFromMap(codecsOrSchema as Record<string, SearchParamField>);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/** Check if a schema has a .shape property with object-type values. */
|
|
322
|
+
function hasShape(schema: unknown): schema is { shape: Record<string, unknown> } {
|
|
323
|
+
return (
|
|
324
|
+
typeof schema === 'object' &&
|
|
325
|
+
schema !== null &&
|
|
326
|
+
'shape' in schema &&
|
|
327
|
+
typeof (schema as { shape: unknown }).shape === 'object' &&
|
|
328
|
+
(schema as { shape: unknown }).shape !== null
|
|
329
|
+
);
|
|
330
|
+
}
|
|
278
331
|
|
|
332
|
+
function defineSearchParamsFromMap(
|
|
333
|
+
codecs: Record<string, SearchParamField>
|
|
334
|
+
): SearchParamsDefinition<Record<string, unknown>> {
|
|
279
335
|
const resolvedCodecs: Record<string, SearchParamCodec<unknown>> = {};
|
|
280
336
|
const urlKeys: Record<string, string> = {};
|
|
281
337
|
|
|
@@ -290,7 +346,7 @@ export function defineSearchParams<C extends Record<string, SearchParamField>>(
|
|
|
290
346
|
// Validate that all codecs handle absent params
|
|
291
347
|
validateDefaults(resolvedCodecs);
|
|
292
348
|
|
|
293
|
-
return buildDefinition
|
|
349
|
+
return buildDefinition(resolvedCodecs as unknown as CodecMap<Record<string, unknown>>, urlKeys);
|
|
294
350
|
}
|
|
295
351
|
|
|
296
352
|
// ---------------------------------------------------------------------------
|
|
@@ -453,15 +509,15 @@ function buildDefinition<T extends Record<string, unknown>>(
|
|
|
453
509
|
|
|
454
510
|
// ---- get ----
|
|
455
511
|
// ALS-backed: reads getSearchParams() from the current request context
|
|
456
|
-
// and parses through codecs. Server-only
|
|
457
|
-
|
|
512
|
+
// and parses through codecs. Server-only, sync.
|
|
513
|
+
function get(): T {
|
|
458
514
|
if (typeof window !== 'undefined') {
|
|
459
515
|
throw new Error(
|
|
460
516
|
'[timber] searchParams.get() is server-only. ' +
|
|
461
517
|
'Use searchParams.useQueryStates() on the client.'
|
|
462
518
|
);
|
|
463
519
|
}
|
|
464
|
-
const raw =
|
|
520
|
+
const raw = getSearchParamsFromAls();
|
|
465
521
|
return parseSync(raw);
|
|
466
522
|
}
|
|
467
523
|
|
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
* Design doc: design/23-search-params.md
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import type { SearchParamCodec, SearchParamCodecWithUrlKey } from './define.js';
|
|
11
|
+
import type { SearchParamCodec, SearchParamCodecWithUrlKey, SearchParamField } from './define.js';
|
|
12
|
+
import { isCodec, isStandardSchema, fromSchema } from '../schema-bridge.js';
|
|
12
13
|
|
|
13
14
|
// ---------------------------------------------------------------------------
|
|
14
15
|
// withDefault
|
|
@@ -74,9 +75,15 @@ export function withDefault<T>(
|
|
|
74
75
|
* ```
|
|
75
76
|
*/
|
|
76
77
|
export function withUrlKey<T>(
|
|
77
|
-
|
|
78
|
+
codecOrSchema: SearchParamField<T>,
|
|
78
79
|
urlKey: string
|
|
79
80
|
): SearchParamCodecWithUrlKey<T> {
|
|
81
|
+
// Auto-detect Standard Schema (Zod, Valibot, ArkType) and wrap
|
|
82
|
+
const codec: SearchParamCodec<T> = isCodec(codecOrSchema)
|
|
83
|
+
? codecOrSchema
|
|
84
|
+
: isStandardSchema(codecOrSchema)
|
|
85
|
+
? fromSchema(codecOrSchema)
|
|
86
|
+
: (codecOrSchema as SearchParamCodec<T>);
|
|
80
87
|
return {
|
|
81
88
|
parse: codec.parse.bind(codec),
|
|
82
89
|
serialize: codec.serialize.bind(codec),
|
|
@@ -27,22 +27,44 @@ import {
|
|
|
27
27
|
|
|
28
28
|
// Same pattern as search-params: eagerly registered at server startup
|
|
29
29
|
// to avoid dynamic imports that lose ALS context. See TIM-523.
|
|
30
|
-
let _getSegmentParamsFn: (() =>
|
|
30
|
+
let _getSegmentParamsFn: (() => Record<string, string | string[]>) | undefined;
|
|
31
|
+
let _useSegmentParamsHook: (() => Record<string, string | string[]>) | undefined;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Register the client useSegmentParams hook.
|
|
35
|
+
* Called by client entry at module load time to avoid require() at call time.
|
|
36
|
+
* @internal
|
|
37
|
+
*/
|
|
38
|
+
export function _registerUseSegmentParams(hook: () => Record<string, string | string[]>): void {
|
|
39
|
+
_useSegmentParamsHook = hook;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Self-register on the client when the barrel hasn't been imported yet.
|
|
43
|
+
// Handles the edge case where a client component imports only from params.ts
|
|
44
|
+
// without importing anything from @timber-js/app/client (which runs the
|
|
45
|
+
// registration). The dynamic import resolves before React hydration in Vite.
|
|
46
|
+
if (typeof window !== 'undefined' && !_useSegmentParamsHook) {
|
|
47
|
+
import('../client/use-segment-params.js').then((mod) => {
|
|
48
|
+
if (!_useSegmentParamsHook) {
|
|
49
|
+
_useSegmentParamsHook = mod.useSegmentParams;
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
}
|
|
31
53
|
|
|
32
54
|
/**
|
|
33
55
|
* Register the getSegmentParams function. Called once at module load time
|
|
34
56
|
* from request-context.ts to avoid dynamic import at call time.
|
|
35
57
|
* @internal
|
|
36
58
|
*/
|
|
37
|
-
export function _setGetSegmentParamsFn(fn: () =>
|
|
59
|
+
export function _setGetSegmentParamsFn(fn: () => Record<string, string | string[]>): void {
|
|
38
60
|
_getSegmentParamsFn = fn;
|
|
39
61
|
}
|
|
40
62
|
|
|
41
|
-
function getSegmentParamsFromAls():
|
|
63
|
+
function getSegmentParamsFromAls(): Record<string, string | string[]> {
|
|
42
64
|
if (!_getSegmentParamsFn) {
|
|
43
65
|
throw new Error(
|
|
44
66
|
'[timber] segmentParams.get() is only available on the server. ' +
|
|
45
|
-
'Use useSegmentParams() on the client.'
|
|
67
|
+
'Use segmentParams.useSegmentParams() on the client.'
|
|
46
68
|
);
|
|
47
69
|
}
|
|
48
70
|
return _getSegmentParamsFn();
|
|
@@ -77,8 +99,7 @@ export interface ParamsDefinition<T extends Record<string, unknown>> {
|
|
|
77
99
|
/**
|
|
78
100
|
* Get typed segment params from the current request context (ALS).
|
|
79
101
|
*
|
|
80
|
-
* Server-only.
|
|
81
|
-
* this definition's codecs, returning fully typed params.
|
|
102
|
+
* Server-only, sync.
|
|
82
103
|
*
|
|
83
104
|
* ```ts
|
|
84
105
|
* // app/products/[id]/params.ts
|
|
@@ -86,12 +107,25 @@ export interface ParamsDefinition<T extends Record<string, unknown>> {
|
|
|
86
107
|
*
|
|
87
108
|
* // app/products/[id]/page.tsx
|
|
88
109
|
* import { segmentParams } from './params'
|
|
89
|
-
* export default
|
|
90
|
-
* const { id } =
|
|
110
|
+
* export default function Page() {
|
|
111
|
+
* const { id } = segmentParams.get() // id: number
|
|
112
|
+
* }
|
|
113
|
+
* ```
|
|
114
|
+
*/
|
|
115
|
+
get(): T;
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Client hook for accessing typed segment params reactively.
|
|
119
|
+
*
|
|
120
|
+
* ```tsx
|
|
121
|
+
* 'use client'
|
|
122
|
+
* import { segmentParams } from './params'
|
|
123
|
+
* export function ProductHeader() {
|
|
124
|
+
* const { id } = segmentParams.useSegmentParams()
|
|
91
125
|
* }
|
|
92
126
|
* ```
|
|
93
127
|
*/
|
|
94
|
-
|
|
128
|
+
useSegmentParams(): T;
|
|
95
129
|
|
|
96
130
|
/** Read-only codec map. */
|
|
97
131
|
codecs: { [K in keyof T]: Codec<T[K]> };
|
|
@@ -250,28 +284,47 @@ export function defineSegmentParams<C extends Record<string, ParamField>>(
|
|
|
250
284
|
|
|
251
285
|
// ---- get ----
|
|
252
286
|
// ALS-backed: reads segment params from the current request context.
|
|
253
|
-
// Server-only
|
|
287
|
+
// Server-only, sync.
|
|
254
288
|
//
|
|
255
289
|
// The pipeline already coerces params via coerceSegmentParams() which
|
|
256
290
|
// calls parse() and stores typed values in ALS via setSegmentParams().
|
|
257
291
|
// We return those directly instead of re-parsing, because codecs may
|
|
258
292
|
// not be idempotent (e.g., a codec that only accepts raw strings would
|
|
259
293
|
// throw if given an already-parsed value). See TIM-574.
|
|
260
|
-
|
|
294
|
+
function get(): T {
|
|
261
295
|
if (typeof window !== 'undefined') {
|
|
262
296
|
throw new Error(
|
|
263
|
-
'[timber] segmentParams.get() is server-only. ' +
|
|
297
|
+
'[timber] segmentParams.get() is server-only. ' +
|
|
298
|
+
'Use segmentParams.useSegmentParams() on the client.'
|
|
264
299
|
);
|
|
265
300
|
}
|
|
266
|
-
const params =
|
|
301
|
+
const params = getSegmentParamsFromAls();
|
|
267
302
|
// params are already coerced by the pipeline — return as-is.
|
|
268
303
|
return params as unknown as T;
|
|
269
304
|
}
|
|
270
305
|
|
|
306
|
+
// ---- useSegmentParams ----
|
|
307
|
+
// Client hook that reads typed params from the navigation context.
|
|
308
|
+
// Delegates to the existing useSegmentParams hook from use-segment-params.ts.
|
|
309
|
+
function useSegmentParams(): T {
|
|
310
|
+
if (!_useSegmentParamsHook) {
|
|
311
|
+
throw new Error(
|
|
312
|
+
'[timber] segmentParams.useSegmentParams() requires @timber-js/app/client to be loaded. ' +
|
|
313
|
+
'This hook can only be used in client components.'
|
|
314
|
+
);
|
|
315
|
+
}
|
|
316
|
+
const raw = _useSegmentParamsHook();
|
|
317
|
+
// Params are already coerced by the server pipeline before being sent
|
|
318
|
+
// to the client — return as-is. Re-parsing would break non-idempotent
|
|
319
|
+
// codecs (e.g., z.coerce.number() on an already-parsed number). See TIM-574.
|
|
320
|
+
return raw as unknown as T;
|
|
321
|
+
}
|
|
322
|
+
|
|
271
323
|
const definition: ParamsDefinition<T> = {
|
|
272
324
|
parse,
|
|
273
325
|
serialize,
|
|
274
326
|
get,
|
|
327
|
+
useSegmentParams,
|
|
275
328
|
codecs: resolvedCodecs as { [K in keyof T]: Codec<T[K]> },
|
|
276
329
|
};
|
|
277
330
|
|
|
@@ -14,11 +14,12 @@
|
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
16
|
import { DenySignal, RedirectSignal } from './primitives.js';
|
|
17
|
-
import type { AccessGateProps, SlotAccessGateProps
|
|
17
|
+
import type { AccessGateProps, SlotAccessGateProps } from './tree-builder.js';
|
|
18
18
|
import { withSpan, setSpanAttribute } from './tracing.js';
|
|
19
19
|
import { isDebug } from './debug.js';
|
|
20
|
-
import type { DenyPageEntry } from './deny-
|
|
21
|
-
import { renderMatchingDenyPage, setDenyStatus } from './deny-
|
|
20
|
+
import type { DenyPageEntry } from './deny-boundary.js';
|
|
21
|
+
import { renderMatchingDenyPage, setDenyStatus } from './deny-boundary.js';
|
|
22
|
+
import type { ReactNode } from 'react';
|
|
22
23
|
|
|
23
24
|
// ─── AccessGate ─────────────────────────────────────────────────────────────
|
|
24
25
|
|
|
@@ -36,7 +37,7 @@ import { renderMatchingDenyPage, setDenyStatus } from './deny-page-resolver.js';
|
|
|
36
37
|
* access.ts is a pure gate — return values are discarded. The layout below
|
|
37
38
|
* gets the same data by calling the same cached functions (React.cache dedup).
|
|
38
39
|
*/
|
|
39
|
-
export function AccessGate(props: AccessGateProps):
|
|
40
|
+
export function AccessGate(props: AccessGateProps): ReactNode | Promise<ReactNode> {
|
|
40
41
|
const { accessFn, segmentName, verdict, denyPages, children } = props;
|
|
41
42
|
|
|
42
43
|
// Fast path: replay pre-computed verdict from the pre-render pass.
|
|
@@ -61,8 +62,8 @@ async function accessGateFallback(
|
|
|
61
62
|
accessFn: AccessGateProps['accessFn'],
|
|
62
63
|
segmentName: AccessGateProps['segmentName'],
|
|
63
64
|
denyPages: DenyPageEntry[] | undefined,
|
|
64
|
-
children:
|
|
65
|
-
): Promise<
|
|
65
|
+
children: ReactNode
|
|
66
|
+
): Promise<ReactNode> {
|
|
66
67
|
try {
|
|
67
68
|
await withSpan('timber.access', { 'timber.segment': segmentName ?? 'unknown' }, async () => {
|
|
68
69
|
try {
|
|
@@ -114,7 +115,7 @@ async function accessGateFallback(
|
|
|
114
115
|
* redirect() in slot access.ts is a dev-mode error — redirecting from a
|
|
115
116
|
* slot doesn't make architectural sense.
|
|
116
117
|
*/
|
|
117
|
-
export async function SlotAccessGate(props: SlotAccessGateProps): Promise<
|
|
118
|
+
export async function SlotAccessGate(props: SlotAccessGateProps): Promise<ReactNode> {
|
|
118
119
|
const { accessFn, DeniedComponent, slotName, createElement, defaultFallback, children } = props;
|
|
119
120
|
|
|
120
121
|
try {
|
|
@@ -175,7 +176,7 @@ function buildDeniedFallback(
|
|
|
175
176
|
slotName: string,
|
|
176
177
|
data: unknown,
|
|
177
178
|
createElement: SlotAccessGateProps['createElement']
|
|
178
|
-
):
|
|
179
|
+
): ReactNode | null {
|
|
179
180
|
if (!DeniedComponent) return null;
|
|
180
181
|
return createElement(DeniedComponent, {
|
|
181
182
|
slot: slotName,
|