@timber-js/app 0.2.0-alpha.212 → 0.2.0-alpha.214
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-CCdnVtWm.js → actions-CUh3cClk.js} +43 -8
- package/dist/_chunks/{actions-CCdnVtWm.js.map → actions-CUh3cClk.js.map} +1 -1
- package/dist/_chunks/{canonicalize-CgHoscYO.js → canonicalize-BAWkWiKK.js} +28 -2
- package/dist/_chunks/{canonicalize-CgHoscYO.js.map → canonicalize-BAWkWiKK.js.map} +1 -1
- package/dist/_chunks/{chains-BfoPFraI.js → chains-CjK1Eu6a.js} +2 -2
- package/dist/_chunks/{chains-BfoPFraI.js.map → chains-CjK1Eu6a.js.map} +1 -1
- package/dist/_chunks/{classify-BT66U83D.js → classify-GAdt6aiA.js} +2 -2
- package/dist/_chunks/{classify-BT66U83D.js.map → classify-GAdt6aiA.js.map} +1 -1
- package/dist/_chunks/{cli-check-BfQ54-UJ.js → cli-check-DOzjlycg.js} +3 -3
- package/dist/_chunks/{cli-check-BfQ54-UJ.js.map → cli-check-DOzjlycg.js.map} +1 -1
- package/dist/_chunks/{cli-schema-sync-czh2dsLs.js → cli-schema-sync-B9wDaQvW.js} +2 -2
- package/dist/_chunks/{cli-schema-sync-czh2dsLs.js.map → cli-schema-sync-B9wDaQvW.js.map} +1 -1
- package/dist/_chunks/{client-dep-entries-CQwpb8dI.js → client-dep-entries-DyDqXOF9.js} +2 -2
- package/dist/_chunks/{client-dep-entries-CQwpb8dI.js.map → client-dep-entries-DyDqXOF9.js.map} +1 -1
- package/dist/_chunks/{convention-lint-BEVW4EID.js → convention-lint-B3QEGJX7.js} +2 -2
- package/dist/_chunks/{convention-lint-BEVW4EID.js.map → convention-lint-B3QEGJX7.js.map} +1 -1
- package/dist/_chunks/{dev-server-FKxptbnI.js → dev-server-_9L4KWC-.js} +3 -3
- package/dist/_chunks/{dev-server-FKxptbnI.js.map → dev-server-_9L4KWC-.js.map} +1 -1
- package/dist/_chunks/{error-boundary-DsNScGRM.js → error-boundary-xxxLtXt6.js} +38 -5
- package/dist/_chunks/{error-boundary-DsNScGRM.js.map → error-boundary-xxxLtXt6.js.map} +1 -1
- package/dist/_chunks/{live-graph-C_4v-fHv.js → live-graph-Cd3YHvrH.js} +4 -4
- package/dist/_chunks/{live-graph-C_4v-fHv.js.map → live-graph-Cd3YHvrH.js.map} +1 -1
- package/dist/_chunks/{poison-scan-C92liMAr.js → poison-scan-BnJjBOkn.js} +2 -2
- package/dist/_chunks/{poison-scan-C92liMAr.js.map → poison-scan-BnJjBOkn.js.map} +1 -1
- package/dist/_chunks/{scanner-CQt12vE2.js → scanner-CKAT5gRx.js} +2 -2
- package/dist/_chunks/{scanner-CQt12vE2.js.map → scanner-CKAT5gRx.js.map} +1 -1
- package/dist/_chunks/{walkers-DAT4avhZ.js → walkers-DwCXEyRu.js} +3 -3
- package/dist/_chunks/{walkers-DAT4avhZ.js.map → walkers-DwCXEyRu.js.map} +1 -1
- package/dist/adapters/nitro-preview.d.ts.map +1 -1
- package/dist/adapters/nitro.js +11 -2
- package/dist/adapters/nitro.js.map +1 -1
- package/dist/analyze/crawl-entry.js +3 -3
- package/dist/analyze/graph-command.js +2 -2
- package/dist/cli.js +3 -3
- package/dist/client/browser-entry/action-dispatch.d.ts.map +1 -1
- package/dist/client/browser-entry/action-queue.d.ts +1 -0
- package/dist/client/browser-entry/action-queue.d.ts.map +1 -1
- package/dist/client/browser-entry/form-state.d.ts +22 -0
- package/dist/client/browser-entry/form-state.d.ts.map +1 -0
- package/dist/client/browser-entry/hydrate.d.ts +9 -1
- package/dist/client/browser-entry/hydrate.d.ts.map +1 -1
- package/dist/client/browser-entry/index.d.ts +2 -0
- package/dist/client/browser-entry/index.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/index.d.ts +1 -0
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +90 -2
- package/dist/client/index.js.map +1 -1
- package/dist/client/internal.js +26 -10
- package/dist/client/internal.js.map +1 -1
- package/dist/client/navigation-transition.d.ts +11 -2
- package/dist/client/navigation-transition.d.ts.map +1 -1
- package/dist/client/router-effects.d.ts +70 -8
- 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 +12 -1
- package/dist/client/router-types.d.ts.map +1 -1
- package/dist/client/router.d.ts.map +1 -1
- package/dist/client/use-form-field.d.ts +39 -0
- package/dist/client/use-form-field.d.ts.map +1 -0
- package/dist/config-types.d.ts +2 -1
- package/dist/config-types.d.ts.map +1 -1
- package/dist/index.js +5 -5
- package/dist/index.js.map +1 -1
- package/dist/routing/index.js +2 -2
- package/dist/server/action-client.d.ts +11 -2
- package/dist/server/action-client.d.ts.map +1 -1
- package/dist/server/canonicalize.d.ts +22 -0
- package/dist/server/canonicalize.d.ts.map +1 -1
- package/dist/server/flight-scripts.d.ts +9 -0
- package/dist/server/flight-scripts.d.ts.map +1 -1
- package/dist/server/form-data.d.ts +13 -4
- package/dist/server/form-data.d.ts.map +1 -1
- package/dist/server/form-state-flight.d.ts +32 -0
- package/dist/server/form-state-flight.d.ts.map +1 -0
- package/dist/server/index.js +37 -23
- package/dist/server/index.js.map +1 -1
- package/dist/server/internal.js +54 -3
- package/dist/server/internal.js.map +1 -1
- package/dist/server/pipeline-helpers.d.ts +31 -0
- package/dist/server/pipeline-helpers.d.ts.map +1 -1
- package/dist/server/pipeline.d.ts.map +1 -1
- package/dist/server/rsc-entry/action-dispatcher.d.ts.map +1 -1
- package/dist/server/rsc-entry/index.d.ts.map +1 -1
- package/dist/server/rsc-entry/render-route.d.ts +2 -0
- package/dist/server/rsc-entry/render-route.d.ts.map +1 -1
- package/dist/server/rsc-entry/ssr-renderer.d.ts +6 -0
- package/dist/server/rsc-entry/ssr-renderer.d.ts.map +1 -1
- package/dist/server/ssr-bridge-types.d.ts +7 -5
- package/dist/server/ssr-bridge-types.d.ts.map +1 -1
- package/dist/server/ssr-entry.d.ts.map +1 -1
- package/dist/server/ssr-form-state.d.ts +30 -0
- package/dist/server/ssr-form-state.d.ts.map +1 -0
- package/dist/shared/form-state-flight.d.ts +36 -0
- package/dist/shared/form-state-flight.d.ts.map +1 -0
- package/docs/api/31-api-client.mdx +22 -0
- package/docs/api/34-api-config.mdx +1 -1
- package/docs/learn/08-forms-and-actions.mdx +109 -22
- package/package.json +1 -1
- package/src/adapters/nitro-preview.ts +10 -1
- package/src/client/browser-entry/action-dispatch.ts +34 -10
- package/src/client/browser-entry/action-queue.ts +1 -1
- package/src/client/browser-entry/form-state.ts +48 -0
- package/src/client/browser-entry/hydrate.ts +9 -18
- package/src/client/browser-entry/index.ts +25 -7
- package/src/client/browser-entry/router-init.ts +3 -2
- package/src/client/index.ts +1 -0
- package/src/client/navigation-transition.ts +13 -2
- package/src/client/router-effects.ts +99 -10
- package/src/client/router-pipeline.ts +7 -3
- package/src/client/router-types.ts +12 -1
- package/src/client/router.ts +35 -6
- package/src/client/use-form-field.ts +132 -0
- package/src/config-types.ts +2 -1
- package/src/server/action-client.ts +68 -53
- package/src/server/canonicalize.ts +29 -0
- package/src/server/flight-scripts.ts +13 -0
- package/src/server/form-data.ts +62 -10
- package/src/server/form-state-flight.ts +67 -0
- package/src/server/pipeline-helpers.ts +61 -0
- package/src/server/pipeline.ts +13 -1
- package/src/server/rsc-entry/action-dispatcher.ts +3 -0
- package/src/server/rsc-entry/index.ts +1 -0
- package/src/server/rsc-entry/render-route.ts +16 -0
- package/src/server/rsc-entry/ssr-renderer.ts +12 -14
- package/src/server/ssr-bridge-types.ts +7 -6
- package/src/server/ssr-entry.ts +16 -3
- package/src/server/ssr-form-state.ts +58 -0
- package/src/shared/form-state-flight.ts +74 -0
- package/dist/server/form-state-embed.d.ts +0 -32
- package/dist/server/form-state-embed.d.ts.map +0 -1
- package/src/server/form-state-embed.ts +0 -63
|
@@ -31,6 +31,37 @@ import type { PhaseName } from './pipeline-outcome.ts';
|
|
|
31
31
|
* See design/08-forms-and-actions.md §"CSRF Protection".
|
|
32
32
|
*/
|
|
33
33
|
export declare function csrfGate(req: Request, canonicalPath: string, csrf: CsrfConfig, matchRoute: RouteMatcher): Response | null;
|
|
34
|
+
/**
|
|
35
|
+
* The redirect for a safe request whose URL is not canonical, or null to
|
|
36
|
+
* serve it.
|
|
37
|
+
*
|
|
38
|
+
* A GET for `/admin/` used to render with a 200 at that URL. The server
|
|
39
|
+
* rendered with the canonical `/admin` while the browser hydrated with the
|
|
40
|
+
* raw `/admin/`, so anything branching on `usePathname()` hydrated
|
|
41
|
+
* mismatched (TIM-1571). Redirecting makes the address bar — the client's
|
|
42
|
+
* copy of the URL — the canonical one, and leaves one URL per page for
|
|
43
|
+
* caches and crawlers.
|
|
44
|
+
*
|
|
45
|
+
* Only GET and HEAD are redirected. Every other method — an action's POST,
|
|
46
|
+
* a CORS preflight — is served at the canonical path, as before: a
|
|
47
|
+
* redirected POST is re-sent by the browser, which an action must not
|
|
48
|
+
* depend on.
|
|
49
|
+
*
|
|
50
|
+
* Runs inside proxy.ts's `next()`, not before it, so proxy.ts still runs
|
|
51
|
+
* on every request and its headers land on the redirect. A cross-origin
|
|
52
|
+
* `GET /api/items/` needs proxy.ts's CORS headers on the 308 itself:
|
|
53
|
+
* browsers apply the CORS check to a redirect response.
|
|
54
|
+
*
|
|
55
|
+
* A document request gets a relative 308 (never built from `Host`). A page
|
|
56
|
+
* navigation gets the router's soft redirect, decided exactly as for a
|
|
57
|
+
* `redirect()` outcome: an RSC request that does not match a route.ts
|
|
58
|
+
* handler (`buildRedirectResponse`). The framework's `_rsc` cache key is
|
|
59
|
+
* dropped from the soft target; the router appends a fresh one when it
|
|
60
|
+
* fetches the canonical URL.
|
|
61
|
+
*
|
|
62
|
+
* See design/07-routing.md §"Non-Canonical URLs Redirect".
|
|
63
|
+
*/
|
|
64
|
+
export declare function canonicalRedirect(req: Request, canonicalPath: string, stripTrailingSlash: boolean, matchRoute: RouteMatcher): Response | null;
|
|
34
65
|
/**
|
|
35
66
|
* Resolver closure produced once at pipeline construction. The lazy variant
|
|
36
67
|
* still calls `loader()` per-request (HMR relies on re-importing), but the
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pipeline-helpers.d.ts","sourceRoot":"","sources":["../../src/server/pipeline-helpers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAS9C,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAIjD,OAAO,EAA+B,KAAK,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAElF,OAAO,EAAsC,KAAK,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAClG,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAC/D,OAAO,EAAuC,KAAK,UAAU,EAAE,MAAM,WAAW,CAAC;AACjF,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"pipeline-helpers.d.ts","sourceRoot":"","sources":["../../src/server/pipeline-helpers.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pipeline.d.ts","sourceRoot":"","sources":["../../src/server/pipeline.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"pipeline.d.ts","sourceRoot":"","sources":["../../src/server/pipeline.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"action-dispatcher.d.ts","sourceRoot":"","sources":["../../../src/server/rsc-entry/action-dispatcher.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAGH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAC1D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAC7C,OAAO,KAAK,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAEpE,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAC;AACpE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAIxD;;;;;;;;GAQG;AACH,MAAM,MAAM,yBAAyB,GAAG,CAAC,GAAG,EAAE,OAAO,KAAK,kBAAkB,CAAC;AAE7E,yEAAyE;AACzE,MAAM,WAAW,oBAAoB;IACnC,6EAA6E;IAC7E,UAAU,EAAE,UAAU,CAAC;IACvB,2DAA2D;IAC3D,UAAU,CAAC,EAAE,gBAAgB,CAAC,QAAQ,CAAC,CAAC;IACxC,oEAAoE;IACpE,eAAe,CAAC,EAAE,qBAAqB,CAAC;IACxC,8DAA8D;IAC9D,uBAAuB,EAAE,yBAAyB,CAAC;IACnD;;;;;;OAMG;IACH,UAAU,EAAE,YAAY,CAAC;CAC1B;AAID;;;;;;;;;;GAUG;AACH,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,oBAAoB,GACzB,CACD,GAAG,EAAE,OAAO,EACZ,aAAa,EAAE,MAAM,EACrB,OAAO,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,OAAO,EAAE,eAAe,KAAK,OAAO,CAAC,QAAQ,CAAC,KACnE,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"action-dispatcher.d.ts","sourceRoot":"","sources":["../../../src/server/rsc-entry/action-dispatcher.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAGH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAC1D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAC7C,OAAO,KAAK,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAEpE,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAC;AACpE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAIxD;;;;;;;;GAQG;AACH,MAAM,MAAM,yBAAyB,GAAG,CAAC,GAAG,EAAE,OAAO,KAAK,kBAAkB,CAAC;AAE7E,yEAAyE;AACzE,MAAM,WAAW,oBAAoB;IACnC,6EAA6E;IAC7E,UAAU,EAAE,UAAU,CAAC;IACvB,2DAA2D;IAC3D,UAAU,CAAC,EAAE,gBAAgB,CAAC,QAAQ,CAAC,CAAC;IACxC,oEAAoE;IACpE,eAAe,CAAC,EAAE,qBAAqB,CAAC;IACxC,8DAA8D;IAC9D,uBAAuB,EAAE,yBAAyB,CAAC;IACnD;;;;;;OAMG;IACH,UAAU,EAAE,YAAY,CAAC;CAC1B;AAID;;;;;;;;;;GAUG;AACH,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,oBAAoB,GACzB,CACD,GAAG,EAAE,OAAO,EACZ,aAAa,EAAE,MAAM,EACrB,OAAO,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,OAAO,EAAE,eAAe,KAAK,OAAO,CAAC,QAAQ,CAAC,KACnE,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CAiE5B"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/server/rsc-entry/index.ts"],"names":[],"mappings":"AA0CA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,mCAAmC,CAAC;AA8ClE;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,QAAQ,GAAG,IAAI,CAEjD;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/server/rsc-entry/index.ts"],"names":[],"mappings":"AA0CA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,mCAAmC,CAAC;AA8ClE;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,QAAQ,GAAG,IAAI,CAEjD;AAiVD,OAAO,EAAE,uBAAuB,EAAE,MAAM,0BAA0B,CAAC;AAInE,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAK1D,OAAO,EAAE,wBAAwB,EAAE,MAAM,4BAA4B,CAAC;AAItE,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEpD;;;;;;;;GAQG;AACH,wBAAgB,wBAAwB,CACtC,OAAO,EAAE,OAAO,wBAAwB,EAAE,cAAc,GACvD,OAAO,CAAC,OAAO,wBAAwB,EAAE,cAAc,CAAC,CAE1D;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,2BAA2B,CACzC,OAAO,EAAE,OAAO,wBAAwB,EAAE,uBAAuB,GAChE,OAAO,CAAC,OAAO,wBAAwB,EAAE,uBAAuB,CAAC,CAQnE;8BAwCoB,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC"}
|
|
@@ -31,6 +31,8 @@ export interface RenderRouteDeps {
|
|
|
31
31
|
clientSegmentCache: boolean;
|
|
32
32
|
renderDenyFallback: DenyFallbackRenderer;
|
|
33
33
|
buildManifest: BuildManifest;
|
|
34
|
+
/** The resolved `renderTimeoutMs`: bounds encoding a no-JS form state. */
|
|
35
|
+
renderTimeoutMs: number;
|
|
34
36
|
globalError?: {
|
|
35
37
|
load: () => Promise<unknown>;
|
|
36
38
|
filePath: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"render-route.d.ts","sourceRoot":"","sources":["../../../src/server/rsc-entry/render-route.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAS1D,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAClE,OAAO,KAAK,EAAE,mBAAmB,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAOtE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"render-route.d.ts","sourceRoot":"","sources":["../../../src/server/rsc-entry/render-route.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAS1D,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAClE,OAAO,KAAK,EAAE,mBAAmB,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAOtE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAQ/D;;;;GAIG;AACH,MAAM,WAAW,eAAe;IAC9B,eAAe,EAAE,qBAAqB,CAAC;IACvC,gBAAgB,EAAE,OAAO,CAAC;IAC1B,kBAAkB,EAAE,OAAO,CAAC;IAC5B,kBAAkB,EAAE,oBAAoB,CAAC;IACzC,aAAa,EAAE,aAAa,CAAC;IAC7B,0EAA0E;IAC1E,eAAe,EAAE,MAAM,CAAC;IACxB,WAAW,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;CAClE;AAED;;;;;;;;;GASG;AACH,wBAAsB,WAAW,CAC/B,GAAG,EAAE,OAAO,EACZ,KAAK,EAAE,UAAU,EACjB,eAAe,EAAE,OAAO,EACxB,IAAI,EAAE,eAAe,EACrB,YAAY,CAAC,EAAE,mBAAmB,GACjC,OAAO,CAAC,QAAQ,CAAC,CAwKnB"}
|
|
@@ -60,6 +60,12 @@ interface SsrRenderOptions {
|
|
|
60
60
|
/** Tier 2 global-error.tsx file, if present in app/. */
|
|
61
61
|
globalError?: GlobalErrorFile;
|
|
62
62
|
slotSkipInfo?: SlotSkipEntry[];
|
|
63
|
+
/**
|
|
64
|
+
* The Flight bytes of the no-JS action's form state this page answers
|
|
65
|
+
* (`encodeFormState`); absent on every other render. Fizz and hydrateRoot
|
|
66
|
+
* both decode them.
|
|
67
|
+
*/
|
|
68
|
+
formState?: Uint8Array | null;
|
|
63
69
|
}
|
|
64
70
|
/**
|
|
65
71
|
* Render the RSC stream to HTML via SSR.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ssr-renderer.d.ts","sourceRoot":"","sources":["../../../src/server/rsc-entry/ssr-renderer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAMlE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAEjD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAC;AACxE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAC/D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"ssr-renderer.d.ts","sourceRoot":"","sources":["../../../src/server/rsc-entry/ssr-renderer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAMlE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAEjD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAC;AACxE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAC/D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAOzD,OAAO,EAAmB,KAAK,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAE5E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AA8BzD;;;;;;;;GAQG;AACH,eAAO,MAAM,gCAAgC,EAAE;IAAE,KAAK,EAAE,MAAM,CAAA;CAAiB,CAAC;AAEhF;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,iCAAiC,EAAE;IAAE,KAAK,EAAE,OAAO,CAAA;CAAqB,CAAC;AAEtF,UAAU,gBAAgB;IACxB,GAAG,EAAE,OAAO,CAAC;IACb,SAAS,EAAE,cAAc,CAAC,UAAU,CAAC,CAAC;IACtC,OAAO,EAAE,aAAa,CAAC;IACvB,QAAQ,EAAE,mBAAmB,EAAE,CAAC;IAChC,gBAAgB,EAAE,oBAAoB,EAAE,CAAC;IACzC,KAAK,EAAE,UAAU,CAAC;IAClB,eAAe,EAAE,OAAO,CAAC;IACzB,eAAe,EAAE,qBAAqB,CAAC;IACvC,gBAAgB,EAAE,OAAO,CAAC;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,gBAAgB,EAAE,MAAM,CAAC;IACzB,wDAAwD;IACxD,WAAW,CAAC,EAAE,eAAe,CAAC;IAC9B,YAAY,CAAC,EAAE,aAAa,EAAE,CAAC;IAC/B;;;;OAIG;IACH,SAAS,CAAC,EAAE,UAAU,GAAG,IAAI,CAAC;CAC/B;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAsB,iBAAiB,CAAC,IAAI,EAAE,gBAAgB,GAAG,OAAO,CAAC,QAAQ,CAAC,CAgRjF"}
|
|
@@ -49,7 +49,6 @@
|
|
|
49
49
|
* See `design/02-rendering-pipeline.md` §"RSC → SSR → Client Hydration"
|
|
50
50
|
* and `CLAUDE.md` §"RSC and SSR Are Separate Vite Environments".
|
|
51
51
|
*/
|
|
52
|
-
import type { ReactFormState } from 'react-dom/client';
|
|
53
52
|
/**
|
|
54
53
|
* Navigation context passed from the RSC environment to SSR.
|
|
55
54
|
*
|
|
@@ -105,11 +104,14 @@ export interface NavContext {
|
|
|
105
104
|
cookies?: Map<string, string>;
|
|
106
105
|
/**
|
|
107
106
|
* The form state of the no-JS action this page answers (React's
|
|
108
|
-
* `decodeFormState`),
|
|
109
|
-
*
|
|
110
|
-
*
|
|
107
|
+
* `decodeFormState`), as Flight bytes. SSR decodes them for Fizz and,
|
|
108
|
+
* once they decoded, embeds the same bytes for hydration
|
|
109
|
+
* (server/ssr-form-state.ts), so the `useActionState` hook that submitted
|
|
110
|
+
* renders with the value the browser hydrates with. Absent
|
|
111
|
+
* on every other render. See design/08-forms-and-actions.md §"No-JS Result
|
|
112
|
+
* Round-Trip".
|
|
111
113
|
*/
|
|
112
|
-
formState?:
|
|
114
|
+
formState?: Uint8Array;
|
|
113
115
|
/**
|
|
114
116
|
* Lazy thunk that produces the `<script>self.__timber_segments=…</script>`
|
|
115
117
|
* tag. Invoked by the SSR entry AFTER `renderSsrNodeStream` /
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ssr-bridge-types.d.ts","sourceRoot":"","sources":["../../src/server/ssr-bridge-types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkDG;AAEH
|
|
1
|
+
{"version":3,"file":"ssr-bridge-types.d.ts","sourceRoot":"","sources":["../../src/server/ssr-bridge-types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkDG;AAEH;;;;;GAKG;AACH,MAAM,WAAW,UAAU;IACzB,6BAA6B;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB;;;;;;;;;;OAUG;IACH,MAAM,EAAE,MAAM,CAAC;IACf,qCAAqC;IACrC,UAAU,EAAE,MAAM,CAAC;IACnB,6CAA6C;IAC7C,eAAe,EAAE,OAAO,CAAC;IACzB,0DAA0D;IAC1D,QAAQ,EAAE,MAAM,CAAC;IACjB,8EAA8E;IAC9E,sBAAsB,EAAE,MAAM,CAAC;IAC/B;;;;;;;;OAQG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,qEAAqE;IACrE,SAAS,CAAC,EAAE,cAAc,CAAC,UAAU,CAAC,CAAC;IACvC;;;0DAGsD;IACtD,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B;;iFAE6E;IAC7E,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB;4DACwD;IACxD,OAAO,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC9B;;;;;;;;OAQG;IACH,SAAS,CAAC,EAAE,UAAU,CAAC;IACvB;;;;;;;;;;;;;;;;;OAiBG;IACH,oBAAoB,CAAC,EAAE,MAAM,MAAM,CAAC;IAEpC;;;;;OAKG;IACH,YAAY,CAAC,EAAE,WAAW,CAAC;CAC5B;AAED;;;;GAIG;AACH,MAAM,WAAW,UAAU;IACzB,oFAAoF;IACpF,QAAQ,EAAE,MAAM,CAAC;IACjB,6EAA6E;IAC7E,OAAO,EAAE,MAAM,CAAC;IAChB,4EAA4E;IAC5E,UAAU,EAAE,MAAM,CAAC;IACnB,+CAA+C;IAC/C,OAAO,EAAE,MAAM,CAAC;IAChB,sDAAsD;IACtD,WAAW,EAAE,OAAO,CAAC;CACtB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,MAAM,WAAW,eAAe;IAC9B,sEAAsE;IACtE,QAAQ,EAAE,QAAQ,CAAC;IACnB,OAAO,EAAE,UAAU,CAAC;IACpB;;;;;;;;;;;;;;;;OAgBG;IACH,oBAAoB,EAAE,OAAO,CAAC;CAC/B"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ssr-entry.d.ts","sourceRoot":"","sources":["../../src/server/ssr-entry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;
|
|
1
|
+
{"version":3,"file":"ssr-entry.d.ts","sourceRoot":"","sources":["../../src/server/ssr-entry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAiCH,OAAO,KAAK,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAiDzE;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAsB,SAAS,CAC7B,SAAS,EAAE,cAAc,CAAC,UAAU,CAAC,EACrC,UAAU,EAAE,UAAU,GACrB,OAAO,CAAC,eAAe,CAAC,CAuM1B;eAEc,SAAS"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The no-JS action form state on the SSR side (TIM-1572, design/08
|
|
3
|
+
* §"No-JS Result Round-Trip").
|
|
4
|
+
*
|
|
5
|
+
* SSR is the one owner of both consumers of the Flight bytes the RSC
|
|
6
|
+
* environment encoded (`NavContext.formState`): it decodes them for Fizz,
|
|
7
|
+
* and emits the script that embeds the same bytes for `hydrateRoot` only
|
|
8
|
+
* when that decode succeeded. A browser must never hydrate with a state Fizz
|
|
9
|
+
* did not render, or the hook that submitted changes state during
|
|
10
|
+
* hydration. If SSR cannot decode them, neither side gets them.
|
|
11
|
+
*/
|
|
12
|
+
import type { ReactFormState } from 'react-dom/client';
|
|
13
|
+
import { type FormStateFlightDecoder } from '../shared/form-state-flight.ts';
|
|
14
|
+
export interface SsrFormState {
|
|
15
|
+
/** For Fizz's `formState` option. */
|
|
16
|
+
formState: ReactFormState | undefined;
|
|
17
|
+
/** The `<script>` that embeds the same bytes for hydration, or `''`. */
|
|
18
|
+
script: string;
|
|
19
|
+
}
|
|
20
|
+
export declare function formStateForSsr(bytes: Uint8Array | undefined, options: {
|
|
21
|
+
/**
|
|
22
|
+
* Whether the page hydrates: its RSC payload is inlined. Without it the
|
|
23
|
+
* browser never calls `hydrateRoot`, so nothing is embedded.
|
|
24
|
+
*/
|
|
25
|
+
hydrates: boolean;
|
|
26
|
+
nonce: string | undefined;
|
|
27
|
+
decode: FormStateFlightDecoder;
|
|
28
|
+
timeoutMs: number;
|
|
29
|
+
}): Promise<SsrFormState>;
|
|
30
|
+
//# sourceMappingURL=ssr-form-state.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ssr-form-state.d.ts","sourceRoot":"","sources":["../../src/server/ssr-form-state.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AACvD,OAAO,EAAmB,KAAK,sBAAsB,EAAE,MAAM,gCAAgC,CAAC;AAI9F,MAAM,WAAW,YAAY;IAC3B,qCAAqC;IACrC,SAAS,EAAE,cAAc,GAAG,SAAS,CAAC;IACtC,wEAAwE;IACxE,MAAM,EAAE,MAAM,CAAC;CAChB;AAID,wBAAsB,eAAe,CACnC,KAAK,EAAE,UAAU,GAAG,SAAS,EAC7B,OAAO,EAAE;IACP;;;OAGG;IACH,QAAQ,EAAE,OAAO,CAAC;IAClB,KAAK,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,MAAM,EAAE,sBAAsB,CAAC;IAC/B,SAAS,EAAE,MAAM,CAAC;CACnB,GACA,OAAO,CAAC,YAAY,CAAC,CAmBvB"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The no-JS action form state on its way to the two renders that need it
|
|
3
|
+
* (TIM-1572, design/08-forms-and-actions.md §"No-JS Result Round-Trip").
|
|
4
|
+
*
|
|
5
|
+
* The RSC environment serializes the form state with Flight
|
|
6
|
+
* (server/form-state-flight.ts). SSR decodes those bytes for Fizz, and the
|
|
7
|
+
* browser decodes the same bytes, embedded in the page, for `hydrateRoot`.
|
|
8
|
+
* Both sides call `decodeFormState` below with their own environment's
|
|
9
|
+
* Flight client, so the two renders get the same value, and the value a
|
|
10
|
+
* `useActionState` result has with JS (Flight, through the action response):
|
|
11
|
+
* a `Date` stays a `Date`, a `Map` a `Map`, and an `undefined` field is
|
|
12
|
+
* absent on both, because the Flight client drops it.
|
|
13
|
+
*
|
|
14
|
+
* The page carries the bytes as base64: Flight may write binary rows (a typed
|
|
15
|
+
* array in a result), which a text chunk would corrupt, and base64 has no
|
|
16
|
+
* character that can end a `<script>` element.
|
|
17
|
+
*/
|
|
18
|
+
import type { ReactFormState } from 'react-dom/client';
|
|
19
|
+
/**
|
|
20
|
+
* A Flight client's `createFromReadableStream`, SSR's or the browser's, typed
|
|
21
|
+
* to the form state tuple it decodes. React builds `ReactFormState`
|
|
22
|
+
* (`decodeFormState` on the server) and only React reads it; the Flight
|
|
23
|
+
* client's type parameter is the only place that type is asserted.
|
|
24
|
+
*/
|
|
25
|
+
export type FormStateFlightDecoder = (stream: ReadableStream<Uint8Array>) => PromiseLike<ReactFormState>;
|
|
26
|
+
/**
|
|
27
|
+
* Decode the form state's Flight bytes. Rejects if they do not decode within
|
|
28
|
+
* `timeoutMs`. Every byte is in hand, so only a Flight client bug could
|
|
29
|
+
* stall it, and the caller falls back to rendering without form state.
|
|
30
|
+
*/
|
|
31
|
+
export declare function decodeFormState(bytes: Uint8Array, decode: FormStateFlightDecoder, timeoutMs: number): Promise<ReactFormState>;
|
|
32
|
+
/** The form state's Flight bytes as the base64 the page embeds. */
|
|
33
|
+
export declare function formStateToBase64(bytes: Uint8Array): string;
|
|
34
|
+
/** The embedded base64 back to the Flight bytes. */
|
|
35
|
+
export declare function formStateFromBase64(text: string): Uint8Array;
|
|
36
|
+
//# sourceMappingURL=form-state-flight.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"form-state-flight.d.ts","sourceRoot":"","sources":["../../src/shared/form-state-flight.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAEvD;;;;;GAKG;AACH,MAAM,MAAM,sBAAsB,GAAG,CACnC,MAAM,EAAE,cAAc,CAAC,UAAU,CAAC,KAC/B,WAAW,CAAC,cAAc,CAAC,CAAC;AAEjC;;;;GAIG;AACH,wBAAsB,eAAe,CACnC,KAAK,EAAE,UAAU,EACjB,MAAM,EAAE,sBAAsB,EAC9B,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,cAAc,CAAC,CAmBzB;AAED,mEAAmE;AACnE,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,CAI3D;AAED,oDAAoD;AACpD,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,CAK5D"}
|
|
@@ -143,6 +143,28 @@ errors.serverError; // { code, data? } | null
|
|
|
143
143
|
errors.getFieldError('title'); // string | null
|
|
144
144
|
```
|
|
145
145
|
|
|
146
|
+
### `useFormField(defaultValue)`
|
|
147
|
+
|
|
148
|
+
State for a form field that needs JavaScript, such as a combobox or a chip list, that follows the form's reset like a native input with a `defaultValue`. React resets a form after every action, so use this instead of `useState` for any field inside a form. See [Keeping What the User Typed](/docs/forms-and-actions#keeping-what-the-user-typed).
|
|
149
|
+
|
|
150
|
+
```tsx
|
|
151
|
+
import { useFormField } from '@timber-js/app/client';
|
|
152
|
+
|
|
153
|
+
// draft = toDraft(result?.submittedValues ?? post): the form's defaults
|
|
154
|
+
const [tags, setTags, ref] = useFormField(draft.tags);
|
|
155
|
+
|
|
156
|
+
<div ref={ref}>
|
|
157
|
+
<input type="hidden" name="tags" value={tags.join(',')} />
|
|
158
|
+
<TagPicker value={tags} onChange={setTags} />
|
|
159
|
+
</div>
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
- `value`: `defaultValue` until `setValue` is called, then the edit. An unedited field follows a new `defaultValue`.
|
|
163
|
+
- `setValue`: like a `useState` setter; accepts a value or an updater.
|
|
164
|
+
- `ref`: attach to any element inside the `<form>`. When the form resets, the edit is dropped and `value` is `defaultValue` again, unless the form cancels the `reset` event.
|
|
165
|
+
|
|
166
|
+
An edit dispatches a bubbling `change` event from the `ref` element, so a native `change` listener on the form sees it. React's `onChange` prop does not: it only reports inputs, selects and textareas. Render the value into the form data yourself, usually with a hidden input.
|
|
167
|
+
|
|
146
168
|
### `useFormAction(action)`
|
|
147
169
|
|
|
148
170
|
Bind a server action to a form with pending state.
|
|
@@ -153,7 +153,7 @@ limits: {
|
|
|
153
153
|
- **Type:** `boolean | string[]`
|
|
154
154
|
- **Default:** `true`
|
|
155
155
|
|
|
156
|
-
Strip sensitive fields (passwords, tokens, CVV, SSN, etc.) from the `submittedValues` echoed back
|
|
156
|
+
Strip sensitive fields (passwords, tokens, CVV, SSN, etc.) from the `submittedValues` echoed back when an action fails. `true` uses the built-in deny-list; an array adds extra field names to the built-in list; `false` disables stripping (never do this in production). See [Forms & Server Actions](/docs/forms-and-actions) for the full pattern list.
|
|
157
157
|
|
|
158
158
|
### `pageExtensions`
|
|
159
159
|
|
|
@@ -95,45 +95,132 @@ errors.hasErrors; // boolean
|
|
|
95
95
|
errors.getFieldError('title'); // string | null (first error for field)
|
|
96
96
|
```
|
|
97
97
|
|
|
98
|
-
##
|
|
98
|
+
## Keeping What the User Typed
|
|
99
99
|
|
|
100
|
-
|
|
101
|
-
| ------------------------- | ------------------------------------------------ |
|
|
102
|
-
| `redirect('/path')` | User should land somewhere new |
|
|
103
|
-
| `revalidatePath('/path')` | Current page needs fresh data |
|
|
104
|
-
| `useOptimistic` | Instant UI update, action confirms in background |
|
|
100
|
+
React resets a form after every action: each field goes back to its `defaultValue`. Build forms on that reset, and they keep the user's input after a failure and show the saved values after a save, with or without JavaScript:
|
|
105
101
|
|
|
106
|
-
|
|
102
|
+
1. Leave fields **uncontrolled**: a `name` and a `defaultValue`, not `value` + `onChange`.
|
|
103
|
+
2. Take defaults from the result, then from the page: `result?.submittedValues ?? pageData`.
|
|
104
|
+
3. On success, `redirect()` (or `revalidatePath()`), so the page renders the saved data.
|
|
107
105
|
|
|
108
|
-
|
|
106
|
+
```ts title="app/events/[id]/actions.ts"
|
|
107
|
+
'use server';
|
|
109
108
|
|
|
110
|
-
|
|
109
|
+
import { z } from 'zod/v4';
|
|
110
|
+
import { coerce, redirect } from '@timber-js/app/server';
|
|
111
|
+
import { action } from '@/lib/action';
|
|
112
|
+
|
|
113
|
+
declare const db: { events: { update(data: unknown): Promise<void> } };
|
|
114
|
+
|
|
115
|
+
export const saveEvent = action
|
|
116
|
+
.schema(
|
|
117
|
+
z.object({
|
|
118
|
+
id: z.string(),
|
|
119
|
+
title: z.string().min(1, 'Title is required'),
|
|
120
|
+
category: z.string(),
|
|
121
|
+
ticketed: z.preprocess(coerce.checkbox, z.boolean()),
|
|
122
|
+
// sets.0.name, sets.1.name… arrive as an array
|
|
123
|
+
sets: z.array(z.object({ name: z.string().min(1) })).default([]), // no rows: no key
|
|
124
|
+
})
|
|
125
|
+
)
|
|
126
|
+
.action(async ({ input }) => {
|
|
127
|
+
await db.events.update(input);
|
|
128
|
+
redirect(`/events/${input.id}`);
|
|
129
|
+
});
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
`submittedValues` is the raw form: strings, lists as arrays, an unchecked box absent. Page data is typed. One small, lenient function reads either into what the fields render:
|
|
133
|
+
|
|
134
|
+
```ts title="app/events/[id]/draft.ts"
|
|
135
|
+
export function toDraft(values: Record<string, unknown>) {
|
|
136
|
+
const sets: { name?: unknown }[] = Array.isArray(values.sets) ? values.sets : [];
|
|
137
|
+
return {
|
|
138
|
+
title: String(values.title ?? ''),
|
|
139
|
+
category: String(values.category ?? ''),
|
|
140
|
+
// Submitted: "on", or absent when unchecked. Saved: a boolean.
|
|
141
|
+
ticketed: values.ticketed === true || values.ticketed === 'on',
|
|
142
|
+
sets: sets.map((set) => ({ name: String(set?.name ?? '') })),
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
```tsx title="app/events/[id]/event-form.tsx"
|
|
111
148
|
'use client';
|
|
112
149
|
|
|
113
150
|
import { useActionState } from 'react';
|
|
114
151
|
import { parseFormErrors } from '@timber-js/app/client';
|
|
115
|
-
import {
|
|
152
|
+
import { saveEvent } from './actions';
|
|
153
|
+
import { toDraft } from './draft';
|
|
116
154
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
155
|
+
type Event = { id: string; title: string; category: string; ticketed: boolean; sets: { name: string }[] };
|
|
156
|
+
|
|
157
|
+
export function EventForm({ event }: { event: Event }) {
|
|
158
|
+
const [result, action, pending] = useActionState(saveEvent, null);
|
|
159
|
+
const errors = parseFormErrors(result);
|
|
160
|
+
// After a failure: what the user submitted. Otherwise: the saved event.
|
|
161
|
+
const d = toDraft(result?.submittedValues ?? event);
|
|
120
162
|
|
|
121
163
|
return (
|
|
122
164
|
<form action={action}>
|
|
123
|
-
<input name="
|
|
124
|
-
{
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
165
|
+
<input type="hidden" name="id" value={event.id} />
|
|
166
|
+
<input name="title" defaultValue={d.title} />
|
|
167
|
+
{errors.getFieldError('title')}
|
|
168
|
+
|
|
169
|
+
{/* React applies a select's defaultValue only on mount: key it on the default */}
|
|
170
|
+
<select key={d.category} name="category" defaultValue={d.category}>
|
|
171
|
+
<option value="meetup">Meetup</option>
|
|
172
|
+
<option value="social">Social</option>
|
|
173
|
+
</select>
|
|
174
|
+
|
|
175
|
+
<input type="checkbox" name="ticketed" defaultChecked={d.ticketed} />
|
|
176
|
+
|
|
177
|
+
{d.sets.map((set, i) => (
|
|
178
|
+
<input key={i} name={`sets.${i}.name`} defaultValue={set.name} />
|
|
179
|
+
))}
|
|
180
|
+
|
|
181
|
+
{errors.serverError && <p>Could not save. Please try again.</p>}
|
|
182
|
+
<button disabled={pending}>Save</button>
|
|
130
183
|
</form>
|
|
131
184
|
);
|
|
132
185
|
}
|
|
133
186
|
```
|
|
134
187
|
|
|
135
|
-
|
|
188
|
+
When a form submission fails — a validation error, an `ActionError`, or an unexpected error — the result carries `submittedValues`, so the user's input survives any of them.
|
|
189
|
+
|
|
190
|
+
Three things break the model:
|
|
191
|
+
|
|
192
|
+
- **Controlled fields.** A field whose value lives in state keeps showing that state after the reset has put the DOM back to its default, and the next submit sends what the DOM holds, not what the user sees. If a form must be controlled, cancel the reset: `<form action={action} onReset={(e) => e.preventDefault()}>`.
|
|
193
|
+
- **An unkeyed `<select>`.** React ignores a changed `defaultValue` on a mounted `<select>`. Key it on its default, as above. Inputs, checkboxes and textareas don't need this.
|
|
194
|
+
- **`useState` for a field that needs JavaScript.** A combobox or a chip list can't be a native input. Hold its state in `useFormField` instead, which follows the reset like a native input:
|
|
195
|
+
|
|
196
|
+
```tsx
|
|
197
|
+
import { useFormField } from '@timber-js/app/client';
|
|
198
|
+
|
|
199
|
+
function TagsField({ defaultValue }: { defaultValue: string[] }) {
|
|
200
|
+
const [tags, setTags, ref] = useFormField(defaultValue);
|
|
201
|
+
return (
|
|
202
|
+
<div ref={ref}>
|
|
203
|
+
<input type="hidden" name="tags" value={tags.join(',')} />
|
|
204
|
+
<TagPicker value={tags} onChange={setTags} />
|
|
205
|
+
</div>
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
## After Mutation
|
|
211
|
+
|
|
212
|
+
| Pattern | When to use |
|
|
213
|
+
| ------------------------- | ------------------------------------------------ |
|
|
214
|
+
| `redirect('/path')` | User should land somewhere new |
|
|
215
|
+
| `revalidatePath('/path')` | Current page needs fresh data |
|
|
216
|
+
| `useOptimistic` | Instant UI update, action confirms in background |
|
|
217
|
+
|
|
218
|
+
## Forms Without JavaScript
|
|
219
|
+
|
|
220
|
+
The same form works with JavaScript disabled, with no extra code. When a no-JS submission's action returns instead of redirecting, timber renders the page again and React hands the result to the `useActionState` hook that submitted it. `result` holds the result on both paths, so errors and the fields' defaults render the same way, and the server writes the user's input back into the HTML.
|
|
221
|
+
|
|
222
|
+
The result goes only to the hook that submitted, so two forms on one page never see each other's results. It has the same types on both paths: a `Date` your action returns is a `Date` in `state` whether or not JavaScript ran. Sensitive fields (password, apiKey, cvv, etc.) are automatically stripped from `submittedValues`.
|
|
136
223
|
|
|
137
224
|
A form that doesn't use `useActionState` gets no result back, just as with JavaScript, where React discards a plain `<form action>`'s return value. Such an action should `redirect()` to a page that shows the new state (post-redirect-get). An action that throws renders the nearest `error.tsx`: with JavaScript the action rejects to the nearest error boundary, and without it the page answers from the same `error.tsx` with a 500; return errors from `createActionClient` actions instead.
|
|
138
225
|
|
|
139
|
-
For advanced topics — coercion helpers, `parseFormData`,
|
|
226
|
+
For advanced topics — coercion helpers, `parseFormData`, indexed lists, and sensitive field stripping configuration — see [Advanced Forms](/docs/advanced-forms).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@timber-js/app",
|
|
3
|
-
"version": "0.2.0-alpha.
|
|
3
|
+
"version": "0.2.0-alpha.214",
|
|
4
4
|
"description": "Vite-native React framework built for Servers and Serverless Platforms — correct HTTP semantics, real status codes, pages that work without JavaScript",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cloudflare-workers",
|
|
@@ -326,7 +326,16 @@ const server = createServer(async (req, res) => {
|
|
|
326
326
|
// connection it would be attacker-controlled. The CSRF layer honors it
|
|
327
327
|
// separately for reverse-proxy deployments.
|
|
328
328
|
const scheme = req.socket?.encrypted ? 'https' : 'http';
|
|
329
|
-
|
|
329
|
+
// An origin-form target ('/path') is concatenated, not resolved against
|
|
330
|
+
// the base: resolved as a relative reference, '//evil.com/' names another
|
|
331
|
+
// host, and the pipeline would see '/' instead of the path it must
|
|
332
|
+
// redirect to '/evil.com' (TIM-1571). Any other form — 'OPTIONS *', an
|
|
333
|
+
// absolute-form target — is resolved as before: concatenating '*' makes
|
|
334
|
+
// an invalid URL, and this callback has no catch around it, so the
|
|
335
|
+
// rejection would take the preview process down.
|
|
336
|
+
const origin = \`\${scheme}://\${host}:\${boundPort}\`;
|
|
337
|
+
const target = req.url || '/';
|
|
338
|
+
const url = target.startsWith('/') ? new URL(origin + target) : new URL(target, origin);
|
|
330
339
|
|
|
331
340
|
// The runtime's own request-boundary canonicalizer (_canonicalize.mjs,
|
|
332
341
|
// compiled from server/canonicalize.ts): one decode, and the same 400s for
|
|
@@ -23,7 +23,7 @@ import { getClientDeploymentId, DEPLOYMENT_ID_HEADER, RELOAD_HEADER } from '../r
|
|
|
23
23
|
import { markClientStale } from '../stale-client.ts';
|
|
24
24
|
import { RSC_CONTENT_TYPE } from '../../shared/rsc-media-type.ts';
|
|
25
25
|
import { CSRF_REJECT_HEADER, CSRF_REJECT_VALUE } from '../../shared/csrf-reject.ts';
|
|
26
|
-
import { createActionQueue } from './action-queue.ts';
|
|
26
|
+
import { createActionQueue, DEFAULT_HOLD_TIMEOUT_MS } from './action-queue.ts';
|
|
27
27
|
|
|
28
28
|
export function setupServerActions(): void {
|
|
29
29
|
const queue = createActionQueue({
|
|
@@ -46,10 +46,10 @@ export function setupServerActions(): void {
|
|
|
46
46
|
},
|
|
47
47
|
});
|
|
48
48
|
|
|
49
|
-
//
|
|
50
|
-
//
|
|
51
|
-
//
|
|
52
|
-
//
|
|
49
|
+
// The page that submitted a redirecting action stays mounted until the
|
|
50
|
+
// destination commits. Its own useActionState stays pending until the
|
|
51
|
+
// hand-off (below), but other forms and effects on it can still dispatch,
|
|
52
|
+
// and a second submit would run the mutation again (TIM-1568). So the
|
|
53
53
|
// departing page may not dispatch: `departing` is set from the redirect
|
|
54
54
|
// response until the router is next idle, and a call made
|
|
55
55
|
// meanwhile is never sent. It resolves at once with `undefined` — what the
|
|
@@ -163,10 +163,26 @@ export function setupServerActions(): void {
|
|
|
163
163
|
}
|
|
164
164
|
|
|
165
165
|
// Redirects apply unconditionally — the server mutated and told
|
|
166
|
-
// the client where to go.
|
|
167
|
-
//
|
|
168
|
-
//
|
|
169
|
-
//
|
|
166
|
+
// the client where to go. The action settles (returning undefined)
|
|
167
|
+
// once the destination's tree has been handed to React, not before
|
|
168
|
+
// and not after:
|
|
169
|
+
//
|
|
170
|
+
// - Not after its commit: React entangles the commit with the action
|
|
171
|
+
// scope, so the commit would wait on the action while the action
|
|
172
|
+
// waited on the commit.
|
|
173
|
+
// - Not before the hand-off: React would commit the action alone —
|
|
174
|
+
// useActionState's result, and React's reset of the submitting form
|
|
175
|
+
// onto the defaults the *departing* page renders — and the form
|
|
176
|
+
// would show the pre-save values until the destination committed
|
|
177
|
+
// (TIM-1573).
|
|
178
|
+
//
|
|
179
|
+
// At the hand-off the destination's render is already entangled with
|
|
180
|
+
// the action, so settling commits both together: the form resets onto
|
|
181
|
+
// the destination's defaults. The wait always ends: the navigation
|
|
182
|
+
// hands off, or fails, or a navigation the user starts supersedes it
|
|
183
|
+
// (its promise settles either way), or DEFAULT_HOLD_TIMEOUT_MS
|
|
184
|
+
// passes. Every transition waits while an action is pending, so this
|
|
185
|
+
// must not hang on a stalled fetch.
|
|
170
186
|
//
|
|
171
187
|
// Set before returning, so React never sees the action settle while
|
|
172
188
|
// the departing page can still dispatch (see `departing`).
|
|
@@ -190,7 +206,9 @@ export function setupServerActions(): void {
|
|
|
190
206
|
// still suspended — can commit afterwards, and must not release
|
|
191
207
|
// the latch: it is the departing page again (codex on #1211).
|
|
192
208
|
const redirectSeq = r.epoch().seq + 1;
|
|
193
|
-
|
|
209
|
+
let handOff!: () => void;
|
|
210
|
+
const handedOff = new Promise<void>((resolve) => (handOff = resolve));
|
|
211
|
+
r.navigate(wrapper._redirect, { onHandOff: handOff }).then(handOff, handOff);
|
|
194
212
|
// Released once the departing page can no longer be on screen:
|
|
195
213
|
// when any navigation's tree commits (a streamed destination
|
|
196
214
|
// commits its shell before its stream finishes, and is live from
|
|
@@ -213,6 +231,12 @@ export function setupServerActions(): void {
|
|
|
213
231
|
// A navigation that failed before taking the router never makes
|
|
214
232
|
// it busy, so no idle transition would come.
|
|
215
233
|
if (r.epoch().idle) arrive();
|
|
234
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
235
|
+
await Promise.race([
|
|
236
|
+
handedOff,
|
|
237
|
+
new Promise<void>((resolve) => (timer = setTimeout(resolve, DEFAULT_HOLD_TIMEOUT_MS))),
|
|
238
|
+
]);
|
|
239
|
+
clearTimeout(timer);
|
|
216
240
|
} catch (e) {
|
|
217
241
|
console.debug(
|
|
218
242
|
'[timber] action redirect: router not ready, using full navigation',
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The form state a page answering a no-JS action embeds for hydration
|
|
3
|
+
* (`self.__timber_form_state`, server/form-state-flight.ts).
|
|
4
|
+
*
|
|
5
|
+
* `hydrateRoot` needs the value Fizz rendered with, or the `useActionState`
|
|
6
|
+
* hook that submitted hydrates back to its initial state. The embed is the
|
|
7
|
+
* form state's Flight bytes, the same bytes SSR decoded for Fizz, so the
|
|
8
|
+
* browser decodes them with its own Flight client before it hydrates. Only
|
|
9
|
+
* this page waits for that; a page without the embed hydrates at once
|
|
10
|
+
* (TIM-1297: hydration does not wait for the payload root).
|
|
11
|
+
*
|
|
12
|
+
* See design/08-forms-and-actions.md §"No-JS Result Round-Trip".
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import type { ReactFormState } from 'react-dom/client';
|
|
16
|
+
import { createFromReadableStream } from '../../rsc-runtime/browser.ts';
|
|
17
|
+
import { decodeFormState, formStateFromBase64 } from '../../shared/form-state-flight.ts';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Every byte is already in the document, so decoding takes microtasks. The
|
|
21
|
+
* bound only keeps a Flight client bug from holding hydration forever.
|
|
22
|
+
*/
|
|
23
|
+
const DECODE_TIMEOUT_MS = 5_000;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Take the embedded form state, and remove it. `null` when the page has
|
|
27
|
+
* none, which is every page but one answering a no-JS action. Otherwise a
|
|
28
|
+
* promise of the decoded state, or of `null` if it could not be decoded: the
|
|
29
|
+
* page then hydrates as though it had none, which resets that one hook.
|
|
30
|
+
*/
|
|
31
|
+
export function takeEmbeddedFormState(): Promise<ReactFormState | null> | null {
|
|
32
|
+
const embedded: unknown = Reflect.get(self, '__timber_form_state');
|
|
33
|
+
Reflect.deleteProperty(self, '__timber_form_state');
|
|
34
|
+
if (typeof embedded !== 'string') return null;
|
|
35
|
+
// Inside the chain, so malformed base64 falls back like a failed decode.
|
|
36
|
+
return Promise.resolve(embedded)
|
|
37
|
+
.then((text) =>
|
|
38
|
+
decodeFormState(
|
|
39
|
+
formStateFromBase64(text),
|
|
40
|
+
(stream) => createFromReadableStream(stream),
|
|
41
|
+
DECODE_TIMEOUT_MS
|
|
42
|
+
)
|
|
43
|
+
)
|
|
44
|
+
.catch((error: unknown) => {
|
|
45
|
+
console.warn('[timber] the form state of a no-JS action did not decode:', error);
|
|
46
|
+
return null;
|
|
47
|
+
});
|
|
48
|
+
}
|