@timber-js/app 0.2.0-alpha.212 → 0.2.0-alpha.213
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-CEootpB1.js} +42 -7
- package/dist/_chunks/{actions-CCdnVtWm.js.map → actions-CEootpB1.js.map} +1 -1
- package/dist/_chunks/{error-boundary-DsNScGRM.js → error-boundary-9g_Lb2na.js} +3 -3
- package/dist/_chunks/{error-boundary-DsNScGRM.js.map → error-boundary-9g_Lb2na.js.map} +1 -1
- 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 +8 -7
- 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 +7 -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 +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.map +1 -1
- package/dist/server/action-client.d.ts +11 -2
- package/dist/server/action-client.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 +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/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 +8 -4
- package/src/client/router-pipeline.ts +7 -3
- package/src/client/router-types.ts +12 -1
- package/src/client/router.ts +16 -3
- 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/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/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
|
@@ -850,9 +850,15 @@ function waitUntil(promise) {
|
|
|
850
850
|
* Handles:
|
|
851
851
|
* - **Duplicate keys → arrays**: `tags=js&tags=ts` → `{ tags: ["js", "ts"] }`
|
|
852
852
|
* - **Nested dot-paths**: `user.name=Alice` → `{ user: { name: "Alice" } }`
|
|
853
|
-
* - **
|
|
853
|
+
* - **Indexed lists → arrays**: `rows.0.name=A&rows.1.name=B` →
|
|
854
|
+
* `{ rows: [{ name: "A" }, { name: "B" }] }`, when the indexes are exactly
|
|
855
|
+
* `0..n-1`. A list with a gap stays an object keyed by index.
|
|
856
|
+
* - **Empty strings stay `""`**: a blank text input is `""`, so
|
|
857
|
+
* `z.string().min(1, 'Required')` reports its own message. Use
|
|
858
|
+
* `coerce.text` to make a blank optional field `undefined`
|
|
854
859
|
* - **Empty Files → undefined**: File inputs with no selection become `undefined`
|
|
855
860
|
* - **Strips `$ACTION_*` fields**: React's internal hidden fields are excluded
|
|
861
|
+
* - **Drops `__proto__` paths and names deeper than 32 segments**
|
|
856
862
|
*/
|
|
857
863
|
function parseFormData(formData) {
|
|
858
864
|
const flat = {};
|
|
@@ -895,6 +901,7 @@ function expandDotPaths(flat) {
|
|
|
895
901
|
for (const [key, value] of Object.entries(flat)) {
|
|
896
902
|
const parts = key.split(".");
|
|
897
903
|
if (parts.some((p) => DANGEROUS_KEYS.has(p))) continue;
|
|
904
|
+
if (parts.length > MAX_PATH_DEPTH) continue;
|
|
898
905
|
if (parts.length === 1) {
|
|
899
906
|
result[parts[0]] = value;
|
|
900
907
|
continue;
|
|
@@ -902,20 +909,47 @@ function expandDotPaths(flat) {
|
|
|
902
909
|
let current = result;
|
|
903
910
|
for (let i = 0; i < parts.length - 1; i++) {
|
|
904
911
|
const part = parts[i];
|
|
905
|
-
if (current[part]
|
|
906
|
-
if (typeof current[part] !== "object" || current[part] instanceof File) current[part] = {};
|
|
912
|
+
if (!isPlainObject(current[part])) current[part] = {};
|
|
907
913
|
current = current[part];
|
|
908
914
|
}
|
|
909
915
|
current[parts[parts.length - 1]] = value;
|
|
910
916
|
}
|
|
917
|
+
for (const [key, value] of Object.entries(result)) if (isPlainObject(value)) result[key] = indexedListsToArrays(value);
|
|
911
918
|
return result;
|
|
912
919
|
}
|
|
920
|
+
/**
|
|
921
|
+
* `node`, with every nested object whose keys are exactly `"0".."n-1"`
|
|
922
|
+
* turned into an array, depth first. Only a dense, canonical index set
|
|
923
|
+
* converts: `{ "0", "2" }` (a gap), `{ "01" }` and `{ "0", "name" }` stay
|
|
924
|
+
* objects. So a forged `rows.99999` stays one key instead of allocating a
|
|
925
|
+
* sparse array, and the array's length is bounded by the field count limit.
|
|
926
|
+
*
|
|
927
|
+
* Object.keys lists integer-like keys first, in ascending order, so a dense
|
|
928
|
+
* set reads as `0, 1, … n-1` and `Object.values` is already in index order.
|
|
929
|
+
*/
|
|
930
|
+
function indexedListsToArrays(node) {
|
|
931
|
+
for (const [key, value] of Object.entries(node)) if (isPlainObject(value)) node[key] = indexedListsToArrays(value);
|
|
932
|
+
const keys = Object.keys(node);
|
|
933
|
+
return keys.length > 0 && keys.every((key, i) => key === String(i)) ? Object.values(node) : node;
|
|
934
|
+
}
|
|
935
|
+
/** An object the dot-path walk built — not an array, File or other instance. */
|
|
936
|
+
function isPlainObject(value) {
|
|
937
|
+
return typeof value === "object" && value !== null && Object.getPrototypeOf(value) === Object.prototype;
|
|
938
|
+
}
|
|
913
939
|
var DANGEROUS_KEYS = /* @__PURE__ */ new Set(["__proto__"]);
|
|
914
940
|
/**
|
|
941
|
+
* The most segments a dot-path field name may have. A deeper name is dropped,
|
|
942
|
+
* like a `__proto__` path, so no walk over the parsed value can exhaust the
|
|
943
|
+
* stack (TIM-1573).
|
|
944
|
+
*/
|
|
945
|
+
var MAX_PATH_DEPTH = 32;
|
|
946
|
+
/**
|
|
915
947
|
* Schema-agnostic coercion primitives for common FormData patterns.
|
|
916
948
|
*
|
|
917
949
|
* These are plain transform functions — they compose with any schema library's
|
|
918
|
-
* `transform`/`preprocess` pipeline
|
|
950
|
+
* `transform`/`preprocess` pipeline. In Zod, use `z.preprocess`: it runs on an
|
|
951
|
+
* absent key (an unchecked checkbox), where `z.unknown().transform()` fails
|
|
952
|
+
* with "expected nonoptional" before the transform runs.
|
|
919
953
|
*
|
|
920
954
|
* ```ts
|
|
921
955
|
* // Zod
|
|
@@ -930,8 +964,9 @@ var coerce = {
|
|
|
930
964
|
* Use with `.optional()` schemas where an empty input means "not provided".
|
|
931
965
|
*
|
|
932
966
|
* ```ts
|
|
933
|
-
* // Zod
|
|
934
|
-
*
|
|
967
|
+
* // Zod — preprocess, not `z.unknown().transform(…)`: Zod 4 rejects an
|
|
968
|
+
* // absent key before a transform runs ("expected nonoptional")
|
|
969
|
+
* z.preprocess(coerce.text, z.string().optional())
|
|
935
970
|
* // Valibot
|
|
936
971
|
* v.pipe(v.unknown(), v.transform(coerce.text), v.optional(v.string()))
|
|
937
972
|
* ```
|
|
@@ -1198,4 +1233,4 @@ function revalidationPathMatches(revalidatedPath, requestPathname, requestSearch
|
|
|
1198
1233
|
//#endregion
|
|
1199
1234
|
export { setMutableCookieContext as C, setMatchedSegmentPath as S, runOutsideReactCacheScope as T, getHeaders as _, parseFormData as a, markResponseFlushed as b, deny as c, waitUntil as d, getCookie as f, getHeader as g, applyRequestHeaderOverlay as h, coerce as i, redirect as l, getSetCookieHeaders as m, revalidatePath as n, DenySignal as o, getCookieJar as p, revalidateTag as r, RedirectSignal as s, executeAction as t, redirectExternal as u, getSearchParams as v, setSegmentParams as w, runWithRequestContext as x, getSegmentParams as y };
|
|
1200
1235
|
|
|
1201
|
-
//# sourceMappingURL=actions-
|
|
1236
|
+
//# sourceMappingURL=actions-CEootpB1.js.map
|