@timber-js/app 0.2.0-alpha.186 → 0.2.0-alpha.188
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/dist/_chunks/{actions-C-Rw9vPc.js → actions-35jnMdeJ.js} +4 -3
- package/dist/_chunks/{actions-C-Rw9vPc.js.map → actions-35jnMdeJ.js.map} +1 -1
- package/dist/_chunks/als-registry-C6kcfprT.js +41 -0
- package/dist/_chunks/als-registry-C6kcfprT.js.map +1 -0
- package/dist/_chunks/{als-slots-mFweg276.js → als-slots-BEEIPKYm.js} +3 -4
- package/dist/_chunks/{als-slots-mFweg276.js.map → als-slots-BEEIPKYm.js.map} +1 -1
- package/dist/_chunks/{cache-api-Cd0VZ_Pd.js → cache-api-DjNrIWRR.js} +7 -13
- package/dist/_chunks/cache-api-DjNrIWRR.js.map +1 -0
- package/dist/_chunks/cli-check-CpmN7Nh-.js +256 -0
- package/dist/_chunks/cli-check-CpmN7Nh-.js.map +1 -0
- package/dist/_chunks/cli-schema-sync-CGMp_Psg.js +298 -0
- package/dist/_chunks/cli-schema-sync-CGMp_Psg.js.map +1 -0
- package/dist/_chunks/{cloudflare-Cs0uZXea.js → cloudflare-CGP6BZKO.js} +4 -3
- package/dist/_chunks/{cloudflare-Cs0uZXea.js.map → cloudflare-CGP6BZKO.js.map} +1 -1
- package/dist/_chunks/convention-lint-kXsgc_-7.js +784 -0
- package/dist/_chunks/convention-lint-kXsgc_-7.js.map +1 -0
- package/dist/_chunks/{error-boundary-DpYRI_I1.js → error-boundary-D-ODYX41.js} +25 -2
- package/dist/_chunks/error-boundary-D-ODYX41.js.map +1 -0
- package/dist/_chunks/file-cache-DmX7OqZP.js +454 -0
- package/dist/_chunks/file-cache-DmX7OqZP.js.map +1 -0
- package/dist/_chunks/{logger-N7e5auP0.js → logger-D8xJZXIN.js} +27 -40
- package/dist/_chunks/logger-D8xJZXIN.js.map +1 -0
- package/dist/_chunks/mdx-file-C005ay-P.js +25 -0
- package/dist/_chunks/mdx-file-C005ay-P.js.map +1 -0
- package/dist/_chunks/{plugin-context-DEGLSJs3.js → plugin-context-rCinWLiE.js} +7 -2
- package/dist/_chunks/{plugin-context-DEGLSJs3.js.map → plugin-context-rCinWLiE.js.map} +1 -1
- package/dist/_chunks/purge-store-Byr8XOjU.js +14 -0
- package/dist/_chunks/purge-store-Byr8XOjU.js.map +1 -0
- package/dist/_chunks/{resolve-schema-Dz3fcFUo.js → resolve-schema-5ma5pp1b.js} +2 -2
- package/dist/_chunks/{resolve-schema-Dz3fcFUo.js.map → resolve-schema-5ma5pp1b.js.map} +1 -1
- package/dist/_chunks/rsc-media-type-DRqE_lD_.js +46 -0
- package/dist/_chunks/rsc-media-type-DRqE_lD_.js.map +1 -0
- package/dist/_chunks/{cli-schema-sync-CKgHC2MB.js → scanner-C8b0Gcw3.js} +5 -298
- package/dist/_chunks/scanner-C8b0Gcw3.js.map +1 -0
- package/dist/_chunks/schema-bridge-Cc2Gngu1.js +199 -0
- package/dist/_chunks/schema-bridge-Cc2Gngu1.js.map +1 -0
- package/dist/_chunks/segment-classify-C539Pa2O.js.map +1 -1
- package/dist/_chunks/{param-value-C8TNYchQ.js → segment-context-CjOlyB8Y.js} +33 -2
- package/dist/_chunks/segment-context-CjOlyB8Y.js.map +1 -0
- package/dist/_chunks/segment-keys-BawYuNFO.js.map +1 -1
- package/dist/_chunks/{use-query-states-DFvWd-EA.js → use-query-states-BbU5Ge1V.js} +74 -26
- package/dist/_chunks/use-query-states-BbU5Ge1V.js.map +1 -0
- package/dist/_chunks/{navigation-root-B29qg0_T.js → use-segment-params-C4r4BD9T.js} +129 -5
- package/dist/_chunks/use-segment-params-C4r4BD9T.js.map +1 -0
- package/dist/_chunks/walkers-RzN6AFjr.js +141 -0
- package/dist/_chunks/walkers-RzN6AFjr.js.map +1 -0
- package/dist/adapters/cloudflare-dev.js +1 -1
- package/dist/adapters/cloudflare-kv-cache.js +1 -1
- package/dist/adapters/cloudflare.d.ts.map +1 -1
- package/dist/adapters/cloudflare.js +1 -1
- package/dist/adapters/compress-module.d.ts +12 -0
- package/dist/adapters/compress-module.d.ts.map +1 -1
- package/dist/adapters/nitro.js +54 -2
- package/dist/adapters/nitro.js.map +1 -1
- package/dist/cache/index.js +1 -1
- package/dist/cdn/cloudflare-purge.js +30 -0
- package/dist/cdn/cloudflare-purge.js.map +1 -0
- package/dist/cdn/fastly-purge.js +33 -0
- package/dist/cdn/fastly-purge.js.map +1 -0
- package/dist/cdn/index.js +94 -0
- package/dist/cdn/index.js.map +1 -0
- package/dist/cdn/workers-cache-purge.js +35 -0
- package/dist/cdn/workers-cache-purge.js.map +1 -0
- package/dist/cli-check.d.ts +153 -0
- package/dist/cli-check.d.ts.map +1 -0
- package/dist/cli.d.ts +34 -8
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +46 -21
- package/dist/cli.js.map +1 -1
- package/dist/client/browser-entry/action-dispatch.d.ts.map +1 -1
- package/dist/client/browser-entry/index.d.ts +1 -1
- package/dist/client/browser-entry/index.d.ts.map +1 -1
- package/dist/client/error-boundary.d.ts +6 -0
- package/dist/client/error-boundary.d.ts.map +1 -1
- package/dist/client/error-boundary.js +1 -1
- package/dist/client/index.d.ts +1 -0
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +27 -33
- package/dist/client/index.js.map +1 -1
- package/dist/client/internal.js +33 -17
- package/dist/client/internal.js.map +1 -1
- package/dist/client/link.d.ts +1 -7
- package/dist/client/link.d.ts.map +1 -1
- package/dist/client/navigation-commit.d.ts +16 -0
- package/dist/client/navigation-commit.d.ts.map +1 -1
- package/dist/client/router-pipeline.d.ts.map +1 -1
- package/dist/client/router.d.ts.map +1 -1
- package/dist/client/rsc-fetch.d.ts +9 -1
- package/dist/client/rsc-fetch.d.ts.map +1 -1
- package/dist/client/segment-cache.d.ts +14 -0
- package/dist/client/segment-cache.d.ts.map +1 -1
- package/dist/client/use-query-states.d.ts +9 -3
- package/dist/client/use-query-states.d.ts.map +1 -1
- package/dist/codec.js +1 -1
- package/dist/cookies/define-cookie.d.ts.map +1 -1
- package/dist/cookies/index.js +2 -2
- package/dist/cookies/index.js.map +1 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +142 -477
- package/dist/index.js.map +1 -1
- package/dist/params/index.js +1 -1
- package/dist/plugin-context.d.ts +15 -0
- package/dist/plugin-context.d.ts.map +1 -1
- package/dist/plugins/routing.d.ts +0 -9
- package/dist/plugins/routing.d.ts.map +1 -1
- package/dist/plugins/shims.d.ts.map +1 -1
- package/dist/plugins/static-build.d.ts +24 -0
- package/dist/plugins/static-build.d.ts.map +1 -1
- package/dist/routing/codegen-shared.d.ts +3 -44
- package/dist/routing/codegen-shared.d.ts.map +1 -1
- package/dist/routing/codegen-types.d.ts +10 -31
- package/dist/routing/codegen-types.d.ts.map +1 -1
- package/dist/routing/codegen-write.d.ts +51 -0
- package/dist/routing/codegen-write.d.ts.map +1 -0
- package/dist/routing/codegen.d.ts.map +1 -1
- package/dist/routing/convention-lint.d.ts +18 -4
- package/dist/routing/convention-lint.d.ts.map +1 -1
- package/dist/routing/export-detect.d.ts +16 -0
- package/dist/routing/export-detect.d.ts.map +1 -1
- package/dist/routing/index.js +3 -2
- package/dist/routing/link-codegen.d.ts +19 -4
- package/dist/routing/link-codegen.d.ts.map +1 -1
- package/dist/routing/manifest-codegen.d.ts +1 -7
- package/dist/routing/manifest-codegen.d.ts.map +1 -1
- package/dist/routing/segment-keys.d.ts +22 -0
- package/dist/routing/segment-keys.d.ts.map +1 -1
- package/dist/routing/types.d.ts +0 -6
- package/dist/routing/types.d.ts.map +1 -1
- package/dist/schema-bridge.d.ts +60 -9
- package/dist/schema-bridge.d.ts.map +1 -1
- package/dist/search-params/define.d.ts +62 -8
- package/dist/search-params/define.d.ts.map +1 -1
- package/dist/search-params/index.d.ts +0 -1
- package/dist/search-params/index.d.ts.map +1 -1
- package/dist/search-params/index.js +66 -29
- package/dist/search-params/index.js.map +1 -1
- package/dist/search-params/parse-total.d.ts +70 -0
- package/dist/search-params/parse-total.d.ts.map +1 -0
- package/dist/search-params/wrappers.d.ts +26 -3
- package/dist/search-params/wrappers.d.ts.map +1 -1
- package/dist/server/access-gate.d.ts +19 -8
- package/dist/server/access-gate.d.ts.map +1 -1
- package/dist/server/action-handler.d.ts.map +1 -1
- package/dist/server/als-registry.d.ts +16 -0
- package/dist/server/als-registry.d.ts.map +1 -1
- package/dist/server/chain-url-parts.d.ts +2 -3
- package/dist/server/chain-url-parts.d.ts.map +1 -1
- package/dist/server/compress.d.ts.map +1 -1
- package/dist/server/deny-boundary.d.ts +148 -15
- package/dist/server/deny-boundary.d.ts.map +1 -1
- package/dist/server/deny-renderer.d.ts +2 -2
- package/dist/server/deny-renderer.d.ts.map +1 -1
- package/dist/server/error-boundary-wrapper.d.ts +85 -15
- package/dist/server/error-boundary-wrapper.d.ts.map +1 -1
- package/dist/server/index.d.ts +0 -1
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +3 -2
- package/dist/server/index.js.map +1 -1
- package/dist/server/internal.d.ts +3 -1
- package/dist/server/internal.d.ts.map +1 -1
- package/dist/server/internal.js +344 -237
- package/dist/server/internal.js.map +1 -1
- package/dist/server/metadata-collector.d.ts +52 -0
- package/dist/server/metadata-collector.d.ts.map +1 -0
- package/dist/server/param-coercion.d.ts +12 -5
- package/dist/server/param-coercion.d.ts.map +1 -1
- package/dist/server/pipeline-helpers.d.ts.map +1 -1
- package/dist/server/pipeline-outcome.d.ts.map +1 -1
- package/dist/server/pipeline-phases.d.ts.map +1 -1
- package/dist/server/primitives.d.ts +23 -0
- package/dist/server/primitives.d.ts.map +1 -1
- package/dist/server/route-element-builder.d.ts +1 -12
- package/dist/server/route-element-builder.d.ts.map +1 -1
- package/dist/server/rsc-cache-key-guard.d.ts.map +1 -1
- package/dist/server/rsc-entry/deny-fallback.d.ts.map +1 -1
- package/dist/server/rsc-entry/error-renderer.d.ts +1 -1
- package/dist/server/rsc-entry/error-renderer.d.ts.map +1 -1
- package/dist/server/rsc-entry/helpers.d.ts +0 -7
- package/dist/server/rsc-entry/helpers.d.ts.map +1 -1
- package/dist/server/rsc-entry/index.d.ts +0 -1
- package/dist/server/rsc-entry/index.d.ts.map +1 -1
- package/dist/server/rsc-entry/render-route.d.ts.map +1 -1
- package/dist/server/rsc-entry/revalidate-renderer.d.ts.map +1 -1
- package/dist/server/rsc-entry/rsc-payload.d.ts +1 -3
- package/dist/server/rsc-entry/rsc-payload.d.ts.map +1 -1
- package/dist/server/rsc-entry/rsc-stream.d.ts +12 -0
- package/dist/server/rsc-entry/rsc-stream.d.ts.map +1 -1
- package/dist/server/rsc-entry/ssr-renderer.d.ts +0 -2
- package/dist/server/rsc-entry/ssr-renderer.d.ts.map +1 -1
- package/dist/server/skippable-prefix.d.ts +3 -0
- package/dist/server/skippable-prefix.d.ts.map +1 -1
- package/dist/server/slot-resolver.d.ts +18 -19
- package/dist/server/slot-resolver.d.ts.map +1 -1
- package/dist/server/ssr-bridge-types.d.ts +11 -0
- package/dist/server/ssr-bridge-types.d.ts.map +1 -1
- package/dist/server/ssr-entry.d.ts +0 -1
- package/dist/server/ssr-entry.d.ts.map +1 -1
- package/dist/server/state-tree-diff.d.ts +9 -16
- package/dist/server/state-tree-diff.d.ts.map +1 -1
- package/dist/server/static-generator.d.ts.map +1 -1
- package/dist/server/status-code-resolver.d.ts +8 -1
- package/dist/server/status-code-resolver.d.ts.map +1 -1
- package/dist/server/tree-builder.d.ts +28 -43
- package/dist/server/tree-builder.d.ts.map +1 -1
- package/dist/server/types.d.ts +12 -7
- package/dist/server/types.d.ts.map +1 -1
- package/dist/server/utils/element-type.d.ts +40 -0
- package/dist/server/utils/element-type.d.ts.map +1 -0
- package/dist/shared/rsc-media-type.d.ts +40 -0
- package/dist/shared/rsc-media-type.d.ts.map +1 -0
- package/dist/shared/segment-info.d.ts +7 -0
- package/dist/shared/segment-info.d.ts.map +1 -1
- package/docs/api/30-api-server.mdx +1 -1
- package/docs/api/33-api-search-params.mdx +38 -16
- package/docs/api/35-api-typescript.mdx +3 -3
- package/docs/api/36-cli.mdx +34 -7
- package/docs/learn/00-introduction.mdx +1 -1
- package/docs/learn/02-pages-and-layouts.mdx +1 -1
- package/docs/learn/05-typed-params.mdx +8 -8
- package/docs/learn/07-typed-routes.mdx +12 -7
- package/docs/learn/11-error-handling.mdx +16 -0
- package/docs/more/01-advanced-routing.mdx +1 -1
- package/docs/more/03-coming-from-nextjs.mdx +2 -2
- package/docs/more/50-ai-agent-instructions.mdx +6 -4
- package/package.json +8 -5
- package/src/adapters/cloudflare.ts +4 -1
- package/src/adapters/compress-module.ts +79 -1
- package/src/cli-check.ts +458 -0
- package/src/cli.ts +59 -24
- package/src/client/browser-entry/action-dispatch.ts +2 -1
- package/src/client/browser-entry/index.ts +0 -5
- package/src/client/error-boundary.tsx +65 -1
- package/src/client/index.ts +14 -3
- package/src/client/link.tsx +65 -64
- package/src/client/navigation-commit.ts +27 -4
- package/src/client/params-context.ts +4 -4
- package/src/client/router-pipeline.ts +4 -0
- package/src/client/router.ts +1 -0
- package/src/client/rsc-fetch.ts +14 -4
- package/src/client/segment-cache.ts +26 -5
- package/src/client/use-query-states.ts +102 -39
- package/src/cookies/define-cookie.ts +6 -1
- package/src/index.ts +20 -3
- package/src/plugin-context.ts +26 -0
- package/src/plugins/routing.ts +84 -146
- package/src/plugins/shims.ts +0 -1
- package/src/plugins/static-build.ts +78 -24
- package/src/routing/codegen-shared.ts +3 -79
- package/src/routing/codegen-types.ts +10 -31
- package/src/routing/codegen-write.ts +139 -0
- package/src/routing/codegen.ts +56 -182
- package/src/routing/convention-lint.ts +139 -40
- package/src/routing/export-detect.ts +151 -7
- package/src/routing/link-codegen.ts +32 -65
- package/src/routing/manifest-codegen.ts +1 -59
- package/src/routing/scanner.ts +3 -3
- package/src/routing/segment-keys.ts +37 -0
- package/src/routing/types.ts +0 -6
- package/src/schema-bridge.ts +180 -58
- package/src/search-params/define.ts +102 -37
- package/src/search-params/index.ts +0 -1
- package/src/search-params/parse-total.ts +78 -0
- package/src/search-params/wrappers.ts +60 -11
- package/src/server/access-gate.tsx +61 -68
- package/src/server/action-handler.ts +1 -4
- package/src/server/als-registry.ts +16 -0
- package/src/server/chain-url-parts.ts +2 -3
- package/src/server/compress.ts +9 -1
- package/src/server/deny-boundary.ts +269 -41
- package/src/server/deny-renderer.ts +32 -21
- package/src/server/error-boundary-wrapper.ts +166 -79
- package/src/server/index.ts +1 -3
- package/src/server/internal.ts +2 -2
- package/src/server/metadata-collector.ts +115 -0
- package/src/server/param-coercion.ts +13 -61
- package/src/server/pipeline-helpers.ts +2 -2
- package/src/server/pipeline-outcome.ts +2 -1
- package/src/server/pipeline-phases.ts +9 -9
- package/src/server/primitives.ts +25 -0
- package/src/server/route-element-builder.ts +169 -194
- package/src/server/rsc-cache-key-guard.ts +2 -8
- package/src/server/rsc-entry/deny-fallback.ts +3 -2
- package/src/server/rsc-entry/error-renderer.ts +31 -11
- package/src/server/rsc-entry/helpers.ts +3 -12
- package/src/server/rsc-entry/index.ts +0 -5
- package/src/server/rsc-entry/render-route.ts +14 -11
- package/src/server/rsc-entry/revalidate-renderer.ts +2 -1
- package/src/server/rsc-entry/rsc-payload.ts +104 -20
- package/src/server/rsc-entry/rsc-stream.ts +25 -2
- package/src/server/rsc-entry/ssr-renderer.ts +28 -16
- package/src/server/skippable-prefix.ts +21 -39
- package/src/server/slot-resolver.ts +69 -189
- package/src/server/ssr-bridge-types.ts +12 -0
- package/src/server/ssr-entry.ts +8 -6
- package/src/server/state-tree-diff.ts +11 -62
- package/src/server/static-generator.ts +3 -2
- package/src/server/status-code-resolver.ts +28 -11
- package/src/server/tree-builder.ts +35 -228
- package/src/server/types.ts +12 -7
- package/src/server/utils/element-type.ts +72 -0
- package/src/shared/rsc-media-type.ts +43 -0
- package/src/shared/segment-info.ts +7 -0
- package/dist/_chunks/cache-api-Cd0VZ_Pd.js.map +0 -1
- package/dist/_chunks/cli-schema-sync-CKgHC2MB.js.map +0 -1
- package/dist/_chunks/error-boundary-DpYRI_I1.js.map +0 -1
- package/dist/_chunks/logger-N7e5auP0.js.map +0 -1
- package/dist/_chunks/navigation-root-B29qg0_T.js.map +0 -1
- package/dist/_chunks/param-value-C8TNYchQ.js.map +0 -1
- package/dist/_chunks/registry-DbJPKoBp.js +0 -20
- package/dist/_chunks/registry-DbJPKoBp.js.map +0 -1
- package/dist/_chunks/schema-bridge-DT_Tn0Xf.js +0 -119
- package/dist/_chunks/schema-bridge-DT_Tn0Xf.js.map +0 -1
- package/dist/_chunks/segment-context-ZDnXDkbz.js +0 -34
- package/dist/_chunks/segment-context-ZDnXDkbz.js.map +0 -1
- package/dist/_chunks/use-query-states-DFvWd-EA.js.map +0 -1
- package/dist/_chunks/use-segment-params-ClyUNq4d.js +0 -128
- package/dist/_chunks/use-segment-params-ClyUNq4d.js.map +0 -1
- package/dist/_chunks/walkers-BhhwI9TD.js +0 -936
- package/dist/_chunks/walkers-BhhwI9TD.js.map +0 -1
- package/dist/search-params/registry.d.ts +0 -20
- package/dist/search-params/registry.d.ts.map +0 -1
- package/dist/segment-params/define.d.ts +0 -83
- package/dist/segment-params/define.d.ts.map +0 -1
- package/dist/segment-params/index.d.ts +0 -3
- package/dist/segment-params/index.d.ts.map +0 -1
- package/dist/segment-params/index.js +0 -70
- package/dist/segment-params/index.js.map +0 -1
- package/src/search-params/registry.ts +0 -31
- package/src/segment-params/define.ts +0 -226
- package/src/segment-params/index.ts +0 -9
|
@@ -15,14 +15,32 @@ import type {
|
|
|
15
15
|
SearchParamField,
|
|
16
16
|
} from './define.js';
|
|
17
17
|
import { isCodec, isStandardSchema, fromSchema } from '../schema-bridge.js';
|
|
18
|
+
import { parseTotal } from './parse-total.js';
|
|
18
19
|
|
|
19
20
|
// ---------------------------------------------------------------------------
|
|
20
21
|
// withDefault
|
|
21
22
|
// ---------------------------------------------------------------------------
|
|
22
23
|
|
|
23
24
|
/**
|
|
24
|
-
* Wrap a nullable codec with a default value.
|
|
25
|
-
*
|
|
25
|
+
* Wrap a nullable codec with a default value. The output type becomes
|
|
26
|
+
* non-nullable, and the wrapper is TOTAL over `string | string[] |
|
|
27
|
+
* undefined` even when the inner codec is not.
|
|
28
|
+
*
|
|
29
|
+
* The default is substituted for `null` — the documented "no value"
|
|
30
|
+
* answer — and for `undefined`, which is what an implicitly-optional
|
|
31
|
+
* Standard Schema field and a bare `parseAsString` produce for an absent
|
|
32
|
+
* param. Substituting only for `null` left a field typed non-nullable
|
|
33
|
+
* `string` holding `undefined` (TIM-1350).
|
|
34
|
+
*
|
|
35
|
+
* The inner codec is invoked through `parseTotal`, so a nuqs parser gets
|
|
36
|
+
* its own normalization (absent → null, repeated → first value, inner
|
|
37
|
+
* throw → null) before either check applies. That is what makes
|
|
38
|
+
* `withDefault(parseAsBoolean, false)` total; the wrapper itself does NOT
|
|
39
|
+
* catch. A throw from a codec is a deliberate signal — `fromSchema` throws
|
|
40
|
+
* on an async schema, and an app codec may `redirect()` or `notFound()` on
|
|
41
|
+
* a value it refuses — and swallowing it would convert a loud failure into
|
|
42
|
+
* a permanently-default field. design/09 §"The SearchParamCodec Protocol":
|
|
43
|
+
* a codec that throws bubbles as a render-phase error, by design.
|
|
26
44
|
*
|
|
27
45
|
* Works with any codec — nuqs parsers, custom codecs, fromSchema results.
|
|
28
46
|
*
|
|
@@ -34,20 +52,35 @@ import { isCodec, isStandardSchema, fromSchema } from '../schema-bridge.js';
|
|
|
34
52
|
* // page.parse(undefined) → 1 (not null)
|
|
35
53
|
* // page.parse('5') → 5
|
|
36
54
|
* ```
|
|
55
|
+
*
|
|
56
|
+
* The signature strips BOTH nullish constituents from the result, not just
|
|
57
|
+
* `null`, because the runtime substitutes for both. An implicitly-optional
|
|
58
|
+
* schema field is a `SearchParamCodec<string | undefined>`, and wrapping it
|
|
59
|
+
* used to yield `SearchParamCodec<string | undefined>` — a field the
|
|
60
|
+
* wrapper guarantees is always present, still typed as maybe-absent.
|
|
37
61
|
*/
|
|
38
62
|
export function withDefault<T>(
|
|
39
|
-
codec: SearchParamCodec<T | null>,
|
|
40
|
-
defaultValue: T
|
|
41
|
-
): SearchParamCodec<T
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
63
|
+
codec: SearchParamCodec<T | null | undefined>,
|
|
64
|
+
defaultValue: NonNullable<T>
|
|
65
|
+
): SearchParamCodec<NonNullable<T>> {
|
|
66
|
+
// A wrapper that rebuilds a codec from two methods drops everything else
|
|
67
|
+
// the codec carries. `withUrlKey` lost `parseServerSide` that way; this
|
|
68
|
+
// one lost `urlKey`, so `withDefault(withUrlKey(parseAsInteger, 'p'), 1)`
|
|
69
|
+
// silently read `?page=` instead of `?p=`. Carry it explicitly rather
|
|
70
|
+
// than spreading: this wrapper's own `parse` IS the total entry point,
|
|
71
|
+
// and re-publishing the inner `parseServerSide` would let `parseTotal`
|
|
72
|
+
// reach past it and skip the default entirely.
|
|
73
|
+
const wrapped: SearchParamCodec<NonNullable<T>> = {
|
|
74
|
+
parse(value: string | string[] | undefined): NonNullable<T> {
|
|
75
|
+
const result = parseTotal(codec, value);
|
|
76
|
+
return result === null || result === undefined ? defaultValue : result;
|
|
46
77
|
},
|
|
47
|
-
serialize(value: T): string | null {
|
|
78
|
+
serialize(value: NonNullable<T>): string | null {
|
|
48
79
|
return codec.serialize(value);
|
|
49
80
|
},
|
|
50
81
|
};
|
|
82
|
+
if (codec.urlKey !== undefined) wrapped.urlKey = codec.urlKey;
|
|
83
|
+
return wrapped;
|
|
51
84
|
}
|
|
52
85
|
|
|
53
86
|
// ---------------------------------------------------------------------------
|
|
@@ -93,9 +126,25 @@ export function withUrlKey<F extends SearchParamField>(
|
|
|
93
126
|
: isStandardSchema(codecOrSchema)
|
|
94
127
|
? (fromSchema(codecOrSchema) as SearchParamCodec<T>)
|
|
95
128
|
: (codecOrSchema as SearchParamCodec<T>);
|
|
96
|
-
|
|
129
|
+
// Carry the total entry point, bound like the other two methods. A
|
|
130
|
+
// codec's totality lives in a THIRD property, `parseServerSide` (see
|
|
131
|
+
// parse-total.ts); picking only `parse`/`serialize` silently produced a
|
|
132
|
+
// non-total alias, so `withUrlKey(parseAsBoolean, 'b')` threw on an
|
|
133
|
+
// absent param while the bare parser did not (TIM-1350).
|
|
134
|
+
//
|
|
135
|
+
// Bound explicitly rather than spread: a spread copies own enumerable
|
|
136
|
+
// properties only, so a class-based codec — whose `parse` and
|
|
137
|
+
// `serialize` this wrapper already reaches through the prototype —
|
|
138
|
+
// would have kept its methods and lost exactly the one that makes it
|
|
139
|
+
// total. Three properties, one rule, no dependence on how the inner
|
|
140
|
+
// codec was constructed.
|
|
141
|
+
const wrapped: SearchParamCodecWithUrlKey<T> = {
|
|
97
142
|
parse: codec.parse.bind(codec),
|
|
98
143
|
serialize: codec.serialize.bind(codec),
|
|
99
144
|
urlKey,
|
|
100
145
|
};
|
|
146
|
+
if (typeof codec.parseServerSide === 'function') {
|
|
147
|
+
wrapped.parseServerSide = codec.parseServerSide.bind(codec);
|
|
148
|
+
}
|
|
149
|
+
return wrapped;
|
|
101
150
|
}
|
|
@@ -18,7 +18,7 @@ import type { AccessGateProps, SlotAccessGateProps } from './tree-builder.js';
|
|
|
18
18
|
import { withSpan, setSpanAttribute } from './tracing.js';
|
|
19
19
|
import { isDebug } from './debug.js';
|
|
20
20
|
import type { DenyPageEntry } from './deny-boundary.js';
|
|
21
|
-
import {
|
|
21
|
+
import { handleCaughtDeny, setFirstDeniedSegmentIndex } from './deny-boundary.js';
|
|
22
22
|
import type { ReactNode } from 'react';
|
|
23
23
|
|
|
24
24
|
// ─── AccessGate ─────────────────────────────────────────────────────────────
|
|
@@ -26,50 +26,64 @@ import type { ReactNode } from 'react';
|
|
|
26
26
|
/**
|
|
27
27
|
* Framework-injected access gate for segments.
|
|
28
28
|
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
* When no verdict is provided (backward compat with tree-builder.ts),
|
|
35
|
-
* AccessGate calls accessFn directly with OTEL instrumentation.
|
|
29
|
+
* Calls accessFn during render — inside `renderToReadableStream`, sharing
|
|
30
|
+
* the same `React.cache` scope as layouts and pages. A `requireUser()`
|
|
31
|
+
* call in access.ts populates the cache; the same call in any layout is
|
|
32
|
+
* a free hit. Access executes exactly once per request. See TIM-1363.
|
|
36
33
|
*
|
|
37
34
|
* access.ts is a pure gate — return values are discarded. The layout below
|
|
38
35
|
* gets the same data by calling the same cached functions (React.cache dedup).
|
|
36
|
+
*
|
|
37
|
+
* On denial the gate renders the matching deny page only when this segment
|
|
38
|
+
* owns it. Because the gate is wrapped OUTSIDE its own layout, that render
|
|
39
|
+
* omits the denying segment's layout — deliberate, and the one place the
|
|
40
|
+
* in-tree path diverges from `renderDenyPage`'s inclusive slice: a layout
|
|
41
|
+
* whose access.ts denied must never run and serialize protected data into
|
|
42
|
+
* the Flight stream (TIM-1074). An ancestor-owned page is hoisted instead,
|
|
43
|
+
* because the gate also sits inside every layout above it and rendering
|
|
44
|
+
* here would wrap the deny page in all of them (TIM-1356).
|
|
45
|
+
*
|
|
46
|
+
* For skipped segments (skippedSegment=true), deny pages are NOT rendered
|
|
47
|
+
* in-tree — the client's cached layout chrome would wrap them incorrectly.
|
|
48
|
+
* Instead, the deny is recorded in ALS and rethrown so the response path
|
|
49
|
+
* can return rscErrorEnvelope for a client hard-navigate.
|
|
39
50
|
*/
|
|
40
|
-
export function AccessGate(props: AccessGateProps):
|
|
41
|
-
const {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
if (denyElement) {
|
|
52
|
-
setDenyStatus(verdict.status);
|
|
53
|
-
return denyElement;
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
throw verdict;
|
|
57
|
-
}
|
|
51
|
+
export function AccessGate(props: AccessGateProps): Promise<ReactNode> {
|
|
52
|
+
const {
|
|
53
|
+
accessFn,
|
|
54
|
+
segmentName,
|
|
55
|
+
segmentKey,
|
|
56
|
+
segmentIndex,
|
|
57
|
+
denyPages,
|
|
58
|
+
skippedSegment,
|
|
59
|
+
onSettled,
|
|
60
|
+
children,
|
|
61
|
+
} = props;
|
|
58
62
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
+
return accessGateFallback(
|
|
64
|
+
accessFn,
|
|
65
|
+
segmentName,
|
|
66
|
+
segmentKey,
|
|
67
|
+
segmentIndex,
|
|
68
|
+
skippedSegment ? undefined : denyPages,
|
|
69
|
+
skippedSegment,
|
|
70
|
+
onSettled,
|
|
71
|
+
children
|
|
72
|
+
);
|
|
63
73
|
}
|
|
64
74
|
|
|
65
75
|
/**
|
|
66
|
-
*
|
|
67
|
-
*
|
|
76
|
+
* Calls accessFn during render with OTEL instrumentation.
|
|
77
|
+
* Records the first denied segment index in ALS for post-render consumers.
|
|
68
78
|
*/
|
|
69
79
|
async function accessGateFallback(
|
|
70
80
|
accessFn: AccessGateProps['accessFn'],
|
|
71
81
|
segmentName: AccessGateProps['segmentName'],
|
|
82
|
+
segmentKey: AccessGateProps['segmentKey'],
|
|
83
|
+
segmentIndex: AccessGateProps['segmentIndex'],
|
|
72
84
|
denyPages: DenyPageEntry[] | undefined,
|
|
85
|
+
skippedSegment: boolean | undefined,
|
|
86
|
+
onSettled: (() => void) | undefined,
|
|
73
87
|
children: ReactNode
|
|
74
88
|
): Promise<ReactNode> {
|
|
75
89
|
try {
|
|
@@ -91,17 +105,23 @@ async function accessGateFallback(
|
|
|
91
105
|
}
|
|
92
106
|
});
|
|
93
107
|
} catch (error: unknown) {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
if (
|
|
100
|
-
|
|
101
|
-
|
|
108
|
+
if (error instanceof DenySignal) {
|
|
109
|
+
if (segmentIndex !== undefined) {
|
|
110
|
+
setFirstDeniedSegmentIndex(segmentIndex);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (skippedSegment) {
|
|
114
|
+
error._hardNavigate = true;
|
|
115
|
+
throw error;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (denyPages) {
|
|
119
|
+
return handleCaughtDeny(denyPages, error, segmentKey);
|
|
102
120
|
}
|
|
103
121
|
}
|
|
104
122
|
throw error;
|
|
123
|
+
} finally {
|
|
124
|
+
onSettled?.();
|
|
105
125
|
}
|
|
106
126
|
|
|
107
127
|
return children;
|
|
@@ -124,35 +144,8 @@ async function accessGateFallback(
|
|
|
124
144
|
* slot doesn't make architectural sense.
|
|
125
145
|
*/
|
|
126
146
|
export async function SlotAccessGate(props: SlotAccessGateProps): Promise<ReactNode> {
|
|
127
|
-
const { accessFn, DeniedComponent, slotName, createElement, defaultFallback, children
|
|
128
|
-
props;
|
|
129
|
-
|
|
130
|
-
// Fast path: replay pre-computed verdict from eager evaluation in resolveSlotProps.
|
|
131
|
-
if (verdict !== undefined) {
|
|
132
|
-
if (verdict === 'pass') return children;
|
|
133
|
-
if (verdict instanceof DenySignal) {
|
|
134
|
-
return (
|
|
135
|
-
buildDeniedFallback(DeniedComponent, slotName, verdict.data, createElement) ??
|
|
136
|
-
defaultFallback ??
|
|
137
|
-
null
|
|
138
|
-
);
|
|
139
|
-
}
|
|
140
|
-
// RedirectSignal: treat as deny in production (same as existing behavior)
|
|
141
|
-
if (isDebug()) {
|
|
142
|
-
console.error(
|
|
143
|
-
'[timber] redirect() is not allowed in slot access.ts. ' +
|
|
144
|
-
'Slots use deny() for graceful degradation — denied.tsx → default.tsx → null. ' +
|
|
145
|
-
"If you need to redirect, move the logic to the parent segment's access.ts."
|
|
146
|
-
);
|
|
147
|
-
}
|
|
148
|
-
return (
|
|
149
|
-
buildDeniedFallback(DeniedComponent, slotName, undefined, createElement) ??
|
|
150
|
-
defaultFallback ??
|
|
151
|
-
null
|
|
152
|
-
);
|
|
153
|
-
}
|
|
147
|
+
const { accessFn, DeniedComponent, slotName, createElement, defaultFallback, children } = props;
|
|
154
148
|
|
|
155
|
-
// Fallback path: call accessFn during render (no pre-computed verdict).
|
|
156
149
|
try {
|
|
157
150
|
await accessFn();
|
|
158
151
|
} catch (error: unknown) {
|
|
@@ -36,6 +36,7 @@ import {
|
|
|
36
36
|
import type { FormFlashData } from './form-flash.js';
|
|
37
37
|
import { checkVersionSkew, applyReloadHeaders } from './version-skew.js';
|
|
38
38
|
import { logActionError } from './logger.js';
|
|
39
|
+
import { RSC_CONTENT_TYPE } from '../shared/rsc-media-type.js';
|
|
39
40
|
|
|
40
41
|
// ─── Types ────────────────────────────────────────────────────────────────
|
|
41
42
|
|
|
@@ -55,10 +56,6 @@ export interface ActionDispatchConfig {
|
|
|
55
56
|
sensitiveFields?: SensitiveFieldsOption;
|
|
56
57
|
}
|
|
57
58
|
|
|
58
|
-
// ─── Constants ────────────────────────────────────────────────────────────
|
|
59
|
-
|
|
60
|
-
const RSC_CONTENT_TYPE = 'text/x-component';
|
|
61
|
-
|
|
62
59
|
// ─── Detection ────────────────────────────────────────────────────────────
|
|
63
60
|
|
|
64
61
|
/**
|
|
@@ -141,6 +141,22 @@ export interface RequestContextStore {
|
|
|
141
141
|
* context for render-phase errors without module-level shared state.
|
|
142
142
|
*/
|
|
143
143
|
debugComponentsGetter?: () => DebugComponentEntry[];
|
|
144
|
+
/**
|
|
145
|
+
* Index of the first segment whose AccessGate denied during render.
|
|
146
|
+
* Recorded by AccessGate when it catches a DenySignal so post-render
|
|
147
|
+
* consumers (buildSegmentInfo, X-Timber-Segments header) can bound
|
|
148
|
+
* the segment list without a pre-render access pass. Infinity when
|
|
149
|
+
* no segment denied. See TIM-1363.
|
|
150
|
+
*/
|
|
151
|
+
_firstDeniedSegmentIndex?: number;
|
|
152
|
+
/**
|
|
153
|
+
* Metadata entries collected during render by MetadataCollector.
|
|
154
|
+
* Populated per-segment in tree order (root→page) during
|
|
155
|
+
* renderToReadableStream. MetadataHead reads all entries,
|
|
156
|
+
* merges them, and renders Float-hoisted head elements.
|
|
157
|
+
* See TIM-1363, design/16-metadata.md.
|
|
158
|
+
*/
|
|
159
|
+
_metadataEntries?: import('./metadata.js').SegmentMetadataEntry[];
|
|
144
160
|
}
|
|
145
161
|
|
|
146
162
|
/** A single outgoing cookie entry in the cookie jar. */
|
|
@@ -7,9 +7,8 @@
|
|
|
7
7
|
* - the layout depth handed to `SegmentProvider` (`route-element-builder.ts`),
|
|
8
8
|
* which `useSelectedLayoutSegment(s)` indexes the live pathname with;
|
|
9
9
|
* - `slotUrlParts`, which slices the destination pathname for slot matching;
|
|
10
|
-
* - `resolveSlotProps`, which
|
|
11
|
-
*
|
|
12
|
-
* - `skippable-prefix.ts`, which does the same for the contiguous skip prefix.
|
|
10
|
+
* - `resolveSlotProps` and `skippable-prefix.ts`, which derive content keys
|
|
11
|
+
* for slot skip decisions (TIM-1370).
|
|
13
12
|
*
|
|
14
13
|
* They were four separate `extractUrlParts(...).length` calls. That was fine
|
|
15
14
|
* while the answer was a plain walk, and stopped being fine when interception
|
package/src/server/compress.ts
CHANGED
|
@@ -7,6 +7,14 @@
|
|
|
7
7
|
// this module is only used on Node.js/Bun.
|
|
8
8
|
//
|
|
9
9
|
// See design/25-production-deployments.md.
|
|
10
|
+
//
|
|
11
|
+
// This module is also emitted standalone as `_compress.mjs` by
|
|
12
|
+
// `adapters/compress-module.ts`, which strips types but does not bundle. Its
|
|
13
|
+
// imports must therefore be interpolated there — a new import that is not
|
|
14
|
+
// fails the build with an explanatory error rather than resolving to nothing
|
|
15
|
+
// on the deployed server.
|
|
16
|
+
|
|
17
|
+
import { RSC_CONTENT_TYPE } from '../shared/rsc-media-type.js';
|
|
10
18
|
|
|
11
19
|
// ─── Constants ────────────────────────────────────────────────────────────
|
|
12
20
|
|
|
@@ -21,7 +29,7 @@ export const COMPRESSIBLE_TYPES = new Set([
|
|
|
21
29
|
'text/plain',
|
|
22
30
|
'text/xml',
|
|
23
31
|
'text/javascript',
|
|
24
|
-
|
|
32
|
+
RSC_CONTENT_TYPE,
|
|
25
33
|
'application/json',
|
|
26
34
|
'application/javascript',
|
|
27
35
|
'application/xml',
|