@timber-js/app 0.2.0-alpha.209 → 0.2.0-alpha.210
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-BerlqoXA.js → actions-Rjk4htmA.js} +19 -25
- package/dist/_chunks/{actions-BerlqoXA.js.map → actions-Rjk4htmA.js.map} +1 -1
- package/dist/_chunks/als-registry-DaxkVjt5.js.map +1 -1
- package/dist/_chunks/{cache-api-CR23J_NC.js → cache-api-DGdYfNJn.js} +4 -4
- package/dist/_chunks/{cache-api-CR23J_NC.js.map → cache-api-DGdYfNJn.js.map} +1 -1
- package/dist/_chunks/{chains-CpFg56UB.js → chains-BoO51joc.js} +2 -2
- package/dist/_chunks/{chains-CpFg56UB.js.map → chains-BoO51joc.js.map} +1 -1
- package/dist/_chunks/{cli-check-C6Ev6wBO.js → cli-check-ajNY3B2e.js} +3 -3
- package/dist/_chunks/{cli-check-C6Ev6wBO.js.map → cli-check-ajNY3B2e.js.map} +1 -1
- package/dist/_chunks/{cli-schema-sync-CbT2AUUI.js → cli-schema-sync-D2eI8jEg.js} +2 -2
- package/dist/_chunks/{cli-schema-sync-CbT2AUUI.js.map → cli-schema-sync-D2eI8jEg.js.map} +1 -1
- package/dist/_chunks/{file-cache-Dw6BJPG7.js → codegen-Bps1sLKJ.js} +3 -29
- package/dist/_chunks/codegen-Bps1sLKJ.js.map +1 -0
- package/dist/_chunks/{convention-lint-jKTwKwPe.js → convention-lint-DLmhGsRS.js} +54 -316
- package/dist/_chunks/convention-lint-DLmhGsRS.js.map +1 -0
- package/dist/_chunks/{dev-server-C4WZdB7L.js → dev-server-v97rQH4b.js} +99 -9
- package/dist/_chunks/dev-server-v97rQH4b.js.map +1 -0
- package/dist/_chunks/{error-boundary-tA7kVfs4.js → error-boundary-DsNScGRM.js} +4 -4
- package/dist/_chunks/{error-boundary-tA7kVfs4.js.map → error-boundary-DsNScGRM.js.map} +1 -1
- package/dist/_chunks/{json-lossy-check-ip0Qi0MT.js → json-lossy-check-CVuRs2hG.js} +2 -2
- package/dist/_chunks/{json-lossy-check-ip0Qi0MT.js.map → json-lossy-check-CVuRs2hG.js.map} +1 -1
- package/dist/_chunks/{live-graph-Dv-JJCZw.js → live-graph-9cSnn_h9.js} +3 -3
- package/dist/_chunks/{live-graph-Dv-JJCZw.js.map → live-graph-9cSnn_h9.js.map} +1 -1
- package/dist/_chunks/{logger-DiDt5ppH.js → logger-BP0LN6vP.js} +17 -2
- package/dist/_chunks/{logger-DiDt5ppH.js.map → logger-BP0LN6vP.js.map} +1 -1
- package/dist/_chunks/metadata-routes-DSDjM_hJ.js.map +1 -1
- package/dist/_chunks/navigation-root-BQfo1-kG.js.map +1 -1
- package/dist/_chunks/{poison-scan-CpeT6_OJ.js → poison-scan-vGV7Re0B.js} +2 -2
- package/dist/_chunks/{poison-scan-CpeT6_OJ.js.map → poison-scan-vGV7Re0B.js.map} +1 -1
- package/dist/_chunks/{scanner-Bw0oq1HB.js → scanner-DmqdxzbW.js} +392 -7
- package/dist/_chunks/scanner-DmqdxzbW.js.map +1 -0
- package/dist/_chunks/segment-classify-C539Pa2O.js.map +1 -1
- package/dist/_chunks/{sizeof-UwzwB1uM.js → sizeof-BM1409x2.js} +2 -2
- package/dist/_chunks/{sizeof-UwzwB1uM.js.map → sizeof-BM1409x2.js.map} +1 -1
- package/dist/_chunks/{status-page-marker-gaihi0KZ.js → status-page-marker-BRX9Ib-d.js} +1 -45
- package/dist/_chunks/status-page-marker-BRX9Ib-d.js.map +1 -0
- package/dist/_chunks/{walkers-BXExhzzk.js → walkers-Czu2jXFq.js} +3 -3
- package/dist/_chunks/{walkers-BXExhzzk.js.map → walkers-Czu2jXFq.js.map} +1 -1
- package/dist/adapters/cloudflare-kv-cache.js +1 -1
- package/dist/analyze/crawl-entry.js +2 -2
- package/dist/analyze/graph-command.js +2 -2
- package/dist/cache/index.js +2 -2
- package/dist/cache/stores/memory.js +1 -1
- package/dist/cli.js +3 -3
- package/dist/client/browser-entry/post-hydration.d.ts.map +1 -1
- package/dist/client/browser-entry/router-init.d.ts.map +1 -1
- package/dist/client/error-boundary.js +1 -1
- package/dist/client/history.d.ts +0 -9
- package/dist/client/history.d.ts.map +1 -1
- package/dist/client/index.d.ts +2 -1
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +9 -3
- package/dist/client/index.js.map +1 -1
- package/dist/client/internal.js +25 -38
- package/dist/client/internal.js.map +1 -1
- package/dist/client/link.d.ts +22 -0
- package/dist/client/link.d.ts.map +1 -1
- package/dist/client/navigation-api.d.ts +14 -2
- package/dist/client/navigation-api.d.ts.map +1 -1
- package/dist/client/navigation-root.d.ts +9 -1
- package/dist/client/navigation-root.d.ts.map +1 -1
- package/dist/client/navigation-transition.d.ts +6 -1
- package/dist/client/navigation-transition.d.ts.map +1 -1
- package/dist/client/react-root.d.ts.map +1 -1
- package/dist/client/router-effects.d.ts +10 -3
- package/dist/client/router-effects.d.ts.map +1 -1
- package/dist/client/router-pipeline.d.ts +3 -1
- package/dist/client/router-pipeline.d.ts.map +1 -1
- package/dist/client/router-types.d.ts +65 -9
- package/dist/client/router-types.d.ts.map +1 -1
- package/dist/client/router.d.ts.map +1 -1
- package/dist/client/segment-cache.d.ts +0 -15
- package/dist/client/segment-cache.d.ts.map +1 -1
- package/dist/client/use-router.d.ts +13 -6
- package/dist/client/use-router.d.ts.map +1 -1
- package/dist/config-validation.d.ts +19 -2
- package/dist/config-validation.d.ts.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -7
- package/dist/index.js.map +1 -1
- package/dist/routing/convention-lint.d.ts.map +1 -1
- package/dist/routing/export-detect.d.ts +19 -0
- package/dist/routing/export-detect.d.ts.map +1 -1
- package/dist/routing/index.js +3 -3
- package/dist/routing/manifest-codegen.d.ts.map +1 -1
- package/dist/routing/scanner.d.ts.map +1 -1
- package/dist/routing/types.d.ts +7 -0
- package/dist/routing/types.d.ts.map +1 -1
- package/dist/server/action-handler.d.ts +1 -1
- package/dist/server/action-handler.d.ts.map +1 -1
- package/dist/server/actions.d.ts +9 -4
- package/dist/server/actions.d.ts.map +1 -1
- package/dist/server/csrf.d.ts +38 -19
- package/dist/server/csrf.d.ts.map +1 -1
- package/dist/server/error-boundary-wrapper.d.ts +8 -4
- package/dist/server/error-boundary-wrapper.d.ts.map +1 -1
- package/dist/server/fallback-error.d.ts.map +1 -1
- package/dist/server/form-flash.d.ts +1 -1
- package/dist/server/index.js +2 -2
- package/dist/server/index.js.map +1 -1
- package/dist/server/internal.js +249 -16
- package/dist/server/internal.js.map +1 -1
- package/dist/server/logger.d.ts +16 -3
- package/dist/server/logger.d.ts.map +1 -1
- package/dist/server/metadata-collector.d.ts +2 -0
- package/dist/server/metadata-collector.d.ts.map +1 -1
- package/dist/server/metadata-routes.d.ts +12 -1
- package/dist/server/metadata-routes.d.ts.map +1 -1
- package/dist/server/pipeline-helpers.d.ts +29 -1
- package/dist/server/pipeline-helpers.d.ts.map +1 -1
- package/dist/server/pipeline.d.ts +8 -1
- package/dist/server/pipeline.d.ts.map +1 -1
- package/dist/server/route-element-builder.d.ts.map +1 -1
- package/dist/server/route-matcher.d.ts +8 -0
- package/dist/server/route-matcher.d.ts.map +1 -1
- package/dist/server/rsc-entry/{wrap-action-dispatch.d.ts → action-dispatcher.d.ts} +18 -40
- package/dist/server/rsc-entry/action-dispatcher.d.ts.map +1 -0
- package/dist/server/rsc-entry/index.d.ts.map +1 -1
- package/dist/server/safe-load.d.ts +5 -12
- package/dist/server/safe-load.d.ts.map +1 -1
- package/docs/api/30-api-server.mdx +1 -1
- package/docs/api/31-api-client.mdx +23 -20
- package/docs/api/34-api-config.mdx +4 -2
- package/package.json +1 -1
- package/src/client/browser-entry/action-dispatch.ts +28 -28
- package/src/client/browser-entry/post-hydration.ts +11 -1
- package/src/client/browser-entry/router-init.ts +10 -4
- package/src/client/history.ts +0 -22
- package/src/client/index.ts +2 -1
- package/src/client/link.tsx +30 -1
- package/src/client/navigation-api.ts +36 -3
- package/src/client/navigation-root.tsx +13 -1
- package/src/client/navigation-transition.ts +17 -7
- package/src/client/react-root.ts +11 -2
- package/src/client/router-effects.ts +11 -4
- package/src/client/router-pipeline.ts +4 -0
- package/src/client/router-types.ts +80 -6
- package/src/client/router.ts +36 -24
- package/src/client/segment-cache.ts +0 -65
- package/src/client/use-router.ts +25 -6
- package/src/config-validation.ts +121 -5
- package/src/index.ts +5 -8
- package/src/routing/convention-lint.ts +75 -0
- package/src/routing/export-detect.ts +88 -0
- package/src/routing/manifest-codegen.ts +6 -0
- package/src/routing/scanner.ts +9 -0
- package/src/routing/types.ts +7 -0
- package/src/server/action-handler.ts +14 -11
- package/src/server/actions.ts +37 -42
- package/src/server/als-registry.ts +1 -1
- package/src/server/csrf.ts +100 -72
- package/src/server/error-boundary-wrapper.ts +11 -12
- package/src/server/fallback-error.ts +13 -21
- package/src/server/form-flash.ts +1 -1
- package/src/server/logger.ts +19 -3
- package/src/server/metadata-collector.ts +4 -1
- package/src/server/metadata-routes.ts +23 -8
- package/src/server/pipeline-helpers.ts +89 -8
- package/src/server/pipeline.ts +27 -2
- package/src/server/route-element-builder.ts +1 -0
- package/src/server/route-matcher.ts +11 -0
- package/src/server/rsc-entry/{wrap-action-dispatch.ts → action-dispatcher.ts} +20 -62
- package/src/server/rsc-entry/index.ts +14 -27
- package/src/server/safe-load.ts +5 -12
- package/dist/_chunks/convention-lint-jKTwKwPe.js.map +0 -1
- package/dist/_chunks/dev-server-C4WZdB7L.js.map +0 -1
- package/dist/_chunks/file-cache-Dw6BJPG7.js.map +0 -1
- package/dist/_chunks/scanner-Bw0oq1HB.js.map +0 -1
- package/dist/_chunks/status-page-marker-gaihi0KZ.js.map +0 -1
- package/dist/server/rsc-entry/wrap-action-dispatch.d.ts.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"convention-lint.d.ts","sourceRoot":"","sources":["../../src/routing/convention-lint.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAGH,OAAO,KAAK,EAAa,SAAS,EAAe,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"convention-lint.d.ts","sourceRoot":"","sources":["../../src/routing/convention-lint.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAGH,OAAO,KAAK,EAAa,SAAS,EAAe,MAAM,YAAY,CAAC;AAapE,MAAM,WAAW,iBAAiB;IAChC,kDAAkD;IAClD,EAAE,EAAE,MAAM,CAAC;IACX,0CAA0C;IAC1C,OAAO,EAAE,MAAM,CAAC;IAChB,4DAA4D;IAC5D,OAAO,EAAE,MAAM,CAAC;IAChB,qFAAqF;IACrF,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC;CACzB;AAID;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,GAAG,iBAAiB,EAAE,CAyCpF;AA8dD;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,iBAAiB,GAAG,IAAI,CAe1E;AAUD,MAAM,WAAW,aAAa;IAC5B;;;;OAIG;IACH,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED;;;;GAIG;AACH,wBAAgB,wBAAwB,CACtC,QAAQ,EAAE,iBAAiB,EAAE,EAC7B,OAAO,GAAE,aAAkB,GAC1B,MAAM,CAkCR"}
|
|
@@ -64,6 +64,25 @@ export type PrerenderExportValue = boolean | {
|
|
|
64
64
|
* export const prerender = { ttl: 3600, tags: ['docs'] };
|
|
65
65
|
*/
|
|
66
66
|
export declare function getPrerenderExport(filePath: string): PrerenderExportValue | undefined;
|
|
67
|
+
/**
|
|
68
|
+
* How a file declares the per-route CSRF exemption.
|
|
69
|
+
*
|
|
70
|
+
* - `'absent'` — no runtime export named `csrf`.
|
|
71
|
+
* - `'exempt'` — exactly `export const csrf = false` (wrappers like
|
|
72
|
+
* `false as const` allowed), declared in this file.
|
|
73
|
+
* - `'invalid'` — some other `csrf` export: another value, `let`, a
|
|
74
|
+
* destructured binding, a specifier or named re-export (`export { csrf }`,
|
|
75
|
+
* `export { x as "csrf" }`), a function or class. `export * from` is not
|
|
76
|
+
* followed: a `csrf` it carries is neither exempt nor an error.
|
|
77
|
+
*
|
|
78
|
+
* The exemption is read from source at build time, never from the loaded
|
|
79
|
+
* module, because the CSRF gate runs before any user code (see
|
|
80
|
+
* design/08-forms-and-actions.md §"Per-route exemption"). Only the literal
|
|
81
|
+
* form can be read that way; any other spelling is a build error rather
|
|
82
|
+
* than a silent "not exempt", so what the developer wrote is what runs.
|
|
83
|
+
*/
|
|
84
|
+
export type CsrfExport = 'absent' | 'exempt' | 'invalid';
|
|
85
|
+
export declare function getCsrfExport(filePath: string): CsrfExport;
|
|
67
86
|
/**
|
|
68
87
|
* Check if a file starts with a specific directive (e.g. "use client").
|
|
69
88
|
* Directives are string literal expression statements at the top of the file.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"export-detect.d.ts","sourceRoot":"","sources":["../../src/routing/export-detect.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AA4HH;;;GAGG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAU3E;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,SAAS,MAAM,EAAE,GAAG,GAAG,CAAC,MAAM,CAAC,CAc9F;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAU9D;AAoED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,6BAA6B,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAiCvE;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAU3D;AAED;;;;;;;;GAQG;AACH,MAAM,MAAM,oBAAoB,GAAG,OAAO,GAAG;IAAE,GAAG,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC;AA8C/E;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,oBAAoB,GAAG,SAAS,CA+CrF;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAgB7E"}
|
|
1
|
+
{"version":3,"file":"export-detect.d.ts","sourceRoot":"","sources":["../../src/routing/export-detect.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AA4HH;;;GAGG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAU3E;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,SAAS,MAAM,EAAE,GAAG,GAAG,CAAC,MAAM,CAAC,CAc9F;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAU9D;AAoED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,6BAA6B,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAiCvE;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAU3D;AAED;;;;;;;;GAQG;AACH,MAAM,MAAM,oBAAoB,GAAG,OAAO,GAAG;IAAE,GAAG,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC;AA8C/E;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,oBAAoB,GAAG,SAAS,CA+CrF;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,MAAM,UAAU,GAAG,QAAQ,GAAG,QAAQ,GAAG,SAAS,CAAC;AAEzD,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,UAAU,CAsC1D;AA+BD;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAgB7E"}
|
package/dist/routing/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { a as INTERCEPTION_MARKERS, i as DEFAULT_PAGE_EXTENSIONS, n as classifyUrlSegment, t as classifySegment } from "../_chunks/segment-classify-C539Pa2O.js";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { n as collectDynamicSegmentsFromTree, r as validateSchemaAgainstRoutes, t as collectLeafRoutes } from "../_chunks/walkers-
|
|
2
|
+
import { d as collectInterceptionRewrites, n as scanRoutes } from "../_chunks/scanner-DmqdxzbW.js";
|
|
3
|
+
import { t as generateRouteMap } from "../_chunks/codegen-Bps1sLKJ.js";
|
|
4
|
+
import { n as collectDynamicSegmentsFromTree, r as validateSchemaAgainstRoutes, t as collectLeafRoutes } from "../_chunks/walkers-Czu2jXFq.js";
|
|
5
5
|
export { DEFAULT_PAGE_EXTENSIONS, INTERCEPTION_MARKERS, classifySegment, classifyUrlSegment, collectDynamicSegmentsFromTree, collectInterceptionRewrites, collectLeafRoutes, generateRouteMap, scanRoutes, validateSchemaAgainstRoutes };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"manifest-codegen.d.ts","sourceRoot":"","sources":["../../src/routing/manifest-codegen.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAKH,OAAO,KAAK,EAAE,SAAS,EAA0B,MAAM,YAAY,CAAC;AAwBpE;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,
|
|
1
|
+
{"version":3,"file":"manifest-codegen.d.ts","sourceRoot":"","sources":["../../src/routing/manifest-codegen.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAKH,OAAO,KAAK,EAAE,SAAS,EAA0B,MAAM,YAAY,CAAC;AAwBpE;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAmJhF;AAID;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CA8B3D"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scanner.d.ts","sourceRoot":"","sources":["../../src/routing/scanner.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAIH,OAAO,KAAK,EACV,SAAS,EAIT,aAAa,EAEd,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"scanner.d.ts","sourceRoot":"","sources":["../../src/routing/scanner.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAIH,OAAO,KAAK,EACV,SAAS,EAIT,aAAa,EAEd,MAAM,YAAY,CAAC;AA6BpB;;;;;;;;;;;GAWG;AACH,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,MAAM,EACZ,GAAG,EAAE,MAAM,EACX,cAAc,GAAE,SAAS,MAAM,EAA4B,GAC1D,OAAO,CAQT;AAED;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,GAAE,aAAkB,GAAG,SAAS,CAuDhF"}
|
package/dist/routing/types.d.ts
CHANGED
|
@@ -81,6 +81,13 @@ export interface SegmentNode<TFile = RouteFile> {
|
|
|
81
81
|
middleware?: TFile;
|
|
82
82
|
access?: TFile;
|
|
83
83
|
route?: TFile;
|
|
84
|
+
/**
|
|
85
|
+
* Set when this segment's route.ts declares `export const csrf = false`.
|
|
86
|
+
* Read from source by the scanner, so the CSRF gate can consult it before
|
|
87
|
+
* any user module loads. See design/08-forms-and-actions.md
|
|
88
|
+
* §"Per-route exemption".
|
|
89
|
+
*/
|
|
90
|
+
csrfExempt?: true;
|
|
84
91
|
error?: TFile;
|
|
85
92
|
default?: TFile;
|
|
86
93
|
/** Status-code files: 4xx.tsx, 5xx.tsx, {status}.tsx (component format) */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/routing/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,kCAAkC;AAClC,MAAM,MAAM,WAAW,GACnB,QAAQ,GACR,SAAS,GACT,WAAW,GACX,oBAAoB,GACpB,OAAO,GACP,MAAM,GACN,cAAc,GACd,SAAS,CAAC;AAEd;;;;;GAKG;AACH,MAAM,MAAM,kBAAkB,GAAG,KAAK,GAAG,MAAM,GAAG,OAAO,GAAG,UAAU,CAAC;AAEvE,8EAA8E;AAC9E,eAAO,MAAM,oBAAoB,EAAE,kBAAkB,EAAyC,CAAC;AAE/F;;;;;;;GAOG;AACH,MAAM,WAAW,SAAS;IACxB,gCAAgC;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,mEAAmE;IACnE,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;;;;GASG;AACH,MAAM,WAAW,WAAW,CAAC,KAAK,GAAG,SAAS;IAC5C,8EAA8E;IAC9E,WAAW,EAAE,MAAM,CAAC;IACpB,8BAA8B;IAC9B,WAAW,EAAE,WAAW,CAAC;IACzB,wFAAwF;IACxF,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,iFAAiF;IACjF,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,gFAAgF;IAChF,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,oEAAoE;IACpE,OAAO,EAAE,MAAM,CAAC;IAChB,8DAA8D;IAC9D,kBAAkB,CAAC,EAAE,kBAAkB,CAAC;IACxC;;;OAGG;IACH,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAGhC,IAAI,CAAC,EAAE,KAAK,CAAC;IACb,MAAM,CAAC,EAAE,KAAK,CAAC;IACf,UAAU,CAAC,EAAE,KAAK,CAAC;IACnB,MAAM,CAAC,EAAE,KAAK,CAAC;IACf,KAAK,CAAC,EAAE,KAAK,CAAC;IACd,KAAK,CAAC,EAAE,KAAK,CAAC;IACd,OAAO,CAAC,EAAE,KAAK,CAAC;IAChB,2EAA2E;IAC3E,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IACpC,gEAAgE;IAChE,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IACxC,8CAA8C;IAC9C,MAAM,CAAC,EAAE,KAAK,CAAC;IAEf,sFAAsF;IACtF,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IAGvC,QAAQ,EAAE,WAAW,CAAC,KAAK,CAAC,EAAE,CAAC;IAC/B,0DAA0D;IAC1D,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,KAAK,CAAC,CAAC,CAAC;CAC3C;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,SAAS,CAAC,KAAK,GAAG,SAAS;IAC1C,gDAAgD;IAChD,IAAI,EAAE,WAAW,CAAC,KAAK,CAAC,CAAC;IACzB,qEAAqE;IACrE,KAAK,CAAC,EAAE,KAAK,CAAC;IACd;;;;;;;;OAQG;IACH,WAAW,CAAC,EAAE,KAAK,CAAC;CACrB;AAED,0CAA0C;AAC1C,MAAM,WAAW,aAAa;IAC5B,4FAA4F;IAC5F,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;CAC3B;AAED,8BAA8B;AAC9B,eAAO,MAAM,uBAAuB,UAA6B,CAAC"}
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/routing/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,kCAAkC;AAClC,MAAM,MAAM,WAAW,GACnB,QAAQ,GACR,SAAS,GACT,WAAW,GACX,oBAAoB,GACpB,OAAO,GACP,MAAM,GACN,cAAc,GACd,SAAS,CAAC;AAEd;;;;;GAKG;AACH,MAAM,MAAM,kBAAkB,GAAG,KAAK,GAAG,MAAM,GAAG,OAAO,GAAG,UAAU,CAAC;AAEvE,8EAA8E;AAC9E,eAAO,MAAM,oBAAoB,EAAE,kBAAkB,EAAyC,CAAC;AAE/F;;;;;;;GAOG;AACH,MAAM,WAAW,SAAS;IACxB,gCAAgC;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,mEAAmE;IACnE,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;;;;GASG;AACH,MAAM,WAAW,WAAW,CAAC,KAAK,GAAG,SAAS;IAC5C,8EAA8E;IAC9E,WAAW,EAAE,MAAM,CAAC;IACpB,8BAA8B;IAC9B,WAAW,EAAE,WAAW,CAAC;IACzB,wFAAwF;IACxF,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,iFAAiF;IACjF,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,gFAAgF;IAChF,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,oEAAoE;IACpE,OAAO,EAAE,MAAM,CAAC;IAChB,8DAA8D;IAC9D,kBAAkB,CAAC,EAAE,kBAAkB,CAAC;IACxC;;;OAGG;IACH,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAGhC,IAAI,CAAC,EAAE,KAAK,CAAC;IACb,MAAM,CAAC,EAAE,KAAK,CAAC;IACf,UAAU,CAAC,EAAE,KAAK,CAAC;IACnB,MAAM,CAAC,EAAE,KAAK,CAAC;IACf,KAAK,CAAC,EAAE,KAAK,CAAC;IACd;;;;;OAKG;IACH,UAAU,CAAC,EAAE,IAAI,CAAC;IAClB,KAAK,CAAC,EAAE,KAAK,CAAC;IACd,OAAO,CAAC,EAAE,KAAK,CAAC;IAChB,2EAA2E;IAC3E,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IACpC,gEAAgE;IAChE,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IACxC,8CAA8C;IAC9C,MAAM,CAAC,EAAE,KAAK,CAAC;IAEf,sFAAsF;IACtF,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IAGvC,QAAQ,EAAE,WAAW,CAAC,KAAK,CAAC,EAAE,CAAC;IAC/B,0DAA0D;IAC1D,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,KAAK,CAAC,CAAC,CAAC;CAC3C;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,SAAS,CAAC,KAAK,GAAG,SAAS;IAC1C,gDAAgD;IAChD,IAAI,EAAE,WAAW,CAAC,KAAK,CAAC,CAAC;IACzB,qEAAqE;IACrE,KAAK,CAAC,EAAE,KAAK,CAAC;IACd;;;;;;;;OAQG;IACH,WAAW,CAAC,EAAE,KAAK,CAAC;CACrB;AAED,0CAA0C;AAC1C,MAAM,WAAW,aAAa;IAC5B,4FAA4F;IAC5F,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;CAC3B;AAED,8BAA8B;AAC9B,eAAO,MAAM,uBAAuB,UAA6B,CAAC"}
|
|
@@ -42,7 +42,7 @@ export interface ActionDispatchConfig {
|
|
|
42
42
|
*
|
|
43
43
|
* **Important:** This function returns true for ANY POST with a form
|
|
44
44
|
* Content-Type, including non-action POSTs to route.ts API handlers.
|
|
45
|
-
* The caller (
|
|
45
|
+
* The caller (rsc-entry/action-dispatcher.ts) MUST check the matched route type
|
|
46
46
|
* before entering the action path — route.ts matches skip action detection
|
|
47
47
|
* entirely so their body is not pre-parsed. See TIM-870.
|
|
48
48
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"action-handler.d.ts","sourceRoot":"","sources":["../../src/server/action-handler.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AASH,OAAO,EAAuC,KAAK,UAAU,EAAE,MAAM,WAAW,CAAC;AAEjF,OAAO,EAAiB,KAAK,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAKtE,OAAO,EAAwC,KAAK,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAE/F,OAAO,EAIL,KAAK,qBAAqB,EAC3B,MAAM,uBAAuB,CAAC;AAC/B,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAUrD,4CAA4C;AAC5C,MAAM,WAAW,oBAAoB;IACnC,0BAA0B;IAC1B,IAAI,EAAE,UAAU,CAAC;IACjB,kEAAkE;IAClE,kBAAkB,CAAC,EAAE,kBAAkB,CAAC;IACxC,gDAAgD;IAChD,UAAU,CAAC,EAAE,gBAAgB,CAAC;IAC9B;;;;OAIG;IACH,eAAe,CAAC,EAAE,qBAAqB,CAAC;CACzC;AAID;;;;;;;;;;;;;GAaG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAarD;AAID;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,aAAa,CAAC;IACxB,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC9B;AAED;;;;;GAKG;AACH,wBAAsB,mBAAmB,CACvC,GAAG,EAAE,OAAO,EACZ,MAAM,EAAE,oBAAoB,GAC3B,OAAO,CAAC,QAAQ,GAAG,YAAY,GAAG,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"action-handler.d.ts","sourceRoot":"","sources":["../../src/server/action-handler.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AASH,OAAO,EAAuC,KAAK,UAAU,EAAE,MAAM,WAAW,CAAC;AAEjF,OAAO,EAAiB,KAAK,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAKtE,OAAO,EAAwC,KAAK,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAE/F,OAAO,EAIL,KAAK,qBAAqB,EAC3B,MAAM,uBAAuB,CAAC;AAC/B,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAUrD,4CAA4C;AAC5C,MAAM,WAAW,oBAAoB;IACnC,0BAA0B;IAC1B,IAAI,EAAE,UAAU,CAAC;IACjB,kEAAkE;IAClE,kBAAkB,CAAC,EAAE,kBAAkB,CAAC;IACxC,gDAAgD;IAChD,UAAU,CAAC,EAAE,gBAAgB,CAAC;IAC9B;;;;OAIG;IACH,eAAe,CAAC,EAAE,qBAAqB,CAAC;CACzC;AAID;;;;;;;;;;;;;GAaG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAarD;AAID;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,aAAa,CAAC;IACxB,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC9B;AAED;;;;;GAKG;AACH,wBAAsB,mBAAmB,CACvC,GAAG,EAAE,OAAO,EACZ,MAAM,EAAE,oBAAoB,GAC3B,OAAO,CAAC,QAAQ,GAAG,YAAY,GAAG,IAAI,CAAC,CAuFzC"}
|
package/dist/server/actions.d.ts
CHANGED
|
@@ -65,11 +65,16 @@ export interface ActionHandlerResult {
|
|
|
65
65
|
/** Revalidation result if revalidatePath was called (element tree, not yet serialized). */
|
|
66
66
|
revalidation?: RevalidationResult;
|
|
67
67
|
/**
|
|
68
|
-
*
|
|
69
|
-
* (TIM-1454). The client
|
|
70
|
-
*
|
|
68
|
+
* True when `revalidatePath()` was called only for pages other than the
|
|
69
|
+
* current one (TIM-1454). The client then evicts its caches and skips the
|
|
70
|
+
* refresh — nothing on screen was named. Whenever the current page was
|
|
71
|
+
* named this is false, even if other pages were named too: its re-render,
|
|
72
|
+
* or the refresh that replaces a dropped one, re-renders every layout it
|
|
73
|
+
* shares with them. Which paths were named is not reported: the client
|
|
74
|
+
* evicts every cached payload after any revalidation (TIM-1476), so a path
|
|
75
|
+
* list would carry nothing it acts on (TIM-1461).
|
|
71
76
|
*/
|
|
72
|
-
|
|
77
|
+
onlyOtherPagesNamed: boolean;
|
|
73
78
|
/** Redirect location if a RedirectSignal was thrown during revalidation. */
|
|
74
79
|
redirectTo?: string;
|
|
75
80
|
/** Redirect status code. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"actions.d.ts","sourceRoot":"","sources":["../../src/server/actions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAMH,OAAO,EAAmB,KAAK,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAC5E,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAG9D,kFAAkF;AAClF,MAAM,WAAW,kBAAkB;IACjC;;;OAGG;IACH,OAAO,EAAE,OAAO,CAAC;IACjB;;;;OAIG;IACH,eAAe,EAAE,eAAe,CAAC;CAClC;AAED,2EAA2E;AAC3E,MAAM,MAAM,kBAAkB,GAAG,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,kBAAkB,CAAC,CAAC;AAE/E;;;;;;;;GAQG;AACH,qBAAa,mBAAoB,SAAQ,KAAK;IAC5C,YAAY,OAAO,EAAE,MAAM,EAG1B;CACF;AAGD,YAAY,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAE3D,+CAA+C;AAC/C,MAAM,WAAW,mBAAmB;IAClC,+DAA+D;IAC/D,QAAQ,CAAC,EAAE,kBAAkB,CAAC;IAC9B;;;;;OAKG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,gEAAgE;IAChE,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,kDAAkD;AAClD,MAAM,WAAW,mBAAmB;IAClC,8CAA8C;IAC9C,YAAY,EAAE,OAAO,CAAC;IACtB,2FAA2F;IAC3F,YAAY,CAAC,EAAE,kBAAkB,CAAC;IAClC
|
|
1
|
+
{"version":3,"file":"actions.d.ts","sourceRoot":"","sources":["../../src/server/actions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAMH,OAAO,EAAmB,KAAK,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAC5E,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAG9D,kFAAkF;AAClF,MAAM,WAAW,kBAAkB;IACjC;;;OAGG;IACH,OAAO,EAAE,OAAO,CAAC;IACjB;;;;OAIG;IACH,eAAe,EAAE,eAAe,CAAC;CAClC;AAED,2EAA2E;AAC3E,MAAM,MAAM,kBAAkB,GAAG,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,kBAAkB,CAAC,CAAC;AAE/E;;;;;;;;GAQG;AACH,qBAAa,mBAAoB,SAAQ,KAAK;IAC5C,YAAY,OAAO,EAAE,MAAM,EAG1B;CACF;AAGD,YAAY,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAE3D,+CAA+C;AAC/C,MAAM,WAAW,mBAAmB;IAClC,+DAA+D;IAC/D,QAAQ,CAAC,EAAE,kBAAkB,CAAC;IAC9B;;;;;OAKG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,gEAAgE;IAChE,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,kDAAkD;AAClD,MAAM,WAAW,mBAAmB;IAClC,8CAA8C;IAC9C,YAAY,EAAE,OAAO,CAAC;IACtB,2FAA2F;IAC3F,YAAY,CAAC,EAAE,kBAAkB,CAAC;IAClC;;;;;;;;;OASG;IACH,mBAAmB,EAAE,OAAO,CAAC;IAC7B,4EAA4E;IAC5E,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,4BAA4B;IAC5B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;;;;OAMG;IACH,kBAAkB,EAAE,OAAO,EAAE,CAAC;CAC/B;AAQD;;;GAGG;AACH,wBAAgB,yBAAyB,CAAC,CAAC,EAAE,KAAK,EAAE,iBAAiB,EAAE,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC,CAErF;AAID;;;;;;;;;;GAUG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAajD;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAOxD;AAID;;;;;;;;;;;;GAYG;AACH,wBAAsB,aAAa,CACjC,QAAQ,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,CAAC,OAAO,CAAC,EAClD,IAAI,EAAE,OAAO,EAAE,EACf,MAAM,GAAE,mBAAwB,EAChC,QAAQ,CAAC,EAAE;IAAE,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAA;CAAE,GACtD,OAAO,CAAC,mBAAmB,CAAC,CAkG9B"}
|
package/dist/server/csrf.d.ts
CHANGED
|
@@ -1,48 +1,67 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* CSRF protection — Origin
|
|
2
|
+
* CSRF protection — Fetch Metadata first, Origin as the fallback.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* CSRF needs a victim's browser to attach cookies, and browsers mark the
|
|
5
|
+
* requests they send: `Sec-Fetch-Site` on HTTPS (and localhost) pages in
|
|
6
|
+
* every current browser, `Origin` on cross-site POSTs everywhere else. So
|
|
7
|
+
* the check trusts those two headers in that order, and lets a request
|
|
8
|
+
* carrying neither through — a webhook, curl, or a server-to-server
|
|
9
|
+
* `fetch`. The residual risk is browsers old enough to send neither on a
|
|
10
|
+
* form POST (Firefox < 70, IE11); SameSite cookies are the defense there.
|
|
11
|
+
* See design/08-forms-and-actions.md §"CSRF Protection".
|
|
12
|
+
*
|
|
13
|
+
* Prior art: Go's `net/http.CrossOriginProtection` (Go 1.25) and the OWASP
|
|
14
|
+
* CSRF cheat sheet (Fetch Metadata as a primary defense).
|
|
15
|
+
*
|
|
16
|
+
* The per-route exemption (`export const csrf = false` in route.ts) is not
|
|
17
|
+
* decided here: the caller owns route matching and applies it to a
|
|
18
|
+
* rejection. Disable globally with csrf: false (not recommended).
|
|
7
19
|
*
|
|
8
20
|
* See design/08-forms-and-actions.md §"CSRF Protection"
|
|
9
21
|
* See design/13-security.md §"Security Testing Checklist" #6
|
|
10
22
|
*/
|
|
11
23
|
export interface CsrfConfig {
|
|
12
|
-
/**
|
|
24
|
+
/**
|
|
25
|
+
* Origins trusted in addition to the request's own. Canonical origins
|
|
26
|
+
* only (`new URL(x).origin === x`); `validateConfig` enforces that.
|
|
27
|
+
*/
|
|
13
28
|
allowedOrigins?: string[];
|
|
14
29
|
/** Set to false to disable CSRF validation entirely. */
|
|
15
30
|
csrf?: boolean;
|
|
16
31
|
}
|
|
17
|
-
/** Why a request failed the
|
|
18
|
-
export type CsrfRejectReason = '
|
|
32
|
+
/** Why a request failed the check — one value per rejection path. */
|
|
33
|
+
export type CsrfRejectReason = 'cross-origin' | 'missing-host' | 'invalid-host' | 'invalid-origin' | 'origin-mismatch';
|
|
19
34
|
/**
|
|
20
|
-
* A failed
|
|
21
|
-
* absent); `expected` is the origin(s)
|
|
22
|
-
* `
|
|
23
|
-
* `
|
|
24
|
-
*
|
|
35
|
+
* A failed check. `origin` is the request's Origin header and `fetchSite`
|
|
36
|
+
* its Sec-Fetch-Site header (null when absent); `expected` is the origin(s)
|
|
37
|
+
* `origin` was compared against: the `allowedOrigins` list, preceded on
|
|
38
|
+
* `origin-mismatch` by the origin derived from the Host header. All three are for
|
|
39
|
+
* the server log only — never the response.
|
|
25
40
|
*/
|
|
26
41
|
export interface CsrfRejection {
|
|
27
42
|
ok: false;
|
|
28
43
|
status: 403;
|
|
29
44
|
reason: CsrfRejectReason;
|
|
30
45
|
origin: string | null;
|
|
46
|
+
fetchSite: string | null;
|
|
31
47
|
expected: readonly string[];
|
|
32
48
|
}
|
|
33
49
|
export type CsrfResult = {
|
|
34
50
|
ok: true;
|
|
35
51
|
} | CsrfRejection;
|
|
36
52
|
/**
|
|
37
|
-
*
|
|
38
|
-
* (scheme + host + port).
|
|
53
|
+
* Decide whether an unsafe-method request may proceed.
|
|
39
54
|
*
|
|
40
|
-
*
|
|
41
|
-
* -
|
|
42
|
-
*
|
|
43
|
-
*
|
|
55
|
+
* In order:
|
|
56
|
+
* 1. `Sec-Fetch-Site` is `same-origin` or `none` → allow.
|
|
57
|
+
* 2. `Sec-Fetch-Site` has any other value → allow only if `Origin` is in
|
|
58
|
+
* `allowedOrigins`.
|
|
59
|
+
* 3. No `Sec-Fetch-Site`, `Origin` present (a plain-HTTP page, or an
|
|
60
|
+
* older browser) → `Origin` must be in `allowedOrigins` or equal the
|
|
61
|
+
* origin derived from the scheme and `Host`.
|
|
62
|
+
* 4. Neither header → allow: not a browser, so no ambient cookies to abuse.
|
|
44
63
|
*
|
|
45
|
-
* Safe methods (GET, HEAD, OPTIONS) always pass.
|
|
64
|
+
* Safe methods (GET, HEAD, OPTIONS) and `csrf: false` always pass.
|
|
46
65
|
*/
|
|
47
66
|
export declare function validateCsrf(req: Request, config: CsrfConfig): CsrfResult;
|
|
48
67
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"csrf.d.ts","sourceRoot":"","sources":["../../src/server/csrf.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"csrf.d.ts","sourceRoot":"","sources":["../../src/server/csrf.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAOH,MAAM,WAAW,UAAU;IACzB;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,wDAAwD;IACxD,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,qEAAqE;AACrE,MAAM,MAAM,gBAAgB,GACxB,cAAc,GACd,cAAc,GACd,cAAc,GACd,gBAAgB,GAChB,iBAAiB,CAAC;AAEtB;;;;;;GAMG;AACH,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,KAAK,CAAC;IACV,MAAM,EAAE,GAAG,CAAC;IACZ,MAAM,EAAE,gBAAgB,CAAC;IACzB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;CAC7B;AAED,MAAM,MAAM,UAAU,GAAG;IAAE,EAAE,EAAE,IAAI,CAAA;CAAE,GAAG,aAAa,CAAC;AAwCtD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,UAAU,GAAG,UAAU,CAsDzE;AA+ED;;;;;;;;GAQG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,OAAO,EAAE,SAAS,EAAE,aAAa,GAAG,QAAQ,CAatF"}
|
|
@@ -59,9 +59,13 @@ export interface ErrorBoundaryProps {
|
|
|
59
59
|
children: ReactNode;
|
|
60
60
|
}
|
|
61
61
|
/**
|
|
62
|
-
* How to load a route file's module. Only `default` is read.
|
|
63
|
-
*
|
|
64
|
-
*
|
|
62
|
+
* How to load a route file's module. Only `default` is read. A module that
|
|
63
|
+
* fails to load resolves to `null` — the loader must not reject, because
|
|
64
|
+
* building an error boundary must not itself throw (TIM-584). The server
|
|
65
|
+
* injects `loadFallbackModule`, which logs and reports the failed load before
|
|
66
|
+
* returning `null`, so a broken status or error page is never skipped
|
|
67
|
+
* silently (TIM-1458). The walk deliberately does not catch a rejection:
|
|
68
|
+
* a catch here would be the silent skip.
|
|
65
69
|
*/
|
|
66
70
|
type BoundaryModuleLoader<TFile> = (file: TFile) => {
|
|
67
71
|
default?: unknown;
|
|
@@ -72,7 +76,7 @@ type BoundaryModuleLoader<TFile> = (file: TFile) => {
|
|
|
72
76
|
export interface ErrorBoundaryWrapConfig<TFile, TElement> {
|
|
73
77
|
/** `React.createElement` or an equivalent element factory. */
|
|
74
78
|
createElement: (type: unknown, props: object) => TElement;
|
|
75
|
-
/** Loads a route file's module
|
|
79
|
+
/** Loads a route file's module; resolves to `null` when it fails. See `BoundaryModuleLoader`. */
|
|
76
80
|
loadModule: BoundaryModuleLoader<TFile>;
|
|
77
81
|
/**
|
|
78
82
|
* The error boundary component to instantiate (`TimberErrorBoundary`, or a
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"error-boundary-wrapper.d.ts","sourceRoot":"","sources":["../../src/server/error-boundary-wrapper.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACvC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AACvD,OAAO,EAAsB,KAAK,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAEnF;;;;;;;;GAQG;AACH,MAAM,WAAW,kBAAkB;IACjC,uDAAuD;IACvD,iBAAiB,CAAC,EAAE,eAAe,CAAC;IACpC,uFAAuF;IACvF,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;;;;;OAQG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;;;;;OAMG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,QAAQ,EAAE,SAAS,CAAC;CACrB;AAED
|
|
1
|
+
{"version":3,"file":"error-boundary-wrapper.d.ts","sourceRoot":"","sources":["../../src/server/error-boundary-wrapper.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACvC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AACvD,OAAO,EAAsB,KAAK,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAEnF;;;;;;;;GAQG;AACH,MAAM,WAAW,kBAAkB;IACjC,uDAAuD;IACvD,iBAAiB,CAAC,EAAE,eAAe,CAAC;IACpC,uFAAuF;IACvF,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;;;;;OAQG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;;;;;OAMG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,QAAQ,EAAE,SAAS,CAAC;CACrB;AAED;;;;;;;;GAQG;AACH,KAAK,oBAAoB,CAAC,KAAK,IAAI,CACjC,IAAI,EAAE,KAAK,KACR;IAAE,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,IAAI,GAAG,OAAO,CAAC;IAAE,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,IAAI,CAAC,CAAC;AAE1E,2DAA2D;AAC3D,MAAM,WAAW,uBAAuB,CAAC,KAAK,EAAE,QAAQ;IACtD,8DAA8D;IAC9D,aAAa,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,KAAK,QAAQ,CAAC;IAC1D,iGAAiG;IACjG,UAAU,EAAE,oBAAoB,CAAC,KAAK,CAAC,CAAC;IACxC;;;OAGG;IACH,sBAAsB,EAAE,OAAO,CAAC;IAChC,yFAAyF;IACzF,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,6EAA6E;IAC7E,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,6EAA6E;IAC7E,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAsCD;;;;;;;;;;;;;GAaG;AACH,wBAAsB,8BAA8B,CAAC,KAAK,EAAE,QAAQ,SAAS,SAAS,EACpF,OAAO,EAAE,WAAW,CAAC,KAAK,CAAC,EAC3B,OAAO,EAAE,QAAQ,EACjB,MAAM,EAAE,uBAAuB,CAAC,KAAK,EAAE,QAAQ,CAAC,GAC/C,OAAO,CAAC,QAAQ,CAAC,CAmCnB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fallback-error.d.ts","sourceRoot":"","sources":["../../src/server/fallback-error.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,EAAmB,KAAK,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAC/E,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAEjE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;
|
|
1
|
+
{"version":3,"file":"fallback-error.d.ts","sourceRoot":"","sources":["../../src/server/fallback-error.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,EAAmB,KAAK,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAC/E,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAEjE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AAIrE;;;;;;GAMG;AACH,wBAAsB,mBAAmB,CACvC,KAAK,EAAE,OAAO,EACd,GAAG,EAAE,OAAO,EACZ,eAAe,EAAE,OAAO,EACxB,KAAK,EAAE,UAAU,GAAG,SAAS,EAC7B,KAAK,EAAE,OAAO,EACd,WAAW,EAAE,mBAAmB,EAChC,eAAe,EAAE,qBAAqB,EACtC,WAAW,CAAC,EAAE,eAAe,EAC7B,WAAW,CAAC,EAAE,MAAM,EACpB,UAAU,CAAC,EAAE,OAAO,gCAAgC,EAAE,kBAAkB,GACvE,OAAO,CAAC,QAAQ,CAAC,CA8CnB;AAED;;;;;;;;;GASG;AACH,wBAAsB,kBAAkB,CACtC,KAAK,EAAE,OAAO,EACd,WAAW,CAAC,EAAE,MAAM,EACpB,UAAU,CAAC,EAAE,OAAO,gCAAgC,EAAE,kBAAkB,GACvE,OAAO,CAAC,QAAQ,CAAC,CAwBnB"}
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
*
|
|
12
12
|
* The flash data is server-side only — never serialized to cookies or headers.
|
|
13
13
|
*
|
|
14
|
-
* See design/08-forms-and-actions.md §"No-JS
|
|
14
|
+
* See design/08-forms-and-actions.md §"No-JS Result Round-Trip"
|
|
15
15
|
*/
|
|
16
16
|
import type { ValidationErrors } from './action-client.ts';
|
|
17
17
|
/**
|
package/dist/server/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { n as formFlashAls } from "../_chunks/als-registry-DaxkVjt5.js";
|
|
2
|
-
import { A as
|
|
2
|
+
import { A as getSpanId, C as isRedirectSignal, E as addSpanEvent, F as withSpan, S as isDenySignal, T as isDevMode, j as getTraceId, w as isDebug } from "../_chunks/logger-BP0LN6vP.js";
|
|
3
3
|
import { t as formatSize } from "../_chunks/format-BKclgVbk.js";
|
|
4
|
-
import { _ as getHeaders, a as parseFormData, c as deny, d as waitUntil, i as coerce, l as redirect, n as revalidatePath, p as getCookieJar, r as revalidateTag, u as redirectExternal, y as getSegmentParams } from "../_chunks/actions-
|
|
4
|
+
import { _ as getHeaders, a as parseFormData, c as deny, d as waitUntil, i as coerce, l as redirect, n as revalidatePath, p as getCookieJar, r as revalidateTag, u as redirectExternal, y as getSegmentParams } from "../_chunks/actions-Rjk4htmA.js";
|
|
5
5
|
//#region src/shared/redirect-type.ts
|
|
6
6
|
/**
|
|
7
7
|
* Next.js redirect type discriminator.
|
package/dist/server/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../src/shared/redirect-type.ts","../../src/server/sensitive-fields.ts","../../src/server/action-client.ts","../../src/server/form-flash.ts"],"sourcesContent":["/**\n * Next.js redirect type discriminator.\n *\n * Provided for API compatibility with libraries that import `RedirectType`\n * from `next/navigation`. In timber, `redirect()` always uses `replace`\n * semantics (no history entry for the redirect itself).\n *\n * Lives in shared/ (isomorphic) so both the server primitives and the\n * client-only next/navigation shim export the same definition without the\n * client shim pulling in server code.\n */\nexport const RedirectType = {\n push: 'push',\n replace: 'replace',\n} as const;\n\nexport type RedirectTypeValue = (typeof RedirectType)[keyof typeof RedirectType];\n","/**\n * Sensitive field stripping — removes password/token/CVV-style fields\n * from form values before they are echoed back to the client as\n * `submittedValues` for form repopulation.\n *\n * Applied to both action paths:\n * - With-JS action path: `createActionClient()` in `action-client.ts`\n * - No-JS form POST path: `handleFormAction()` in `action-handler.ts`\n *\n * Why: on a validation failure, timber echoes submitted form values back so\n * the user doesn't have to re-type everything. Without filtering, plaintext\n * passwords / credit-card numbers / TOTP codes would travel through the RSC\n * stream (with-JS) or land in the HTML as `defaultValue` attributes (no-JS)\n * — ending up in browser history, proxy logs, disk caches, and the\n * back-forward cache.\n *\n * Safe by default: the built-in deny-list is applied unconditionally unless\n * the user explicitly opts out via `forms.stripSensitiveFields: false` in\n * `timber.config.ts` or per-action via `createActionClient({ stripSensitiveFields: false })`.\n *\n * See design/08-forms-and-actions.md §\"Validation errors\"\n * See design/13-security.md §\"Sensitive field stripping\"\n * See TIM-816\n */\n\nimport { isDebug } from './debug.ts';\n\n// ─── Public types ────────────────────────────────────────────────────────\n\n/**\n * How to strip sensitive fields from `submittedValues`.\n *\n * - `true` / `undefined` — use the built-in deny-list (default, safe).\n * - `false` — do not strip anything (dev convenience; never do this in prod).\n * - `string[]` — additional field names to strip, merged with the built-in list.\n * - `(name) => boolean` — custom predicate, fully replaces the built-in list.\n * Return `true` to strip, `false` to keep. The `name` argument is the raw\n * (un-normalized) field name as it appeared in the submitted form.\n */\nexport type SensitiveFieldsOption = boolean | readonly string[] | ((name: string) => boolean);\n\n// ─── Built-in deny-list ──────────────────────────────────────────────────\n\n/**\n * Substring patterns matched against the normalized field name.\n * Normalization = lowercase + strip `_` and `-`.\n *\n * Any field whose normalized name *contains* one of these strings is\n * considered sensitive. Entries like `currentPassword`, `passwordConfirmation`,\n * and `user.password` all match via the `password` substring.\n */\nconst BUILTIN_SUBSTRING_PATTERNS: readonly string[] = [\n 'password',\n 'passwd',\n 'pwd',\n 'secret',\n 'apikey',\n 'accesstoken',\n 'refreshtoken',\n 'cvv',\n 'cvc',\n 'cardnumber',\n 'cardcvc',\n 'ssn',\n 'socialsecuritynumber',\n 'otp',\n 'totp',\n 'mfacode',\n 'twofactorcode',\n 'privatekey',\n];\n\n/**\n * Exact matches against the normalized field name. These are field names that\n * are too short or too common to substring-match safely. e.g. `token` alone\n * would match `csrfToken`, which is not sensitive — so `token` is exact-only,\n * while legitimate token fields are covered by `accesstoken` / `refreshtoken`.\n */\nconst BUILTIN_EXACT_PATTERNS: readonly string[] = ['token'];\n\n/**\n * Normalize a field name for deny-list comparison.\n * Lowercases the string and strips `_` and `-` so camelCase, snake_case, and\n * kebab-case variants all compare equal (`api_key` / `apiKey` / `api-key` →\n * `apikey`).\n */\nfunction normalize(name: string): string {\n let out = '';\n for (let i = 0; i < name.length; i++) {\n const ch = name.charCodeAt(i);\n if (ch === 0x5f /* _ */ || ch === 0x2d /* - */) continue;\n // A-Z → a-z\n if (ch >= 0x41 && ch <= 0x5a) {\n out += String.fromCharCode(ch + 32);\n } else {\n out += name[i];\n }\n }\n return out;\n}\n\n/**\n * Check whether a name matches the built-in deny-list (with optional extras).\n * Extras are merged into the substring pattern list after normalization.\n */\nfunction isBuiltinSensitive(name: string, extras?: readonly string[]): boolean {\n const normalized = normalize(name);\n if (BUILTIN_EXACT_PATTERNS.includes(normalized)) return true;\n for (const pattern of BUILTIN_SUBSTRING_PATTERNS) {\n if (normalized.includes(pattern)) return true;\n }\n if (extras && extras.length > 0) {\n for (const extra of extras) {\n const normExtra = normalize(extra);\n if (normExtra.length === 0) continue;\n if (normalized.includes(normExtra)) return true;\n }\n }\n return false;\n}\n\n// ─── Predicate resolution ────────────────────────────────────────────────\n\n/**\n * A resolved predicate: `null` means \"don't strip anything\" (the option was\n * explicitly `false`). Otherwise a function from raw field name → boolean.\n */\nexport type ResolvedSensitivePredicate = ((name: string) => boolean) | null;\n\n/**\n * Resolve a `SensitiveFieldsOption` into a concrete predicate.\n * Precedence: per-action > global > built-in default.\n *\n * - Per-action `undefined` → fall back to global.\n * - Global `undefined` → use built-in list.\n * - Either level set to `false` → disable stripping entirely (returns `null`).\n * - `true` → built-in list.\n * - `string[]` → built-in ∪ extras.\n * - function → custom, replaces the built-in list entirely.\n */\nexport function resolveSensitivePredicate(\n perAction: SensitiveFieldsOption | undefined,\n global: SensitiveFieldsOption | undefined\n): ResolvedSensitivePredicate {\n const chosen = perAction !== undefined ? perAction : global;\n\n if (chosen === false) return null;\n if (chosen === undefined || chosen === true) {\n return (name) => isBuiltinSensitive(name);\n }\n if (typeof chosen === 'function') {\n return chosen;\n }\n // Array of extra names merged with the built-in list.\n const extras = chosen;\n return (name) => isBuiltinSensitive(name, extras);\n}\n\n// ─── Module-level global config ──────────────────────────────────────────\n\nlet globalConfig: SensitiveFieldsOption | undefined;\n\n/**\n * Set the global `forms.stripSensitiveFields` config from `timber.config.ts`.\n * Called once at startup from `rsc-entry`.\n */\nexport function setGlobalSensitiveFieldsConfig(option: SensitiveFieldsOption | undefined): void {\n globalConfig = option;\n}\n\n/** Read the global `forms.stripSensitiveFields` config. */\nexport function getGlobalSensitiveFieldsConfig(): SensitiveFieldsOption | undefined {\n return globalConfig;\n}\n\n// ─── Stripping ───────────────────────────────────────────────────────────\n\n// One warning per field name per process — prevents log spam when a form is\n// submitted many times in dev mode.\nconst warnedFields = new Set<string>();\n\nfunction warnStripped(name: string): void {\n if (!isDebug()) return;\n if (warnedFields.has(name)) return;\n warnedFields.add(name);\n console.warn(\n `[timber] stripped sensitive field \"${name}\" from submittedValues. ` +\n `Override via forms.stripSensitiveFields in timber.config.ts.`\n );\n}\n\n/**\n * Walk an object (recursively) and return a copy with every key matching\n * `predicate` removed. Nested objects like `{ user: { password: '...' } }`\n * are handled — `user.password` is stripped while other `user.*` fields remain.\n *\n * - Arrays are walked element-wise (object entries inside arrays are cleaned).\n * - Non-plain values (strings, numbers, Files, Dates, etc.) are returned as-is.\n * - When a stripped key is encountered, it is omitted from the result entirely\n * — we do NOT set it to an empty string, because that would overwrite a\n * valid `defaultValue` the form author might have set.\n */\nexport function stripSensitiveFields<T>(value: T, predicate: ResolvedSensitivePredicate): T {\n // Null predicate = stripping disabled entirely.\n if (predicate === null) return value;\n if (value === null || value === undefined) return value;\n if (typeof value !== 'object') return value;\n if (value instanceof File || value instanceof Date) return value;\n\n if (Array.isArray(value)) {\n return value.map((item) => stripSensitiveFields(item, predicate)) as unknown as T;\n }\n\n const result: Record<string, unknown> = {};\n for (const [key, nested] of Object.entries(value as Record<string, unknown>)) {\n if (predicate(key)) {\n warnStripped(key);\n continue;\n }\n result[key] = stripSensitiveFields(nested, predicate);\n }\n return result as unknown as T;\n}\n\n// ─── Test helpers ────────────────────────────────────────────────────────\n\n/** Reset the \"warned once\" cache. Exposed for tests. */\nexport function __resetSensitiveFieldsWarnings(): void {\n warnedFields.clear();\n}\n","/**\n * createActionClient — typed middleware and schema validation for server actions.\n *\n * Inspired by next-safe-action. Provides a builder API:\n * createActionClient({ middleware }) → .schema(z.object(...)) → .action(fn)\n *\n * The resulting action function satisfies both:\n * 1. Direct call: action(input) → Promise<ActionResult>\n * 2. React useActionState: (prevState, formData) => Promise<ActionResult>\n *\n * See design/08-forms-and-actions.md §\"Middleware and Server Actions\"\n */\n\n// ─── ActionError ─────────────────────────────────────────────────────────\n\n/**\n * Typed error class for server actions. Carries a string code and optional data.\n * When thrown from middleware or the action body, the action short-circuits and\n * the client receives `result.serverError`.\n *\n * In production, unexpected errors (non-ActionError) return `{ code: 'INTERNAL_ERROR' }`\n * with no message. In dev, `data.message` is included.\n */\nexport class ActionError<TCode extends string = string> extends Error {\n readonly code: TCode;\n readonly data: Record<string, unknown> | undefined;\n\n constructor(code: TCode, data?: Record<string, unknown>) {\n super(`ActionError: ${code}`);\n this.name = 'ActionError';\n this.code = code;\n this.data = data;\n }\n}\n\n// ─── Standard Schema ──────────────────────────────────────────────────────\n\n/**\n * Standard Schema v1 interface (subset).\n * Zod ≥3.24, Valibot ≥1.0, and ArkType all implement this.\n * See https://github.com/standard-schema/standard-schema\n *\n * We use permissive types here to accept all compliant libraries without\n * requiring exact structural matches on issues/path shapes.\n */\ninterface StandardSchemaV1<Output = unknown> {\n readonly '~standard': {\n readonly validate: (\n value: unknown\n ) => StandardSchemaResult<Output> | Promise<StandardSchemaResult<Output>>;\n };\n}\n\ntype StandardSchemaResult<Output> =\n | { value: Output; issues?: undefined }\n | { value?: undefined; issues: ReadonlyArray<StandardSchemaIssue> };\n\ninterface StandardSchemaIssue {\n message: string;\n path?: ReadonlyArray<PropertyKey | { key: PropertyKey }>;\n}\n\n/** Check if a schema implements the Standard Schema protocol. */\nfunction isStandardSchema(schema: unknown): schema is StandardSchemaV1 {\n return (\n typeof schema === 'object' &&\n schema !== null &&\n '~standard' in schema &&\n typeof (schema as StandardSchemaV1)['~standard'].validate === 'function'\n );\n}\n\n// ─── Types ───────────────────────────────────────────────────────────────\n\n/**\n * Minimal schema interface — compatible with Zod, Valibot, ArkType, etc.\n *\n * Accepts either:\n * - Standard Schema (preferred): any object with `~standard.validate()`\n * - Legacy parse interface: objects with `.parse()` / `.safeParse()`\n *\n * At runtime, Standard Schema is detected via `~standard` property and\n * takes priority over the legacy interface.\n */\nexport type ActionSchema<T = unknown> = StandardSchemaV1<T> | LegacyActionSchema<T>;\n\n/** Legacy schema interface with .parse() / .safeParse(). */\ninterface LegacyActionSchema<T = unknown> {\n 'parse'(data: unknown): T;\n 'safeParse'?(data: unknown): { success: true; data: T } | { success: false; error: SchemaError };\n // Exclude Standard Schema objects from matching this interface\n '~standard'?: never;\n}\n\n/** Schema validation error shape (for legacy .safeParse()/.parse() interface). */\nexport interface SchemaError {\n issues?: Array<{ path?: Array<string | number>; message: string }>;\n flatten?(): { fieldErrors: Record<string, string[]> };\n}\n\n/** Flattened validation errors keyed by field name. */\nexport type ValidationErrors = Record<string, string[]>;\n\n/** Middleware function: returns context to merge into the action body's ctx. */\nexport type ActionMiddleware<TCtx = Record<string, unknown>> = () => Promise<TCtx> | TCtx;\n\n/** The result type returned to the client. */\nexport type ActionResult<TData = unknown> =\n | { data: TData; validationErrors?: never; serverError?: never; submittedValues?: never }\n | {\n data?: never;\n validationErrors: ValidationErrors;\n serverError?: never;\n /** Raw input values on validation failure — for repopulating form fields. */\n submittedValues?: Record<string, unknown>;\n }\n | {\n data?: never;\n validationErrors?: never;\n serverError: { code: string; data?: Record<string, unknown> };\n submittedValues?: never;\n };\n\n/** Context passed to the action body. */\nexport interface ActionContext<TCtx, TInput> {\n ctx: TCtx;\n input: TInput;\n}\n\n// ─── Builder ─────────────────────────────────────────────────────────────\n\ninterface ActionClientConfig<TCtx> {\n middleware?: ActionMiddleware<TCtx> | ActionMiddleware<Record<string, unknown>>[];\n /** Max file size in bytes. Files exceeding this are rejected with validation errors. */\n fileSizeLimit?: number;\n /**\n * Override the sensitive-field deny-list for this action client.\n * See `SensitiveFieldsOption` in `./sensitive-fields.ts`. Per-action config\n * takes precedence over the global `forms.stripSensitiveFields` option in\n * `timber.config.ts`. See design/08-forms-and-actions.md and TIM-816.\n */\n stripSensitiveFields?: SensitiveFieldsOption;\n}\n\n/** Intermediate builder returned by createActionClient(). */\nexport interface ActionBuilder<TCtx> {\n /** Declare the input schema. Validation errors are returned typed. */\n schema<TInput>(schema: ActionSchema<TInput>): ActionBuilderWithSchema<TCtx, TInput>;\n /** Define the action body without input validation. */\n action<TData>(\n fn: (ctx: ActionContext<TCtx, undefined>) => Promise<TData>\n ): ActionFn<TData, undefined>;\n}\n\n/** Builder after .schema() has been called. */\nexport interface ActionBuilderWithSchema<TCtx, TInput> {\n /** Define the action body with validated input. */\n action<TData>(fn: (ctx: ActionContext<TCtx, TInput>) => Promise<TData>): ActionFn<TData, TInput>;\n}\n\n/**\n * The final action function. Callable three ways:\n * - Direct: action(input) → Promise<ActionResult<TData>>\n * - React useActionState: action(prevState, formData) → Promise<ActionResult<TData>>\n * - React <form action={fn}>: action(formData) → void (return value ignored by React)\n *\n * The third overload exists purely for type compatibility with React's\n * `<form action>` prop, which expects `(formData: FormData) => void`.\n * At runtime the function still returns Promise<ActionResult>, but React\n * discards it. This lets validated actions be passed directly to forms\n * without casts.\n */\n/**\n * Map schema output keys to `string | undefined` for form-facing APIs.\n * HTML form values are always strings, and fields can be absent.\n * Gives autocomplete for field names without lying about value types.\n */\nexport type InputHint<T> =\n T extends Record<string, unknown> ? { [K in keyof T]: string | undefined } : T;\n\n/**\n * ActionFn — the callable returned by `createActionClient().action()`.\n *\n * Generic order: `<TData, TInput>` — TData first for backward compatibility.\n * Previously ActionFn had a single `<TData>` generic, so existing code like\n * `ActionFn<MyResult>` must still work with TData in the first position.\n * See TIM-797.\n */\nexport type ActionFn<TData = unknown, TInput = unknown> = {\n /** <form action={fn}> compatibility — React discards the return value. */\n (formData: FormData): void;\n /** Direct call: action(input) — optional when TInput is undefined/unknown (no-schema actions). */\n (\n ...args: undefined extends TInput ? [input?: TInput] : [input: TInput]\n ): Promise<ActionResult<TData>>;\n /** React useActionState: action(prevState, formData) */\n (prevState: ActionResult<TData> | null, formData: FormData): Promise<ActionResult<TData>>;\n};\n\n// ─── Implementation ──────────────────────────────────────────────────────\n\n/**\n * Run middleware array or single function. Returns merged context.\n */\nasync function runActionMiddleware<TCtx>(\n middleware: ActionMiddleware<TCtx> | ActionMiddleware<Record<string, unknown>>[] | undefined\n): Promise<TCtx> {\n if (!middleware) {\n return {} as TCtx;\n }\n\n if (Array.isArray(middleware)) {\n let merged = {} as Record<string, unknown>;\n for (const mw of middleware) {\n const result = await mw();\n merged = { ...merged, ...result };\n }\n return merged as TCtx;\n }\n\n return await middleware();\n}\n\n// Re-export parseFormData for use throughout the framework\nimport { parseFormData } from './form-data.ts';\nimport { formatSize } from '../utils/format.ts';\nimport { isDebug, isDevMode } from './debug.ts';\nimport { isDenySignal, isRedirectSignal } from './signal-identity.ts';\nimport {\n stripSensitiveFields,\n resolveSensitivePredicate,\n getGlobalSensitiveFieldsConfig,\n type SensitiveFieldsOption,\n} from './sensitive-fields.ts';\n\n/**\n * Extract validation errors from a schema error.\n * Supports Zod's flatten() and generic issues array.\n */\nfunction extractValidationErrors(error: SchemaError): ValidationErrors {\n // Zod-style flatten\n if (typeof error.flatten === 'function') {\n return error.flatten().fieldErrors;\n }\n\n // Generic issues array\n if (error.issues) {\n const errors: ValidationErrors = {};\n for (const issue of error.issues) {\n const path = issue.path?.join('.') ?? '_root';\n if (!errors[path]) errors[path] = [];\n errors[path].push(issue.message);\n }\n return errors;\n }\n\n return { _root: ['Validation failed'] };\n}\n\n/**\n * Extract validation errors from Standard Schema issues.\n */\nfunction extractStandardSchemaErrors(issues: ReadonlyArray<StandardSchemaIssue>): ValidationErrors {\n const errors: ValidationErrors = {};\n for (const issue of issues) {\n const path =\n issue.path\n ?.map((p) => {\n // Standard Schema path items can be { key: ... } objects or bare PropertyKey values\n if (typeof p === 'object' && p !== null && 'key' in p) return String(p.key);\n return String(p);\n })\n .join('.') ?? '_root';\n if (!errors[path]) errors[path] = [];\n errors[path].push(issue.message);\n }\n return Object.keys(errors).length > 0 ? errors : { _root: ['Validation failed'] };\n}\n\n/**\n * Wrap unexpected errors into a safe server error result.\n * ActionError → typed result. Other errors → INTERNAL_ERROR (no leak).\n *\n * Exported for use by action-handler.ts to catch errors from raw 'use server'\n * functions that don't use createActionClient.\n */\nexport function handleActionError(error: unknown): ActionResult<never> {\n if (error instanceof ActionError) {\n return {\n serverError: {\n code: error.code,\n ...(error.data ? { data: error.data } : {}),\n },\n };\n }\n\n // In dev, include the message for debugging.\n // Uses isDevMode() — NOT isDebug() — because this data is sent to the\n // browser. TIMBER_DEBUG must never cause error messages to leak to clients.\n // See design/13-security.md principle 4: \"Errors don't leak.\"\n const devMode = isDevMode();\n return {\n serverError: {\n code: 'INTERNAL_ERROR',\n ...(devMode && error instanceof Error ? { data: { message: error.message } } : {}),\n },\n };\n}\n\n/**\n * Create a typed action client with middleware and schema validation.\n *\n * @example\n * ```ts\n * const action = createActionClient({\n * middleware: async () => {\n * const user = await getUser()\n * if (!user) throw new ActionError('UNAUTHORIZED')\n * return { user }\n * },\n * })\n *\n * export const createTodo = action\n * .schema(z.object({ title: z.string().min(1) }))\n * .action(async ({ input, ctx }) => {\n * await db.todos.create({ ...input, userId: ctx.user.id })\n * })\n * ```\n */\nexport function createActionClient<TCtx = Record<string, never>>(\n config: ActionClientConfig<TCtx> = {}\n): ActionBuilder<TCtx> {\n function buildAction<TInput, TData>(\n schema: ActionSchema<TInput> | undefined,\n fn: (ctx: ActionContext<TCtx, TInput>) => Promise<TData>\n ): ActionFn<TData, TInput> {\n async function actionHandler(...args: unknown[]): Promise<ActionResult<TData>> {\n try {\n // Run middleware\n const ctx = await runActionMiddleware(config.middleware);\n\n // Determine input — either FormData (from useActionState) or direct arg\n let rawInput: unknown;\n if (args.length === 2 && args[1] instanceof FormData) {\n // Called as (prevState, formData) by React useActionState (with-JS path)\n rawInput = schema ? parseFormData(args[1]) : args[1];\n } else if (args.length === 1 && args[0] instanceof FormData) {\n // No-JS path: React's decodeAction binds FormData as the sole argument.\n // The form POSTs without JavaScript, decodeAction resolves the server\n // reference and binds the FormData, then executeAction calls fn() with\n // no additional args — so the bound FormData arrives as args[0].\n rawInput = schema ? parseFormData(args[0]) : args[0];\n } else {\n // Direct call: action(input)\n rawInput = args[0];\n }\n\n // Resolve the sensitive-field stripping predicate once per invocation.\n // Precedence: per-action (config.stripSensitiveFields) > global\n // (forms.stripSensitiveFields from timber.config.ts) > built-in deny-list.\n // See TIM-816.\n const sensitivePredicate = resolveSensitivePredicate(\n config.stripSensitiveFields,\n getGlobalSensitiveFieldsConfig()\n );\n\n // Capture a \"safe-to-echo\" snapshot of the raw input once. Files are\n // stripped (can't serialize, shouldn't echo back) and sensitive fields\n // (passwords, tokens, CVV, etc.) are removed before they would land\n // in the RSC payload → client form `defaultValue` → DOM.\n const buildSubmittedValues = (): Record<string, unknown> | undefined => {\n const withoutFiles = stripFiles(rawInput);\n if (withoutFiles === undefined) return undefined;\n return stripSensitiveFields(withoutFiles, sensitivePredicate);\n };\n\n // Validate file sizes before schema validation.\n if (config.fileSizeLimit !== undefined && rawInput && typeof rawInput === 'object') {\n const fileSizeErrors = validateFileSizes(\n rawInput as Record<string, unknown>,\n config.fileSizeLimit\n );\n if (fileSizeErrors) {\n return { validationErrors: fileSizeErrors, submittedValues: buildSubmittedValues() };\n }\n }\n\n // Capture submitted values for repopulation on validation failure.\n const submittedValues = schema ? buildSubmittedValues() : undefined;\n\n // Validate with schema if provided\n let input: TInput;\n if (schema) {\n if (isStandardSchema(schema)) {\n // Standard Schema protocol (Zod ≥3.24, Valibot ≥1.0, ArkType)\n const result = schema['~standard'].validate(rawInput);\n if (result instanceof Promise) {\n throw new Error(\n '[timber] createActionClient: schema returned a Promise — only sync schemas are supported.'\n );\n }\n if (result.issues) {\n const validationErrors = extractStandardSchemaErrors(result.issues);\n logValidationFailure(validationErrors);\n return { validationErrors, submittedValues };\n }\n input = result.value;\n } else if (typeof schema.safeParse === 'function') {\n const result = schema.safeParse(rawInput);\n if (!result.success) {\n const validationErrors = extractValidationErrors(result.error);\n logValidationFailure(validationErrors);\n return { validationErrors, submittedValues };\n }\n input = result.data;\n } else {\n try {\n input = schema.parse(rawInput);\n } catch (parseError) {\n const validationErrors = extractValidationErrors(parseError as SchemaError);\n logValidationFailure(validationErrors);\n return { validationErrors, submittedValues };\n }\n }\n } else {\n input = rawInput as TInput;\n }\n\n // Execute the action body\n const data = await fn({ ctx, input });\n return { data };\n } catch (error) {\n // Re-throw redirect/deny signals — these are control flow, not errors.\n // They must propagate to executeAction() which converts them to proper\n // HTTP responses (302 redirect, 4xx deny). Catching them here would\n // wrap them as INTERNAL_ERROR and break redirect()/redirectExternal()/deny().\n if (isRedirectSignal(error) || isDenySignal(error)) {\n throw error;\n }\n return handleActionError(error);\n }\n }\n\n return actionHandler as ActionFn<TData, TInput>;\n }\n\n return {\n schema<TInput>(schema: ActionSchema<TInput>) {\n return {\n action<TData>(\n fn: (ctx: ActionContext<TCtx, TInput>) => Promise<TData>\n ): ActionFn<TData, TInput> {\n return buildAction(schema, fn);\n },\n };\n },\n action<TData>(\n fn: (ctx: ActionContext<TCtx, undefined>) => Promise<TData>\n ): ActionFn<TData, undefined> {\n return buildAction(undefined, fn as (ctx: ActionContext<TCtx, unknown>) => Promise<TData>);\n },\n };\n}\n\n// ─── validated() ────────────────────────────────────────────────────────\n\n/**\n * Convenience wrapper for the common case: validate input, run handler.\n * No middleware needed.\n *\n * @example\n * ```ts\n * 'use server'\n * import { validated } from '@timber-js/app/server'\n * import { z } from 'zod'\n *\n * export const createTodo = validated(\n * z.object({ title: z.string().min(1) }),\n * async (input) => {\n * await db.todos.create(input)\n * }\n * )\n * ```\n */\nexport function validated<TInput, TData>(\n schema: ActionSchema<TInput>,\n handler: (input: TInput) => Promise<TData>\n): ActionFn<TData, TInput> {\n return createActionClient()\n .schema(schema)\n .action(async ({ input }) => handler(input));\n}\n\n// ─── Helpers ────────────────────────────────────────────────────────────\n\n/**\n * Log validation failures in dev mode so developers can see what went wrong.\n * In production, validation errors are only returned to the client.\n */\nfunction logValidationFailure(errors: ValidationErrors): void {\n const isDev = isDebug();\n if (!isDev) return;\n\n const fields = Object.entries(errors)\n .map(([field, messages]) => ` ${field}: ${messages.join(', ')}`)\n .join('\\n');\n console.warn(`[timber] action schema validation failed:\\n${fields}`);\n}\n\n/**\n * Validate that all File objects in the input are within the size limit.\n * Returns validation errors keyed by field name, or null if all files are ok.\n */\nfunction validateFileSizes(input: Record<string, unknown>, limit: number): ValidationErrors | null {\n const limitKb = Math.round(limit / 1024);\n const limitLabel =\n limit >= 1024 * 1024 ? `${Math.round(limit / (1024 * 1024))}MB` : `${limitKb}KB`;\n\n const errors: ValidationErrors = {};\n\n function walk(obj: Record<string, unknown>, prefix: string): void {\n for (const [key, value] of Object.entries(obj)) {\n const path = prefix ? `${prefix}.${key}` : key;\n if (value instanceof File && value.size > limit) {\n errors[path] = [\n `File \"${value.name}\" (${formatSize(value.size)}) exceeds the ${limitLabel} limit`,\n ];\n } else if (Array.isArray(value)) {\n for (let i = 0; i < value.length; i++) {\n const item = value[i];\n const itemPath = `${path}[${i}]`;\n if (item instanceof File && item.size > limit) {\n (errors[itemPath] ??= []).push(\n `File \"${item.name}\" (${formatSize(item.size)}) exceeds the ${limitLabel} limit`\n );\n } else if (typeof item === 'object' && item !== null && !(item instanceof File)) {\n walk(item as Record<string, unknown>, itemPath);\n }\n }\n } else if (typeof value === 'object' && value !== null && !(value instanceof File)) {\n walk(value as Record<string, unknown>, path);\n }\n }\n }\n\n walk(input, '');\n return Object.keys(errors).length > 0 ? errors : null;\n}\n\n/**\n * Strip File objects from a value, returning a plain object safe for\n * serialization. File objects can't be serialized and shouldn't be echoed back.\n */\nfunction stripFiles(value: unknown): Record<string, unknown> | undefined {\n if (value === null || value === undefined) return undefined;\n if (typeof value !== 'object') return undefined;\n\n const result: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(value as Record<string, unknown>)) {\n if (v instanceof File) continue;\n if (Array.isArray(v)) {\n result[k] = v\n .filter((item) => !(item instanceof File))\n .map((item) =>\n typeof item === 'object' && item !== null && !(item instanceof File)\n ? (stripFiles(item) ?? {})\n : item\n );\n } else if (typeof v === 'object' && v !== null && !(v instanceof File)) {\n result[k] = stripFiles(v) ?? {};\n } else {\n result[k] = v;\n }\n }\n return result;\n}\n","/**\n * Form Flash — ALS-based store for no-JS form action results.\n *\n * When a no-JS form action completes, the server re-renders the page with\n * the action result injected via AsyncLocalStorage instead of redirecting\n * (which would discard the result). Server components read the flash and\n * pass it to client form components as the initial `useActionState` value.\n *\n * This follows the Remix/Rails pattern — the form component becomes the\n * single source of truth for both with-JS (React state) and no-JS (flash).\n *\n * The flash data is server-side only — never serialized to cookies or headers.\n *\n * See design/08-forms-and-actions.md §\"No-JS Error Round-Trip\"\n */\n\nimport type { ValidationErrors } from './action-client.ts';\nimport { formFlashAls } from './als-registry.ts';\n\n// ─── Types ───────────────────────────────────────────────────────────────\n\n/**\n * Flash data injected into the re-render after a no-JS form submission.\n *\n * This is the action result from the server action, stored in ALS so server\n * components can read it and pass it to client form components as the initial\n * state for `useActionState`. This makes the form component a single source\n * of truth for both with-JS and no-JS paths.\n *\n * The shape matches `ActionResult<unknown>` — it's one of:\n * - `{ data: ... }` — success\n * - `{ validationErrors, submittedValues }` — validation failure\n * - `{ serverError }` — server error\n */\nexport interface FormFlashData {\n /** Success data from the action. */\n data?: unknown;\n /** Validation errors keyed by field name. `_root` for form-level errors. */\n validationErrors?: ValidationErrors;\n /** Raw submitted values for repopulating form fields. File objects are excluded. */\n submittedValues?: Record<string, unknown>;\n /** Server error if the action threw an ActionError. */\n serverError?: { code: string; data?: Record<string, unknown> };\n}\n\n// ─── Public API ──────────────────────────────────────────────────────────\n\n/**\n * Read the form flash data for the current request.\n *\n * Returns `null` if no flash data is present (i.e., this is a normal page\n * render, not a re-render after a no-JS form submission).\n *\n * Pass the flash as the initial state to `useActionState` so the form\n * component has a single source of truth for both with-JS and no-JS paths:\n *\n * ```tsx\n * // app/contact/page.tsx (server component)\n * import { getFormFlash } from '@timber-js/app/server'\n *\n * export default function ContactPage() {\n * const flash = getFormFlash()\n * return <ContactForm flash={flash} />\n * }\n *\n * // app/contact/form.tsx (client component)\n * export function ContactForm({ flash }) {\n * const [result, action, isPending] = useActionState(submitContact, flash)\n * // result is the single source of truth — flash seeds it on no-JS\n * }\n * ```\n */\nexport function getFormFlash(): FormFlashData | null {\n return formFlashAls.getStore() ?? null;\n}\n\n// ─── Framework-Internal ──────────────────────────────────────────────────\n\n/**\n * Run a callback with form flash data in scope.\n *\n * Used by the action handler to re-render the page with validation errors\n * available via `getFormFlash()`. Not part of the public API.\n *\n * @internal\n */\nexport function runWithFormFlash<T>(data: FormFlashData, fn: () => T): T {\n return formFlashAls.run(data, fn);\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../src/shared/redirect-type.ts","../../src/server/sensitive-fields.ts","../../src/server/action-client.ts","../../src/server/form-flash.ts"],"sourcesContent":["/**\n * Next.js redirect type discriminator.\n *\n * Provided for API compatibility with libraries that import `RedirectType`\n * from `next/navigation`. In timber, `redirect()` always uses `replace`\n * semantics (no history entry for the redirect itself).\n *\n * Lives in shared/ (isomorphic) so both the server primitives and the\n * client-only next/navigation shim export the same definition without the\n * client shim pulling in server code.\n */\nexport const RedirectType = {\n push: 'push',\n replace: 'replace',\n} as const;\n\nexport type RedirectTypeValue = (typeof RedirectType)[keyof typeof RedirectType];\n","/**\n * Sensitive field stripping — removes password/token/CVV-style fields\n * from form values before they are echoed back to the client as\n * `submittedValues` for form repopulation.\n *\n * Applied to both action paths:\n * - With-JS action path: `createActionClient()` in `action-client.ts`\n * - No-JS form POST path: `handleFormAction()` in `action-handler.ts`\n *\n * Why: on a validation failure, timber echoes submitted form values back so\n * the user doesn't have to re-type everything. Without filtering, plaintext\n * passwords / credit-card numbers / TOTP codes would travel through the RSC\n * stream (with-JS) or land in the HTML as `defaultValue` attributes (no-JS)\n * — ending up in browser history, proxy logs, disk caches, and the\n * back-forward cache.\n *\n * Safe by default: the built-in deny-list is applied unconditionally unless\n * the user explicitly opts out via `forms.stripSensitiveFields: false` in\n * `timber.config.ts` or per-action via `createActionClient({ stripSensitiveFields: false })`.\n *\n * See design/08-forms-and-actions.md §\"Validation errors\"\n * See design/13-security.md §\"Sensitive field stripping\"\n * See TIM-816\n */\n\nimport { isDebug } from './debug.ts';\n\n// ─── Public types ────────────────────────────────────────────────────────\n\n/**\n * How to strip sensitive fields from `submittedValues`.\n *\n * - `true` / `undefined` — use the built-in deny-list (default, safe).\n * - `false` — do not strip anything (dev convenience; never do this in prod).\n * - `string[]` — additional field names to strip, merged with the built-in list.\n * - `(name) => boolean` — custom predicate, fully replaces the built-in list.\n * Return `true` to strip, `false` to keep. The `name` argument is the raw\n * (un-normalized) field name as it appeared in the submitted form.\n */\nexport type SensitiveFieldsOption = boolean | readonly string[] | ((name: string) => boolean);\n\n// ─── Built-in deny-list ──────────────────────────────────────────────────\n\n/**\n * Substring patterns matched against the normalized field name.\n * Normalization = lowercase + strip `_` and `-`.\n *\n * Any field whose normalized name *contains* one of these strings is\n * considered sensitive. Entries like `currentPassword`, `passwordConfirmation`,\n * and `user.password` all match via the `password` substring.\n */\nconst BUILTIN_SUBSTRING_PATTERNS: readonly string[] = [\n 'password',\n 'passwd',\n 'pwd',\n 'secret',\n 'apikey',\n 'accesstoken',\n 'refreshtoken',\n 'cvv',\n 'cvc',\n 'cardnumber',\n 'cardcvc',\n 'ssn',\n 'socialsecuritynumber',\n 'otp',\n 'totp',\n 'mfacode',\n 'twofactorcode',\n 'privatekey',\n];\n\n/**\n * Exact matches against the normalized field name. These are field names that\n * are too short or too common to substring-match safely. e.g. `token` alone\n * would match `csrfToken`, which is not sensitive — so `token` is exact-only,\n * while legitimate token fields are covered by `accesstoken` / `refreshtoken`.\n */\nconst BUILTIN_EXACT_PATTERNS: readonly string[] = ['token'];\n\n/**\n * Normalize a field name for deny-list comparison.\n * Lowercases the string and strips `_` and `-` so camelCase, snake_case, and\n * kebab-case variants all compare equal (`api_key` / `apiKey` / `api-key` →\n * `apikey`).\n */\nfunction normalize(name: string): string {\n let out = '';\n for (let i = 0; i < name.length; i++) {\n const ch = name.charCodeAt(i);\n if (ch === 0x5f /* _ */ || ch === 0x2d /* - */) continue;\n // A-Z → a-z\n if (ch >= 0x41 && ch <= 0x5a) {\n out += String.fromCharCode(ch + 32);\n } else {\n out += name[i];\n }\n }\n return out;\n}\n\n/**\n * Check whether a name matches the built-in deny-list (with optional extras).\n * Extras are merged into the substring pattern list after normalization.\n */\nfunction isBuiltinSensitive(name: string, extras?: readonly string[]): boolean {\n const normalized = normalize(name);\n if (BUILTIN_EXACT_PATTERNS.includes(normalized)) return true;\n for (const pattern of BUILTIN_SUBSTRING_PATTERNS) {\n if (normalized.includes(pattern)) return true;\n }\n if (extras && extras.length > 0) {\n for (const extra of extras) {\n const normExtra = normalize(extra);\n if (normExtra.length === 0) continue;\n if (normalized.includes(normExtra)) return true;\n }\n }\n return false;\n}\n\n// ─── Predicate resolution ────────────────────────────────────────────────\n\n/**\n * A resolved predicate: `null` means \"don't strip anything\" (the option was\n * explicitly `false`). Otherwise a function from raw field name → boolean.\n */\nexport type ResolvedSensitivePredicate = ((name: string) => boolean) | null;\n\n/**\n * Resolve a `SensitiveFieldsOption` into a concrete predicate.\n * Precedence: per-action > global > built-in default.\n *\n * - Per-action `undefined` → fall back to global.\n * - Global `undefined` → use built-in list.\n * - Either level set to `false` → disable stripping entirely (returns `null`).\n * - `true` → built-in list.\n * - `string[]` → built-in ∪ extras.\n * - function → custom, replaces the built-in list entirely.\n */\nexport function resolveSensitivePredicate(\n perAction: SensitiveFieldsOption | undefined,\n global: SensitiveFieldsOption | undefined\n): ResolvedSensitivePredicate {\n const chosen = perAction !== undefined ? perAction : global;\n\n if (chosen === false) return null;\n if (chosen === undefined || chosen === true) {\n return (name) => isBuiltinSensitive(name);\n }\n if (typeof chosen === 'function') {\n return chosen;\n }\n // Array of extra names merged with the built-in list.\n const extras = chosen;\n return (name) => isBuiltinSensitive(name, extras);\n}\n\n// ─── Module-level global config ──────────────────────────────────────────\n\nlet globalConfig: SensitiveFieldsOption | undefined;\n\n/**\n * Set the global `forms.stripSensitiveFields` config from `timber.config.ts`.\n * Called once at startup from `rsc-entry`.\n */\nexport function setGlobalSensitiveFieldsConfig(option: SensitiveFieldsOption | undefined): void {\n globalConfig = option;\n}\n\n/** Read the global `forms.stripSensitiveFields` config. */\nexport function getGlobalSensitiveFieldsConfig(): SensitiveFieldsOption | undefined {\n return globalConfig;\n}\n\n// ─── Stripping ───────────────────────────────────────────────────────────\n\n// One warning per field name per process — prevents log spam when a form is\n// submitted many times in dev mode.\nconst warnedFields = new Set<string>();\n\nfunction warnStripped(name: string): void {\n if (!isDebug()) return;\n if (warnedFields.has(name)) return;\n warnedFields.add(name);\n console.warn(\n `[timber] stripped sensitive field \"${name}\" from submittedValues. ` +\n `Override via forms.stripSensitiveFields in timber.config.ts.`\n );\n}\n\n/**\n * Walk an object (recursively) and return a copy with every key matching\n * `predicate` removed. Nested objects like `{ user: { password: '...' } }`\n * are handled — `user.password` is stripped while other `user.*` fields remain.\n *\n * - Arrays are walked element-wise (object entries inside arrays are cleaned).\n * - Non-plain values (strings, numbers, Files, Dates, etc.) are returned as-is.\n * - When a stripped key is encountered, it is omitted from the result entirely\n * — we do NOT set it to an empty string, because that would overwrite a\n * valid `defaultValue` the form author might have set.\n */\nexport function stripSensitiveFields<T>(value: T, predicate: ResolvedSensitivePredicate): T {\n // Null predicate = stripping disabled entirely.\n if (predicate === null) return value;\n if (value === null || value === undefined) return value;\n if (typeof value !== 'object') return value;\n if (value instanceof File || value instanceof Date) return value;\n\n if (Array.isArray(value)) {\n return value.map((item) => stripSensitiveFields(item, predicate)) as unknown as T;\n }\n\n const result: Record<string, unknown> = {};\n for (const [key, nested] of Object.entries(value as Record<string, unknown>)) {\n if (predicate(key)) {\n warnStripped(key);\n continue;\n }\n result[key] = stripSensitiveFields(nested, predicate);\n }\n return result as unknown as T;\n}\n\n// ─── Test helpers ────────────────────────────────────────────────────────\n\n/** Reset the \"warned once\" cache. Exposed for tests. */\nexport function __resetSensitiveFieldsWarnings(): void {\n warnedFields.clear();\n}\n","/**\n * createActionClient — typed middleware and schema validation for server actions.\n *\n * Inspired by next-safe-action. Provides a builder API:\n * createActionClient({ middleware }) → .schema(z.object(...)) → .action(fn)\n *\n * The resulting action function satisfies both:\n * 1. Direct call: action(input) → Promise<ActionResult>\n * 2. React useActionState: (prevState, formData) => Promise<ActionResult>\n *\n * See design/08-forms-and-actions.md §\"Middleware and Server Actions\"\n */\n\n// ─── ActionError ─────────────────────────────────────────────────────────\n\n/**\n * Typed error class for server actions. Carries a string code and optional data.\n * When thrown from middleware or the action body, the action short-circuits and\n * the client receives `result.serverError`.\n *\n * In production, unexpected errors (non-ActionError) return `{ code: 'INTERNAL_ERROR' }`\n * with no message. In dev, `data.message` is included.\n */\nexport class ActionError<TCode extends string = string> extends Error {\n readonly code: TCode;\n readonly data: Record<string, unknown> | undefined;\n\n constructor(code: TCode, data?: Record<string, unknown>) {\n super(`ActionError: ${code}`);\n this.name = 'ActionError';\n this.code = code;\n this.data = data;\n }\n}\n\n// ─── Standard Schema ──────────────────────────────────────────────────────\n\n/**\n * Standard Schema v1 interface (subset).\n * Zod ≥3.24, Valibot ≥1.0, and ArkType all implement this.\n * See https://github.com/standard-schema/standard-schema\n *\n * We use permissive types here to accept all compliant libraries without\n * requiring exact structural matches on issues/path shapes.\n */\ninterface StandardSchemaV1<Output = unknown> {\n readonly '~standard': {\n readonly validate: (\n value: unknown\n ) => StandardSchemaResult<Output> | Promise<StandardSchemaResult<Output>>;\n };\n}\n\ntype StandardSchemaResult<Output> =\n | { value: Output; issues?: undefined }\n | { value?: undefined; issues: ReadonlyArray<StandardSchemaIssue> };\n\ninterface StandardSchemaIssue {\n message: string;\n path?: ReadonlyArray<PropertyKey | { key: PropertyKey }>;\n}\n\n/** Check if a schema implements the Standard Schema protocol. */\nfunction isStandardSchema(schema: unknown): schema is StandardSchemaV1 {\n return (\n typeof schema === 'object' &&\n schema !== null &&\n '~standard' in schema &&\n typeof (schema as StandardSchemaV1)['~standard'].validate === 'function'\n );\n}\n\n// ─── Types ───────────────────────────────────────────────────────────────\n\n/**\n * Minimal schema interface — compatible with Zod, Valibot, ArkType, etc.\n *\n * Accepts either:\n * - Standard Schema (preferred): any object with `~standard.validate()`\n * - Legacy parse interface: objects with `.parse()` / `.safeParse()`\n *\n * At runtime, Standard Schema is detected via `~standard` property and\n * takes priority over the legacy interface.\n */\nexport type ActionSchema<T = unknown> = StandardSchemaV1<T> | LegacyActionSchema<T>;\n\n/** Legacy schema interface with .parse() / .safeParse(). */\ninterface LegacyActionSchema<T = unknown> {\n 'parse'(data: unknown): T;\n 'safeParse'?(data: unknown): { success: true; data: T } | { success: false; error: SchemaError };\n // Exclude Standard Schema objects from matching this interface\n '~standard'?: never;\n}\n\n/** Schema validation error shape (for legacy .safeParse()/.parse() interface). */\nexport interface SchemaError {\n issues?: Array<{ path?: Array<string | number>; message: string }>;\n flatten?(): { fieldErrors: Record<string, string[]> };\n}\n\n/** Flattened validation errors keyed by field name. */\nexport type ValidationErrors = Record<string, string[]>;\n\n/** Middleware function: returns context to merge into the action body's ctx. */\nexport type ActionMiddleware<TCtx = Record<string, unknown>> = () => Promise<TCtx> | TCtx;\n\n/** The result type returned to the client. */\nexport type ActionResult<TData = unknown> =\n | { data: TData; validationErrors?: never; serverError?: never; submittedValues?: never }\n | {\n data?: never;\n validationErrors: ValidationErrors;\n serverError?: never;\n /** Raw input values on validation failure — for repopulating form fields. */\n submittedValues?: Record<string, unknown>;\n }\n | {\n data?: never;\n validationErrors?: never;\n serverError: { code: string; data?: Record<string, unknown> };\n submittedValues?: never;\n };\n\n/** Context passed to the action body. */\nexport interface ActionContext<TCtx, TInput> {\n ctx: TCtx;\n input: TInput;\n}\n\n// ─── Builder ─────────────────────────────────────────────────────────────\n\ninterface ActionClientConfig<TCtx> {\n middleware?: ActionMiddleware<TCtx> | ActionMiddleware<Record<string, unknown>>[];\n /** Max file size in bytes. Files exceeding this are rejected with validation errors. */\n fileSizeLimit?: number;\n /**\n * Override the sensitive-field deny-list for this action client.\n * See `SensitiveFieldsOption` in `./sensitive-fields.ts`. Per-action config\n * takes precedence over the global `forms.stripSensitiveFields` option in\n * `timber.config.ts`. See design/08-forms-and-actions.md and TIM-816.\n */\n stripSensitiveFields?: SensitiveFieldsOption;\n}\n\n/** Intermediate builder returned by createActionClient(). */\nexport interface ActionBuilder<TCtx> {\n /** Declare the input schema. Validation errors are returned typed. */\n schema<TInput>(schema: ActionSchema<TInput>): ActionBuilderWithSchema<TCtx, TInput>;\n /** Define the action body without input validation. */\n action<TData>(\n fn: (ctx: ActionContext<TCtx, undefined>) => Promise<TData>\n ): ActionFn<TData, undefined>;\n}\n\n/** Builder after .schema() has been called. */\nexport interface ActionBuilderWithSchema<TCtx, TInput> {\n /** Define the action body with validated input. */\n action<TData>(fn: (ctx: ActionContext<TCtx, TInput>) => Promise<TData>): ActionFn<TData, TInput>;\n}\n\n/**\n * The final action function. Callable three ways:\n * - Direct: action(input) → Promise<ActionResult<TData>>\n * - React useActionState: action(prevState, formData) → Promise<ActionResult<TData>>\n * - React <form action={fn}>: action(formData) → void (return value ignored by React)\n *\n * The third overload exists purely for type compatibility with React's\n * `<form action>` prop, which expects `(formData: FormData) => void`.\n * At runtime the function still returns Promise<ActionResult>, but React\n * discards it. This lets validated actions be passed directly to forms\n * without casts.\n */\n/**\n * Map schema output keys to `string | undefined` for form-facing APIs.\n * HTML form values are always strings, and fields can be absent.\n * Gives autocomplete for field names without lying about value types.\n */\nexport type InputHint<T> =\n T extends Record<string, unknown> ? { [K in keyof T]: string | undefined } : T;\n\n/**\n * ActionFn — the callable returned by `createActionClient().action()`.\n *\n * Generic order: `<TData, TInput>` — TData first for backward compatibility.\n * Previously ActionFn had a single `<TData>` generic, so existing code like\n * `ActionFn<MyResult>` must still work with TData in the first position.\n * See TIM-797.\n */\nexport type ActionFn<TData = unknown, TInput = unknown> = {\n /** <form action={fn}> compatibility — React discards the return value. */\n (formData: FormData): void;\n /** Direct call: action(input) — optional when TInput is undefined/unknown (no-schema actions). */\n (\n ...args: undefined extends TInput ? [input?: TInput] : [input: TInput]\n ): Promise<ActionResult<TData>>;\n /** React useActionState: action(prevState, formData) */\n (prevState: ActionResult<TData> | null, formData: FormData): Promise<ActionResult<TData>>;\n};\n\n// ─── Implementation ──────────────────────────────────────────────────────\n\n/**\n * Run middleware array or single function. Returns merged context.\n */\nasync function runActionMiddleware<TCtx>(\n middleware: ActionMiddleware<TCtx> | ActionMiddleware<Record<string, unknown>>[] | undefined\n): Promise<TCtx> {\n if (!middleware) {\n return {} as TCtx;\n }\n\n if (Array.isArray(middleware)) {\n let merged = {} as Record<string, unknown>;\n for (const mw of middleware) {\n const result = await mw();\n merged = { ...merged, ...result };\n }\n return merged as TCtx;\n }\n\n return await middleware();\n}\n\n// Re-export parseFormData for use throughout the framework\nimport { parseFormData } from './form-data.ts';\nimport { formatSize } from '../utils/format.ts';\nimport { isDebug, isDevMode } from './debug.ts';\nimport { isDenySignal, isRedirectSignal } from './signal-identity.ts';\nimport {\n stripSensitiveFields,\n resolveSensitivePredicate,\n getGlobalSensitiveFieldsConfig,\n type SensitiveFieldsOption,\n} from './sensitive-fields.ts';\n\n/**\n * Extract validation errors from a schema error.\n * Supports Zod's flatten() and generic issues array.\n */\nfunction extractValidationErrors(error: SchemaError): ValidationErrors {\n // Zod-style flatten\n if (typeof error.flatten === 'function') {\n return error.flatten().fieldErrors;\n }\n\n // Generic issues array\n if (error.issues) {\n const errors: ValidationErrors = {};\n for (const issue of error.issues) {\n const path = issue.path?.join('.') ?? '_root';\n if (!errors[path]) errors[path] = [];\n errors[path].push(issue.message);\n }\n return errors;\n }\n\n return { _root: ['Validation failed'] };\n}\n\n/**\n * Extract validation errors from Standard Schema issues.\n */\nfunction extractStandardSchemaErrors(issues: ReadonlyArray<StandardSchemaIssue>): ValidationErrors {\n const errors: ValidationErrors = {};\n for (const issue of issues) {\n const path =\n issue.path\n ?.map((p) => {\n // Standard Schema path items can be { key: ... } objects or bare PropertyKey values\n if (typeof p === 'object' && p !== null && 'key' in p) return String(p.key);\n return String(p);\n })\n .join('.') ?? '_root';\n if (!errors[path]) errors[path] = [];\n errors[path].push(issue.message);\n }\n return Object.keys(errors).length > 0 ? errors : { _root: ['Validation failed'] };\n}\n\n/**\n * Wrap unexpected errors into a safe server error result.\n * ActionError → typed result. Other errors → INTERNAL_ERROR (no leak).\n *\n * Exported for use by action-handler.ts to catch errors from raw 'use server'\n * functions that don't use createActionClient.\n */\nexport function handleActionError(error: unknown): ActionResult<never> {\n if (error instanceof ActionError) {\n return {\n serverError: {\n code: error.code,\n ...(error.data ? { data: error.data } : {}),\n },\n };\n }\n\n // In dev, include the message for debugging.\n // Uses isDevMode() — NOT isDebug() — because this data is sent to the\n // browser. TIMBER_DEBUG must never cause error messages to leak to clients.\n // See design/13-security.md principle 4: \"Errors don't leak.\"\n const devMode = isDevMode();\n return {\n serverError: {\n code: 'INTERNAL_ERROR',\n ...(devMode && error instanceof Error ? { data: { message: error.message } } : {}),\n },\n };\n}\n\n/**\n * Create a typed action client with middleware and schema validation.\n *\n * @example\n * ```ts\n * const action = createActionClient({\n * middleware: async () => {\n * const user = await getUser()\n * if (!user) throw new ActionError('UNAUTHORIZED')\n * return { user }\n * },\n * })\n *\n * export const createTodo = action\n * .schema(z.object({ title: z.string().min(1) }))\n * .action(async ({ input, ctx }) => {\n * await db.todos.create({ ...input, userId: ctx.user.id })\n * })\n * ```\n */\nexport function createActionClient<TCtx = Record<string, never>>(\n config: ActionClientConfig<TCtx> = {}\n): ActionBuilder<TCtx> {\n function buildAction<TInput, TData>(\n schema: ActionSchema<TInput> | undefined,\n fn: (ctx: ActionContext<TCtx, TInput>) => Promise<TData>\n ): ActionFn<TData, TInput> {\n async function actionHandler(...args: unknown[]): Promise<ActionResult<TData>> {\n try {\n // Run middleware\n const ctx = await runActionMiddleware(config.middleware);\n\n // Determine input — either FormData (from useActionState) or direct arg\n let rawInput: unknown;\n if (args.length === 2 && args[1] instanceof FormData) {\n // Called as (prevState, formData) by React useActionState (with-JS path)\n rawInput = schema ? parseFormData(args[1]) : args[1];\n } else if (args.length === 1 && args[0] instanceof FormData) {\n // No-JS path: React's decodeAction binds FormData as the sole argument.\n // The form POSTs without JavaScript, decodeAction resolves the server\n // reference and binds the FormData, then executeAction calls fn() with\n // no additional args — so the bound FormData arrives as args[0].\n rawInput = schema ? parseFormData(args[0]) : args[0];\n } else {\n // Direct call: action(input)\n rawInput = args[0];\n }\n\n // Resolve the sensitive-field stripping predicate once per invocation.\n // Precedence: per-action (config.stripSensitiveFields) > global\n // (forms.stripSensitiveFields from timber.config.ts) > built-in deny-list.\n // See TIM-816.\n const sensitivePredicate = resolveSensitivePredicate(\n config.stripSensitiveFields,\n getGlobalSensitiveFieldsConfig()\n );\n\n // Capture a \"safe-to-echo\" snapshot of the raw input once. Files are\n // stripped (can't serialize, shouldn't echo back) and sensitive fields\n // (passwords, tokens, CVV, etc.) are removed before they would land\n // in the RSC payload → client form `defaultValue` → DOM.\n const buildSubmittedValues = (): Record<string, unknown> | undefined => {\n const withoutFiles = stripFiles(rawInput);\n if (withoutFiles === undefined) return undefined;\n return stripSensitiveFields(withoutFiles, sensitivePredicate);\n };\n\n // Validate file sizes before schema validation.\n if (config.fileSizeLimit !== undefined && rawInput && typeof rawInput === 'object') {\n const fileSizeErrors = validateFileSizes(\n rawInput as Record<string, unknown>,\n config.fileSizeLimit\n );\n if (fileSizeErrors) {\n return { validationErrors: fileSizeErrors, submittedValues: buildSubmittedValues() };\n }\n }\n\n // Capture submitted values for repopulation on validation failure.\n const submittedValues = schema ? buildSubmittedValues() : undefined;\n\n // Validate with schema if provided\n let input: TInput;\n if (schema) {\n if (isStandardSchema(schema)) {\n // Standard Schema protocol (Zod ≥3.24, Valibot ≥1.0, ArkType)\n const result = schema['~standard'].validate(rawInput);\n if (result instanceof Promise) {\n throw new Error(\n '[timber] createActionClient: schema returned a Promise — only sync schemas are supported.'\n );\n }\n if (result.issues) {\n const validationErrors = extractStandardSchemaErrors(result.issues);\n logValidationFailure(validationErrors);\n return { validationErrors, submittedValues };\n }\n input = result.value;\n } else if (typeof schema.safeParse === 'function') {\n const result = schema.safeParse(rawInput);\n if (!result.success) {\n const validationErrors = extractValidationErrors(result.error);\n logValidationFailure(validationErrors);\n return { validationErrors, submittedValues };\n }\n input = result.data;\n } else {\n try {\n input = schema.parse(rawInput);\n } catch (parseError) {\n const validationErrors = extractValidationErrors(parseError as SchemaError);\n logValidationFailure(validationErrors);\n return { validationErrors, submittedValues };\n }\n }\n } else {\n input = rawInput as TInput;\n }\n\n // Execute the action body\n const data = await fn({ ctx, input });\n return { data };\n } catch (error) {\n // Re-throw redirect/deny signals — these are control flow, not errors.\n // They must propagate to executeAction() which converts them to proper\n // HTTP responses (302 redirect, 4xx deny). Catching them here would\n // wrap them as INTERNAL_ERROR and break redirect()/redirectExternal()/deny().\n if (isRedirectSignal(error) || isDenySignal(error)) {\n throw error;\n }\n return handleActionError(error);\n }\n }\n\n return actionHandler as ActionFn<TData, TInput>;\n }\n\n return {\n schema<TInput>(schema: ActionSchema<TInput>) {\n return {\n action<TData>(\n fn: (ctx: ActionContext<TCtx, TInput>) => Promise<TData>\n ): ActionFn<TData, TInput> {\n return buildAction(schema, fn);\n },\n };\n },\n action<TData>(\n fn: (ctx: ActionContext<TCtx, undefined>) => Promise<TData>\n ): ActionFn<TData, undefined> {\n return buildAction(undefined, fn as (ctx: ActionContext<TCtx, unknown>) => Promise<TData>);\n },\n };\n}\n\n// ─── validated() ────────────────────────────────────────────────────────\n\n/**\n * Convenience wrapper for the common case: validate input, run handler.\n * No middleware needed.\n *\n * @example\n * ```ts\n * 'use server'\n * import { validated } from '@timber-js/app/server'\n * import { z } from 'zod'\n *\n * export const createTodo = validated(\n * z.object({ title: z.string().min(1) }),\n * async (input) => {\n * await db.todos.create(input)\n * }\n * )\n * ```\n */\nexport function validated<TInput, TData>(\n schema: ActionSchema<TInput>,\n handler: (input: TInput) => Promise<TData>\n): ActionFn<TData, TInput> {\n return createActionClient()\n .schema(schema)\n .action(async ({ input }) => handler(input));\n}\n\n// ─── Helpers ────────────────────────────────────────────────────────────\n\n/**\n * Log validation failures in dev mode so developers can see what went wrong.\n * In production, validation errors are only returned to the client.\n */\nfunction logValidationFailure(errors: ValidationErrors): void {\n const isDev = isDebug();\n if (!isDev) return;\n\n const fields = Object.entries(errors)\n .map(([field, messages]) => ` ${field}: ${messages.join(', ')}`)\n .join('\\n');\n console.warn(`[timber] action schema validation failed:\\n${fields}`);\n}\n\n/**\n * Validate that all File objects in the input are within the size limit.\n * Returns validation errors keyed by field name, or null if all files are ok.\n */\nfunction validateFileSizes(input: Record<string, unknown>, limit: number): ValidationErrors | null {\n const limitKb = Math.round(limit / 1024);\n const limitLabel =\n limit >= 1024 * 1024 ? `${Math.round(limit / (1024 * 1024))}MB` : `${limitKb}KB`;\n\n const errors: ValidationErrors = {};\n\n function walk(obj: Record<string, unknown>, prefix: string): void {\n for (const [key, value] of Object.entries(obj)) {\n const path = prefix ? `${prefix}.${key}` : key;\n if (value instanceof File && value.size > limit) {\n errors[path] = [\n `File \"${value.name}\" (${formatSize(value.size)}) exceeds the ${limitLabel} limit`,\n ];\n } else if (Array.isArray(value)) {\n for (let i = 0; i < value.length; i++) {\n const item = value[i];\n const itemPath = `${path}[${i}]`;\n if (item instanceof File && item.size > limit) {\n (errors[itemPath] ??= []).push(\n `File \"${item.name}\" (${formatSize(item.size)}) exceeds the ${limitLabel} limit`\n );\n } else if (typeof item === 'object' && item !== null && !(item instanceof File)) {\n walk(item as Record<string, unknown>, itemPath);\n }\n }\n } else if (typeof value === 'object' && value !== null && !(value instanceof File)) {\n walk(value as Record<string, unknown>, path);\n }\n }\n }\n\n walk(input, '');\n return Object.keys(errors).length > 0 ? errors : null;\n}\n\n/**\n * Strip File objects from a value, returning a plain object safe for\n * serialization. File objects can't be serialized and shouldn't be echoed back.\n */\nfunction stripFiles(value: unknown): Record<string, unknown> | undefined {\n if (value === null || value === undefined) return undefined;\n if (typeof value !== 'object') return undefined;\n\n const result: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(value as Record<string, unknown>)) {\n if (v instanceof File) continue;\n if (Array.isArray(v)) {\n result[k] = v\n .filter((item) => !(item instanceof File))\n .map((item) =>\n typeof item === 'object' && item !== null && !(item instanceof File)\n ? (stripFiles(item) ?? {})\n : item\n );\n } else if (typeof v === 'object' && v !== null && !(v instanceof File)) {\n result[k] = stripFiles(v) ?? {};\n } else {\n result[k] = v;\n }\n }\n return result;\n}\n","/**\n * Form Flash — ALS-based store for no-JS form action results.\n *\n * When a no-JS form action completes, the server re-renders the page with\n * the action result injected via AsyncLocalStorage instead of redirecting\n * (which would discard the result). Server components read the flash and\n * pass it to client form components as the initial `useActionState` value.\n *\n * This follows the Remix/Rails pattern — the form component becomes the\n * single source of truth for both with-JS (React state) and no-JS (flash).\n *\n * The flash data is server-side only — never serialized to cookies or headers.\n *\n * See design/08-forms-and-actions.md §\"No-JS Result Round-Trip\"\n */\n\nimport type { ValidationErrors } from './action-client.ts';\nimport { formFlashAls } from './als-registry.ts';\n\n// ─── Types ───────────────────────────────────────────────────────────────\n\n/**\n * Flash data injected into the re-render after a no-JS form submission.\n *\n * This is the action result from the server action, stored in ALS so server\n * components can read it and pass it to client form components as the initial\n * state for `useActionState`. This makes the form component a single source\n * of truth for both with-JS and no-JS paths.\n *\n * The shape matches `ActionResult<unknown>` — it's one of:\n * - `{ data: ... }` — success\n * - `{ validationErrors, submittedValues }` — validation failure\n * - `{ serverError }` — server error\n */\nexport interface FormFlashData {\n /** Success data from the action. */\n data?: unknown;\n /** Validation errors keyed by field name. `_root` for form-level errors. */\n validationErrors?: ValidationErrors;\n /** Raw submitted values for repopulating form fields. File objects are excluded. */\n submittedValues?: Record<string, unknown>;\n /** Server error if the action threw an ActionError. */\n serverError?: { code: string; data?: Record<string, unknown> };\n}\n\n// ─── Public API ──────────────────────────────────────────────────────────\n\n/**\n * Read the form flash data for the current request.\n *\n * Returns `null` if no flash data is present (i.e., this is a normal page\n * render, not a re-render after a no-JS form submission).\n *\n * Pass the flash as the initial state to `useActionState` so the form\n * component has a single source of truth for both with-JS and no-JS paths:\n *\n * ```tsx\n * // app/contact/page.tsx (server component)\n * import { getFormFlash } from '@timber-js/app/server'\n *\n * export default function ContactPage() {\n * const flash = getFormFlash()\n * return <ContactForm flash={flash} />\n * }\n *\n * // app/contact/form.tsx (client component)\n * export function ContactForm({ flash }) {\n * const [result, action, isPending] = useActionState(submitContact, flash)\n * // result is the single source of truth — flash seeds it on no-JS\n * }\n * ```\n */\nexport function getFormFlash(): FormFlashData | null {\n return formFlashAls.getStore() ?? null;\n}\n\n// ─── Framework-Internal ──────────────────────────────────────────────────\n\n/**\n * Run a callback with form flash data in scope.\n *\n * Used by the action handler to re-render the page with validation errors\n * available via `getFormFlash()`. Not part of the public API.\n *\n * @internal\n */\nexport function runWithFormFlash<T>(data: FormFlashData, fn: () => T): T {\n return formFlashAls.run(data, fn);\n}\n"],"mappings":"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"}
|