@timber-js/app 0.2.0-alpha.80 → 0.2.0-alpha.82

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.
Files changed (39) hide show
  1. package/dist/_chunks/{stale-reload-C2plcNtG.js → stale-reload-BX5gL1r-.js} +1 -1
  2. package/dist/_chunks/{stale-reload-C2plcNtG.js.map → stale-reload-BX5gL1r-.js.map} +1 -1
  3. package/dist/cli.d.ts.map +1 -1
  4. package/dist/cli.js +2 -2
  5. package/dist/cli.js.map +1 -1
  6. package/dist/client/form.d.ts +2 -2
  7. package/dist/client/form.d.ts.map +1 -1
  8. package/dist/client/index.js.map +1 -1
  9. package/dist/client/internal.js +1 -1
  10. package/dist/config-types.d.ts +17 -0
  11. package/dist/config-types.d.ts.map +1 -1
  12. package/dist/index.d.ts.map +1 -1
  13. package/dist/index.js +40 -3
  14. package/dist/index.js.map +1 -1
  15. package/dist/plugin-context.d.ts +23 -0
  16. package/dist/plugin-context.d.ts.map +1 -1
  17. package/dist/search-params/index.js +62 -1
  18. package/dist/search-params/index.js.map +1 -0
  19. package/dist/segment-params/index.d.ts +0 -3
  20. package/dist/segment-params/index.d.ts.map +1 -1
  21. package/dist/segment-params/index.js +1 -3
  22. package/dist/server/action-client.d.ts +14 -6
  23. package/dist/server/action-client.d.ts.map +1 -1
  24. package/dist/server/index.d.ts +0 -1
  25. package/dist/server/index.d.ts.map +1 -1
  26. package/dist/server/index.js +1 -2
  27. package/dist/server/index.js.map +1 -1
  28. package/package.json +5 -1
  29. package/src/cli.ts +10 -5
  30. package/src/client/form.tsx +8 -6
  31. package/src/config-types.ts +17 -0
  32. package/src/index.ts +37 -0
  33. package/src/plugin-context.ts +40 -0
  34. package/src/plugins/adapter-build.ts +1 -1
  35. package/src/segment-params/index.ts +3 -23
  36. package/src/server/action-client.ts +26 -10
  37. package/src/server/index.ts +2 -3
  38. package/dist/_chunks/wrappers-_DTmImGt.js +0 -63
  39. package/dist/_chunks/wrappers-_DTmImGt.js.map +0 -1
@@ -49,6 +49,16 @@ export interface PluginContext {
49
49
  timer: StartupTimer;
50
50
  /** Holding server that binds the port during dev startup (closed in timber-dev-server) */
51
51
  holdingServer?: HoldingServer | null;
52
+ /**
53
+ * Resolved absolute path to the build output directory.
54
+ *
55
+ * Defaults to `<root>/.timber/dist`. Can be overridden via
56
+ * `timber.config.ts` `buildDir` or Vite's `build.outDir`.
57
+ * Timber config takes precedence when both are set.
58
+ *
59
+ * Used by adapter-build and cli preview.
60
+ */
61
+ buildDir: string;
52
62
  }
53
63
  /**
54
64
  * Resolve the app directory. Checks (in order):
@@ -60,6 +70,19 @@ export interface PluginContext {
60
70
  */
61
71
  export declare function resolveAppDir(root: string, configAppDir?: string): string;
62
72
  export declare function createPluginContext(config?: TimberUserConfig, root?: string): PluginContext;
73
+ /** Default build output directory (relative to root). */
74
+ export declare const DEFAULT_BUILD_DIR: string;
75
+ /**
76
+ * Resolve the build output directory.
77
+ *
78
+ * Priority:
79
+ * 1. Explicit `timberBuildDir` from timber.config.ts
80
+ * 2. Explicit `viteBuildOutDir` from Vite's build.outDir (if not the default 'dist')
81
+ * 3. Default: `.timber/dist`
82
+ *
83
+ * Returns an absolute path.
84
+ */
85
+ export declare function resolveBuildDir(root: string, timberBuildDir?: string, viteBuildOutDir?: string): string;
63
86
  /**
64
87
  * Load timber.config.ts (or .js, .mjs) from the project root.
65
88
  * Returns the config object or null if no config file is found.
@@ -1 +1 @@
1
- {"version":3,"file":"plugin-context.d.ts","sourceRoot":"","sources":["../src/plugin-context.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAKH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAC7D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAE1D,OAAO,KAAK,EAAE,gBAAgB,EAA0B,MAAM,mBAAmB,CAAC;AAClF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gCAAgC,CAAC;AAGpE,YAAY,EAAE,gBAAgB,EAAE,sBAAsB,EAAE,MAAM,mBAAmB,CAAC;AAIlF,qEAAqE;AACrE,MAAM,WAAW,wBAAwB;IACvC,QAAQ,EAAE,OAAO,CAAC;IAClB,cAAc,EAAE,OAAO,CAAC;CACzB;AAED;;GAEG;AACH,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,gBAAgB,GAAG,wBAAwB,CAc1F;AAID;;;;;;GAMG;AACH,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,gBAAgB,CAAC;IACzB,+CAA+C;IAC/C,gBAAgB,EAAE,wBAAwB,CAAC;IAC3C,uFAAuF;IACvF,SAAS,EAAE,SAAS,GAAG,IAAI,CAAC;IAC5B,0CAA0C;IAC1C,MAAM,EAAE,MAAM,CAAC;IACf,wCAAwC;IACxC,IAAI,EAAE,MAAM,CAAC;IACb,oFAAoF;IACpF,GAAG,EAAE,OAAO,CAAC;IACb,gFAAgF;IAChF,aAAa,EAAE,aAAa,GAAG,IAAI,CAAC;IACpC,uEAAuE;IACvE,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,mFAAmF;IACnF,KAAK,EAAE,YAAY,CAAC;IACpB,0FAA0F;IAC1F,aAAa,CAAC,EAAE,aAAa,GAAG,IAAI,CAAC;CACtC;AAID;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,MAAM,GAAG,MAAM,CAmBzE;AAID,wBAAgB,mBAAmB,CAAC,MAAM,CAAC,EAAE,gBAAgB,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,aAAa,CAe3F;AAID;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,gBAAgB,GAAG,IAAI,CAY1E;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,gBAAgB,EACxB,UAAU,EAAE,gBAAgB,GAC3B,MAAM,EAAE,CAgBV;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,aAAa,EAAE,UAAU,EAAE,gBAAgB,GAAG,IAAI,CAatF"}
1
+ {"version":3,"file":"plugin-context.d.ts","sourceRoot":"","sources":["../src/plugin-context.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAKH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAC7D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAE1D,OAAO,KAAK,EAAE,gBAAgB,EAA0B,MAAM,mBAAmB,CAAC;AAClF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gCAAgC,CAAC;AAGpE,YAAY,EAAE,gBAAgB,EAAE,sBAAsB,EAAE,MAAM,mBAAmB,CAAC;AAIlF,qEAAqE;AACrE,MAAM,WAAW,wBAAwB;IACvC,QAAQ,EAAE,OAAO,CAAC;IAClB,cAAc,EAAE,OAAO,CAAC;CACzB;AAED;;GAEG;AACH,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,gBAAgB,GAAG,wBAAwB,CAc1F;AAID;;;;;;GAMG;AACH,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,gBAAgB,CAAC;IACzB,+CAA+C;IAC/C,gBAAgB,EAAE,wBAAwB,CAAC;IAC3C,uFAAuF;IACvF,SAAS,EAAE,SAAS,GAAG,IAAI,CAAC;IAC5B,0CAA0C;IAC1C,MAAM,EAAE,MAAM,CAAC;IACf,wCAAwC;IACxC,IAAI,EAAE,MAAM,CAAC;IACb,oFAAoF;IACpF,GAAG,EAAE,OAAO,CAAC;IACb,gFAAgF;IAChF,aAAa,EAAE,aAAa,GAAG,IAAI,CAAC;IACpC,uEAAuE;IACvE,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,mFAAmF;IACnF,KAAK,EAAE,YAAY,CAAC;IACpB,0FAA0F;IAC1F,aAAa,CAAC,EAAE,aAAa,GAAG,IAAI,CAAC;IACrC;;;;;;;;OAQG;IACH,QAAQ,EAAE,MAAM,CAAC;CAClB;AAID;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,MAAM,GAAG,MAAM,CAmBzE;AAID,wBAAgB,mBAAmB,CAAC,MAAM,CAAC,EAAE,gBAAgB,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,aAAa,CAgB3F;AAID,yDAAyD;AACzD,eAAO,MAAM,iBAAiB,QAA0B,CAAC;AAEzD;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAC7B,IAAI,EAAE,MAAM,EACZ,cAAc,CAAC,EAAE,MAAM,EACvB,eAAe,CAAC,EAAE,MAAM,GACvB,MAAM,CAQR;AAID;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,gBAAgB,GAAG,IAAI,CAY1E;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,gBAAgB,EACxB,UAAU,EAAE,gBAAgB,GAC3B,MAAM,EAAE,CAgBV;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,aAAa,EAAE,UAAU,EAAE,gBAAgB,GAAG,IAAI,CAatF"}
@@ -1,4 +1,65 @@
1
1
  import { i as registerSearchParams, r as getSearchParamsDefinition } from "../_chunks/use-query-states-Lo_s_pw2.js";
2
2
  import { n as defineSearchParams } from "../_chunks/define-CZqDwhSu.js";
3
- import { n as withUrlKey, t as withDefault } from "../_chunks/wrappers-_DTmImGt.js";
3
+ //#region src/search-params/wrappers.ts
4
+ /**
5
+ * Wrap a nullable codec with a default value. When the inner codec returns
6
+ * null, the default is used instead. The output type becomes non-nullable.
7
+ *
8
+ * Works with any codec — nuqs parsers, custom codecs, fromSchema results.
9
+ *
10
+ * ```ts
11
+ * import { parseAsInteger } from 'nuqs'
12
+ * import { withDefault } from '@timber-js/app/search-params'
13
+ *
14
+ * const page = withDefault(parseAsInteger, 1)
15
+ * // page.parse(undefined) → 1 (not null)
16
+ * // page.parse('5') → 5
17
+ * ```
18
+ */
19
+ function withDefault(codec, defaultValue) {
20
+ return {
21
+ parse(value) {
22
+ const result = codec.parse(value);
23
+ return result === null ? defaultValue : result;
24
+ },
25
+ serialize(value) {
26
+ return codec.serialize(value);
27
+ }
28
+ };
29
+ }
30
+ /**
31
+ * Attach a URL key alias to a codec. The alias determines what query
32
+ * parameter key is used in the URL, while the TypeScript property name
33
+ * stays descriptive.
34
+ *
35
+ * Aliases travel with codecs through object spread composition — when
36
+ * you spread a bundle containing aliased codecs into defineSearchParams,
37
+ * the aliases come along automatically.
38
+ *
39
+ * ```ts
40
+ * import { parseAsString } from 'nuqs'
41
+ * import { withUrlKey } from '@timber-js/app/search-params'
42
+ *
43
+ * export const searchable = {
44
+ * q: withUrlKey(parseAsString, 'search'),
45
+ * // ?search=shoes → { q: 'shoes' }
46
+ * }
47
+ * ```
48
+ *
49
+ * Composes with withDefault:
50
+ * ```ts
51
+ * import { parseAsInteger } from 'nuqs'
52
+ * withUrlKey(withDefault(parseAsInteger, 1), 'p')
53
+ * ```
54
+ */
55
+ function withUrlKey(codec, urlKey) {
56
+ return {
57
+ parse: codec.parse.bind(codec),
58
+ serialize: codec.serialize.bind(codec),
59
+ urlKey
60
+ };
61
+ }
62
+ //#endregion
4
63
  export { defineSearchParams, getSearchParamsDefinition, registerSearchParams, withDefault, withUrlKey };
64
+
65
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../src/search-params/wrappers.ts"],"sourcesContent":["/**\n * Codec wrappers — withDefault and withUrlKey.\n *\n * These are timber-specific utilities that work with any SearchParamCodec.\n * For actual codecs (string, integer, boolean, etc.), use nuqs parsers\n * or Standard Schema objects (Zod, Valibot, ArkType) with auto-detection.\n *\n * Design doc: design/23-search-params.md\n */\n\nimport type { SearchParamCodec, SearchParamCodecWithUrlKey } from './define.js';\n\n// ---------------------------------------------------------------------------\n// withDefault\n// ---------------------------------------------------------------------------\n\n/**\n * Wrap a nullable codec with a default value. When the inner codec returns\n * null, the default is used instead. The output type becomes non-nullable.\n *\n * Works with any codec — nuqs parsers, custom codecs, fromSchema results.\n *\n * ```ts\n * import { parseAsInteger } from 'nuqs'\n * import { withDefault } from '@timber-js/app/search-params'\n *\n * const page = withDefault(parseAsInteger, 1)\n * // page.parse(undefined) → 1 (not null)\n * // page.parse('5') → 5\n * ```\n */\nexport function withDefault<T>(\n codec: SearchParamCodec<T | null>,\n defaultValue: T\n): SearchParamCodec<T> {\n return {\n parse(value: string | string[] | undefined): T {\n const result = codec.parse(value);\n return result === null ? defaultValue : result;\n },\n serialize(value: T): string | null {\n return codec.serialize(value);\n },\n };\n}\n\n// ---------------------------------------------------------------------------\n// withUrlKey\n// ---------------------------------------------------------------------------\n\n/**\n * Attach a URL key alias to a codec. The alias determines what query\n * parameter key is used in the URL, while the TypeScript property name\n * stays descriptive.\n *\n * Aliases travel with codecs through object spread composition — when\n * you spread a bundle containing aliased codecs into defineSearchParams,\n * the aliases come along automatically.\n *\n * ```ts\n * import { parseAsString } from 'nuqs'\n * import { withUrlKey } from '@timber-js/app/search-params'\n *\n * export const searchable = {\n * q: withUrlKey(parseAsString, 'search'),\n * // ?search=shoes → { q: 'shoes' }\n * }\n * ```\n *\n * Composes with withDefault:\n * ```ts\n * import { parseAsInteger } from 'nuqs'\n * withUrlKey(withDefault(parseAsInteger, 1), 'p')\n * ```\n */\nexport function withUrlKey<T>(\n codec: SearchParamCodec<T>,\n urlKey: string\n): SearchParamCodecWithUrlKey<T> {\n return {\n parse: codec.parse.bind(codec),\n serialize: codec.serialize.bind(codec),\n urlKey,\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AA+BA,SAAgB,YACd,OACA,cACqB;AACrB,QAAO;EACL,MAAM,OAAyC;GAC7C,MAAM,SAAS,MAAM,MAAM,MAAM;AACjC,UAAO,WAAW,OAAO,eAAe;;EAE1C,UAAU,OAAyB;AACjC,UAAO,MAAM,UAAU,MAAM;;EAEhC;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgCH,SAAgB,WACd,OACA,QAC+B;AAC/B,QAAO;EACL,OAAO,MAAM,MAAM,KAAK,MAAM;EAC9B,WAAW,MAAM,UAAU,KAAK,MAAM;EACtC;EACD"}
@@ -1,6 +1,3 @@
1
1
  export type { ParamsDefinition, InferParamField, ParamField } from './define.js';
2
2
  export { defineSegmentParams } from './define.js';
3
- export { defineSearchParams } from '../search-params/define.js';
4
- export { withDefault, withUrlKey } from '../search-params/wrappers.js';
5
- export type { SearchParamCodec, SearchParamsDefinition, SetParams, SetParamsOptions, QueryStatesOptions, CodecMap, } from '../search-params/define.js';
6
3
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/segment-params/index.ts"],"names":[],"mappings":"AAQA,YAAY,EAAE,gBAAgB,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACjF,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAKlD,OAAO,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AAIhE,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,8BAA8B,CAAC;AAGvE,YAAY,EACV,gBAAgB,EAChB,sBAAsB,EACtB,SAAS,EACT,gBAAgB,EAChB,kBAAkB,EAClB,QAAQ,GACT,MAAM,4BAA4B,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/segment-params/index.ts"],"names":[],"mappings":"AAOA,YAAY,EAAE,gBAAgB,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACjF,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC"}
@@ -1,4 +1,2 @@
1
- import { n as defineSearchParams } from "../_chunks/define-CZqDwhSu.js";
2
1
  import { n as defineSegmentParams } from "../_chunks/define-C77ScO0m.js";
3
- import { n as withUrlKey, t as withDefault } from "../_chunks/wrappers-_DTmImGt.js";
4
- export { defineSearchParams, defineSegmentParams, withDefault, withUrlKey };
2
+ export { defineSegmentParams };
@@ -122,12 +122,12 @@ export interface ActionBuilder<TCtx> {
122
122
  /** Declare the input schema. Validation errors are returned typed. */
123
123
  schema<TInput>(schema: ActionSchema<TInput>): ActionBuilderWithSchema<TCtx, TInput>;
124
124
  /** Define the action body without input validation. */
125
- action<TData>(fn: (ctx: ActionContext<TCtx, undefined>) => Promise<TData>): ActionFn<TData>;
125
+ action<TData>(fn: (ctx: ActionContext<TCtx, undefined>) => Promise<TData>): ActionFn<undefined, TData>;
126
126
  }
127
127
  /** Builder after .schema() has been called. */
128
128
  export interface ActionBuilderWithSchema<TCtx, TInput> {
129
129
  /** Define the action body with validated input. */
130
- action<TData>(fn: (ctx: ActionContext<TCtx, TInput>) => Promise<TData>): ActionFn<TData>;
130
+ action<TData>(fn: (ctx: ActionContext<TCtx, TInput>) => Promise<TData>): ActionFn<TInput, TData>;
131
131
  }
132
132
  /**
133
133
  * The final action function. Callable three ways:
@@ -141,11 +141,19 @@ export interface ActionBuilderWithSchema<TCtx, TInput> {
141
141
  * discards it. This lets validated actions be passed directly to forms
142
142
  * without casts.
143
143
  */
144
- export type ActionFn<TData> = {
144
+ /**
145
+ * Map schema output keys to `string | undefined` for form-facing APIs.
146
+ * HTML form values are always strings, and fields can be absent.
147
+ * Gives autocomplete for field names without lying about value types.
148
+ */
149
+ export type InputHint<T> = T extends Record<string, unknown> ? {
150
+ [K in keyof T]: string | undefined;
151
+ } : T;
152
+ export type ActionFn<TInput = unknown, TData = unknown> = {
145
153
  /** <form action={fn}> compatibility — React discards the return value. */
146
154
  (formData: FormData): void;
147
- /** Direct call: action(input) */
148
- (input?: unknown): Promise<ActionResult<TData>>;
155
+ /** Direct call: action(input) — optional when TInput is undefined (no-schema actions). */
156
+ (...args: undefined extends TInput ? [input?: TInput] : [input: TInput]): Promise<ActionResult<TData>>;
149
157
  /** React useActionState: action(prevState, formData) */
150
158
  (prevState: ActionResult<TData> | null, formData: FormData): Promise<ActionResult<TData>>;
151
159
  };
@@ -196,6 +204,6 @@ export declare function createActionClient<TCtx = Record<string, never>>(config?
196
204
  * )
197
205
  * ```
198
206
  */
199
- export declare function validated<TInput, TData>(schema: ActionSchema<TInput>, handler: (input: TInput) => Promise<TData>): ActionFn<TData>;
207
+ export declare function validated<TInput, TData>(schema: ActionSchema<TInput>, handler: (input: TInput) => Promise<TData>): ActionFn<TInput, TData>;
200
208
  export {};
201
209
  //# sourceMappingURL=action-client.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"action-client.d.ts","sourceRoot":"","sources":["../../src/server/action-client.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"action-client.d.ts","sourceRoot":"","sources":["../../src/server/action-client.ts"],"names":[],"mappings":"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"}
@@ -13,6 +13,5 @@ export { getFormFlash } from './form-flash';
13
13
  export type { FormFlashData } from './form-flash';
14
14
  export { revalidatePath, revalidateTag } from './actions';
15
15
  export { getTraceId, getSpanId, withSpan, addSpanEvent } from './tracing';
16
- export { defineSegmentParams } from '../segment-params/define.js';
17
16
  export type { ParamsDefinition, InferParamField, ParamField } from '../segment-params/define.js';
18
17
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAMA,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC7C,YAAY,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AACjD,YAAY,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAC5C,YAAY,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAKvD,OAAO,EACL,UAAU,EACV,SAAS,EACT,UAAU,EACV,SAAS,EACT,eAAe,EACf,gBAAgB,GACjB,MAAM,mBAAmB,CAAC;AAC3B,YAAY,EAAE,eAAe,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAGxF,OAAO,EACL,IAAI,EACJ,QAAQ,EACR,gBAAgB,EAChB,YAAY,EACZ,SAAS,EACT,KAAK,WAAW,EAChB,KAAK,eAAe,GACrB,MAAM,cAAc,CAAC;AAItB,YAAY,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAKxE,OAAO,EAAE,kBAAkB,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC7E,YAAY,EACV,YAAY,EACZ,QAAQ,EACR,aAAa,EACb,uBAAuB,EACvB,aAAa,EACb,gBAAgB,EAChB,YAAY,EACZ,gBAAgB,GACjB,MAAM,iBAAiB,CAAC;AAGzB,OAAO,EAAE,aAAa,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAGpD,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC5C,YAAY,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAGlD,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAI1D,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAI1E,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAClE,YAAY,EAAE,gBAAgB,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,6BAA6B,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAMA,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC7C,YAAY,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AACjD,YAAY,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAC5C,YAAY,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAKvD,OAAO,EACL,UAAU,EACV,SAAS,EACT,UAAU,EACV,SAAS,EACT,eAAe,EACf,gBAAgB,GACjB,MAAM,mBAAmB,CAAC;AAC3B,YAAY,EAAE,eAAe,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAGxF,OAAO,EACL,IAAI,EACJ,QAAQ,EACR,gBAAgB,EAChB,YAAY,EACZ,SAAS,EACT,KAAK,WAAW,EAChB,KAAK,eAAe,GACrB,MAAM,cAAc,CAAC;AAItB,YAAY,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAKxE,OAAO,EAAE,kBAAkB,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC7E,YAAY,EACV,YAAY,EACZ,QAAQ,EACR,aAAa,EACb,uBAAuB,EACvB,aAAa,EACb,gBAAgB,EAChB,YAAY,EACZ,gBAAgB,GACjB,MAAM,iBAAiB,CAAC;AAGzB,OAAO,EAAE,aAAa,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAGpD,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC5C,YAAY,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAGlD,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAI1D,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAI1E,YAAY,EAAE,gBAAgB,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,6BAA6B,CAAC"}
@@ -1,7 +1,6 @@
1
1
  import { n as isDevMode, t as isDebug } from "../_chunks/debug-ECi_61pb.js";
2
2
  import { t as formatSize } from "../_chunks/format-CYBGxKtc.js";
3
3
  import { n as formFlashAls } from "../_chunks/als-registry-HS0LGUl2.js";
4
- import { n as defineSegmentParams } from "../_chunks/define-C77ScO0m.js";
5
4
  import { a as getHeaders, c as getSegmentParams, i as getHeader, n as getCookie, r as getCookies, s as getSearchParams } from "../_chunks/request-context-qMsWgy9C.js";
6
5
  import { a as revalidateTag, c as RedirectType, d as redirect, f as redirectExternal, i as revalidatePath, o as DenySignal, p as waitUntil, s as RedirectSignal, u as deny } from "../_chunks/actions-Dg-ANYHb.js";
7
6
  import { a as getSpanId, d as withSpan, o as getTraceId, t as addSpanEvent } from "../_chunks/tracing-CCYbKn5n.js";
@@ -435,6 +434,6 @@ function getFormFlash() {
435
434
  return formFlashAls.getStore() ?? null;
436
435
  }
437
436
  //#endregion
438
- export { ActionError, RedirectType, addSpanEvent, coerce, createActionClient, defineSegmentParams, deny, getCookie, getCookies, getFormFlash, getHeader, getHeaders, getSearchParams, getSegmentParams, getSpanId, getTraceId, parseFormData, redirect, redirectExternal, revalidatePath, revalidateTag, validated, waitUntil, withSpan };
437
+ export { ActionError, RedirectType, addSpanEvent, coerce, createActionClient, deny, getCookie, getCookies, getFormFlash, getHeader, getHeaders, getSearchParams, getSegmentParams, getSpanId, getTraceId, parseFormData, redirect, redirectExternal, revalidatePath, revalidateTag, validated, waitUntil, withSpan };
439
438
 
440
439
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../src/server/form-data.ts","../../src/server/action-client.ts","../../src/server/form-flash.ts"],"sourcesContent":["/**\n * FormData preprocessing — schema-agnostic conversion of FormData to typed objects.\n *\n * FormData is all strings. Schema validation expects typed values. This module\n * bridges the gap with intelligent coercion that runs *before* schema validation.\n *\n * Inspired by zod-form-data, but schema-agnostic — works with any Standard Schema\n * library (Zod, Valibot, ArkType).\n *\n * See design/08-forms-and-actions.md §\"parseFormData() and coerce helpers\"\n */\n\n// ─── parseFormData ───────────────────────────────────────────────────────\n\n/**\n * Convert FormData into a plain object with intelligent coercion.\n *\n * Handles:\n * - **Duplicate keys → arrays**: `tags=js&tags=ts` → `{ tags: [\"js\", \"ts\"] }`\n * - **Nested dot-paths**: `user.name=Alice` → `{ user: { name: \"Alice\" } }`\n * - **Empty strings → undefined**: Enables `.optional()` semantics in schemas\n * - **Empty Files → undefined**: File inputs with no selection become `undefined`\n * - **Strips `$ACTION_*` fields**: React's internal hidden fields are excluded\n */\nexport function parseFormData(formData: FormData): Record<string, unknown> {\n const flat: Record<string, unknown> = {};\n\n for (const key of new Set(formData.keys())) {\n // Skip React internal fields\n if (key.startsWith('$ACTION_')) continue;\n\n const values = formData.getAll(key);\n const processed = values.map(normalizeValue);\n\n if (processed.length === 1) {\n flat[key] = processed[0];\n } else {\n // Filter out undefined entries from multi-value fields\n flat[key] = processed.filter((v) => v !== undefined);\n }\n }\n\n // Expand dot-notation paths into nested objects\n return expandDotPaths(flat);\n}\n\n/**\n * Normalize a single FormData entry value.\n * - Empty strings → undefined (enables .optional() semantics)\n * - Empty File objects (no selection) → undefined\n * - Everything else passes through as-is\n */\nfunction normalizeValue(value: FormDataEntryValue): unknown {\n if (typeof value === 'string') {\n return value === '' ? undefined : value;\n }\n\n // File input with no selection: browsers submit a File with name=\"\" and size=0\n if (value instanceof File && value.size === 0 && value.name === '') {\n return undefined;\n }\n\n return value;\n}\n\n/**\n * Expand dot-notation keys into nested objects.\n * `{ \"user.name\": \"Alice\", \"user.age\": \"30\" }` → `{ user: { name: \"Alice\", age: \"30\" } }`\n *\n * Keys without dots are left as-is. Bracket notation (e.g. `items[0]`) is NOT\n * supported — use dot notation (`items.0`) instead.\n */\nfunction expandDotPaths(flat: Record<string, unknown>): Record<string, unknown> {\n const result: Record<string, unknown> = {};\n let hasDotPaths = false;\n\n // First pass: check if any keys have dots\n for (const key of Object.keys(flat)) {\n if (key.includes('.')) {\n hasDotPaths = true;\n break;\n }\n }\n\n // Fast path: no dot-notation keys, return as-is\n if (!hasDotPaths) return flat;\n\n for (const [key, value] of Object.entries(flat)) {\n if (!key.includes('.')) {\n result[key] = value;\n continue;\n }\n\n const parts = key.split('.');\n let current: Record<string, unknown> = result;\n\n for (let i = 0; i < parts.length - 1; i++) {\n const part = parts[i];\n if (current[part] === undefined || current[part] === null) {\n current[part] = {};\n }\n // If current[part] is not an object (e.g., a string from a non-dotted key),\n // the dot-path takes precedence\n if (typeof current[part] !== 'object' || current[part] instanceof File) {\n current[part] = {};\n }\n current = current[part] as Record<string, unknown>;\n }\n\n current[parts[parts.length - 1]] = value;\n }\n\n return result;\n}\n\n// ─── Coercion Helpers ────────────────────────────────────────────────────\n\n/**\n * Schema-agnostic coercion primitives for common FormData patterns.\n *\n * These are plain transform functions — they compose with any schema library's\n * `transform`/`preprocess` pipeline:\n *\n * ```ts\n * // Zod\n * z.preprocess(coerce.number, z.number())\n * // Valibot\n * v.pipe(v.unknown(), v.transform(coerce.number), v.number())\n * ```\n */\nexport const coerce = {\n /**\n * Coerce a string to a number.\n * - `\"42\"` → `42`\n * - `\"3.14\"` → `3.14`\n * - `\"\"` / `undefined` / `null` → `undefined`\n * - Non-numeric strings → `undefined` (schema validation will catch this)\n */\n number(value: unknown): number | undefined {\n if (value === undefined || value === null || value === '') return undefined;\n if (typeof value === 'number') return value;\n if (typeof value !== 'string') return undefined;\n const num = Number(value);\n if (Number.isNaN(num)) return undefined;\n return num;\n },\n\n /**\n * Coerce a checkbox value to a boolean.\n * HTML checkboxes submit \"on\" when checked and are absent when unchecked.\n * - `\"on\"` / any truthy string → `true`\n * - `undefined` / `null` / `\"\"` → `false`\n */\n checkbox(value: unknown): boolean {\n if (value === undefined || value === null || value === '') return false;\n if (typeof value === 'boolean') return value;\n // Any non-empty string (typically \"on\") is true\n return typeof value === 'string' && value.length > 0;\n },\n\n /**\n * Parse a JSON string into an object.\n * - Valid JSON string → parsed object\n * - `\"\"` / `undefined` / `null` → `undefined`\n * - Invalid JSON → `undefined` (schema validation will catch this)\n */\n json(value: unknown): unknown {\n if (value === undefined || value === null || value === '') return undefined;\n if (typeof value !== 'string') return value;\n try {\n return JSON.parse(value);\n } catch {\n return undefined;\n }\n },\n\n /**\n * Coerce a date string to a Date object.\n * Handles `<input type=\"date\">` (`\"2024-01-15\"`), `<input type=\"datetime-local\">`\n * (`\"2024-01-15T10:30\"`), and full ISO 8601 strings.\n * - Valid date string → `Date`\n * - `\"\"` / `undefined` / `null` → `undefined`\n * - Invalid date strings → `undefined` (schema validation will catch this)\n * - Impossible dates that `new Date()` silently normalizes (e.g. Feb 31) → `undefined`\n */\n date(value: unknown): Date | undefined {\n if (value === undefined || value === null || value === '') return undefined;\n if (value instanceof Date) return value;\n if (typeof value !== 'string') return undefined;\n const date = new Date(value);\n if (Number.isNaN(date.getTime())) return undefined;\n\n // Overflow detection: extract Y/M/D from the input string and verify\n // they match the parsed Date components. new Date('2024-02-31') silently\n // normalizes to March 2nd — we reject such inputs.\n const ymdMatch = value.match(/^(\\d{4})-(\\d{2})-(\\d{2})/);\n if (ymdMatch) {\n const inputYear = Number(ymdMatch[1]);\n const inputMonth = Number(ymdMatch[2]);\n const inputDay = Number(ymdMatch[3]);\n\n // Use UTC methods for date-only and Z-suffixed strings to avoid\n // timezone offset shifting the day. For datetime-local (no Z suffix),\n // the Date constructor parses in local time, so use local methods.\n const isUTC = value.length === 10 || value.endsWith('Z');\n const parsedYear = isUTC ? date.getUTCFullYear() : date.getFullYear();\n const parsedMonth = isUTC ? date.getUTCMonth() + 1 : date.getMonth() + 1;\n const parsedDay = isUTC ? date.getUTCDate() : date.getDate();\n\n if (inputYear !== parsedYear || inputMonth !== parsedMonth || inputDay !== parsedDay) {\n return undefined;\n }\n }\n\n return date;\n },\n\n /**\n * Create a File coercion function with optional size and mime type validation.\n * Returns the File if valid, `undefined` otherwise.\n *\n * ```ts\n * // Basic — just checks it's a real File\n * z.preprocess(coerce.file(), z.instanceof(File))\n *\n * // With constraints\n * z.preprocess(\n * coerce.file({ maxSize: 5 * 1024 * 1024, accept: ['image/png', 'image/jpeg'] }),\n * z.instanceof(File)\n * )\n * ```\n */\n file(options?: { maxSize?: number; accept?: string[] }): (value: unknown) => File | undefined {\n return (value: unknown): File | undefined => {\n if (value === undefined || value === null || value === '') return undefined;\n if (!(value instanceof File)) return undefined;\n\n // Empty file input (no selection): browsers submit File with name=\"\" and size=0\n if (value.size === 0 && value.name === '') return undefined;\n\n if (options?.maxSize !== undefined && value.size > options.maxSize) {\n return undefined;\n }\n\n if (options?.accept !== undefined && !options.accept.includes(value.type)) {\n return undefined;\n }\n\n return value;\n };\n },\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 for 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 '~standard': {\n validate(value: unknown): 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\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>(fn: (ctx: ActionContext<TCtx, undefined>) => Promise<TData>): ActionFn<TData>;\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>;\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 */\nexport type ActionFn<TData> = {\n /** <form action={fn}> compatibility — React discards the return value. */\n (formData: FormData): void;\n /** Direct call: action(input) */\n (input?: unknown): 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.js';\nimport { formatSize } from '../utils/format.js';\nimport { isDebug, isDevMode } from './debug.js';\nimport { RedirectSignal, DenySignal } from './primitives.js';\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> {\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 // 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 const submittedValues = stripFiles(rawInput);\n return { validationErrors: fileSizeErrors, submittedValues };\n }\n }\n\n // Capture submitted values for repopulation on validation failure.\n // Exclude File objects (can't serialize, shouldn't echo back).\n const submittedValues = schema ? stripFiles(rawInput) : 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 (error instanceof RedirectSignal || error instanceof DenySignal) {\n throw error;\n }\n return handleActionError(error);\n }\n }\n\n return actionHandler as ActionFn<TData>;\n }\n\n return {\n schema<TInput>(schema: ActionSchema<TInput>) {\n return {\n action<TData>(fn: (ctx: ActionContext<TCtx, TInput>) => Promise<TData>): ActionFn<TData> {\n return buildAction(schema, fn);\n },\n };\n },\n action<TData>(fn: (ctx: ActionContext<TCtx, undefined>) => Promise<TData>): ActionFn<TData> {\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> {\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 errors: ValidationErrors = {};\n const limitKb = Math.round(limit / 1024);\n const limitLabel =\n limit >= 1024 * 1024 ? `${Math.round(limit / (1024 * 1024))}MB` : `${limitKb}KB`;\n\n for (const [key, value] of Object.entries(input)) {\n if (value instanceof File && value.size > limit) {\n errors[key] = [\n `File \"${value.name}\" (${formatSize(value.size)}) exceeds the ${limitLabel} limit`,\n ];\n } else if (Array.isArray(value)) {\n const oversized = value.filter((item) => item instanceof File && item.size > limit);\n if (oversized.length > 0) {\n errors[key] = oversized.map(\n (f: File) => `File \"${f.name}\" (${formatSize(f.size)}) exceeds the ${limitLabel} limit`\n );\n }\n }\n }\n\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.filter((item) => !(item instanceof File));\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.js';\nimport { formFlashAls } from './als-registry.js';\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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAwBA,SAAgB,cAAc,UAA6C;CACzE,MAAM,OAAgC,EAAE;AAExC,MAAK,MAAM,OAAO,IAAI,IAAI,SAAS,MAAM,CAAC,EAAE;AAE1C,MAAI,IAAI,WAAW,WAAW,CAAE;EAGhC,MAAM,YADS,SAAS,OAAO,IAAI,CACV,IAAI,eAAe;AAE5C,MAAI,UAAU,WAAW,EACvB,MAAK,OAAO,UAAU;MAGtB,MAAK,OAAO,UAAU,QAAQ,MAAM,MAAM,KAAA,EAAU;;AAKxD,QAAO,eAAe,KAAK;;;;;;;;AAS7B,SAAS,eAAe,OAAoC;AAC1D,KAAI,OAAO,UAAU,SACnB,QAAO,UAAU,KAAK,KAAA,IAAY;AAIpC,KAAI,iBAAiB,QAAQ,MAAM,SAAS,KAAK,MAAM,SAAS,GAC9D;AAGF,QAAO;;;;;;;;;AAUT,SAAS,eAAe,MAAwD;CAC9E,MAAM,SAAkC,EAAE;CAC1C,IAAI,cAAc;AAGlB,MAAK,MAAM,OAAO,OAAO,KAAK,KAAK,CACjC,KAAI,IAAI,SAAS,IAAI,EAAE;AACrB,gBAAc;AACd;;AAKJ,KAAI,CAAC,YAAa,QAAO;AAEzB,MAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,KAAK,EAAE;AAC/C,MAAI,CAAC,IAAI,SAAS,IAAI,EAAE;AACtB,UAAO,OAAO;AACd;;EAGF,MAAM,QAAQ,IAAI,MAAM,IAAI;EAC5B,IAAI,UAAmC;AAEvC,OAAK,IAAI,IAAI,GAAG,IAAI,MAAM,SAAS,GAAG,KAAK;GACzC,MAAM,OAAO,MAAM;AACnB,OAAI,QAAQ,UAAU,KAAA,KAAa,QAAQ,UAAU,KACnD,SAAQ,QAAQ,EAAE;AAIpB,OAAI,OAAO,QAAQ,UAAU,YAAY,QAAQ,iBAAiB,KAChE,SAAQ,QAAQ,EAAE;AAEpB,aAAU,QAAQ;;AAGpB,UAAQ,MAAM,MAAM,SAAS,MAAM;;AAGrC,QAAO;;;;;;;;;;;;;;;AAkBT,IAAa,SAAS;CAQpB,OAAO,OAAoC;AACzC,MAAI,UAAU,KAAA,KAAa,UAAU,QAAQ,UAAU,GAAI,QAAO,KAAA;AAClE,MAAI,OAAO,UAAU,SAAU,QAAO;AACtC,MAAI,OAAO,UAAU,SAAU,QAAO,KAAA;EACtC,MAAM,MAAM,OAAO,MAAM;AACzB,MAAI,OAAO,MAAM,IAAI,CAAE,QAAO,KAAA;AAC9B,SAAO;;CAST,SAAS,OAAyB;AAChC,MAAI,UAAU,KAAA,KAAa,UAAU,QAAQ,UAAU,GAAI,QAAO;AAClE,MAAI,OAAO,UAAU,UAAW,QAAO;AAEvC,SAAO,OAAO,UAAU,YAAY,MAAM,SAAS;;CASrD,KAAK,OAAyB;AAC5B,MAAI,UAAU,KAAA,KAAa,UAAU,QAAQ,UAAU,GAAI,QAAO,KAAA;AAClE,MAAI,OAAO,UAAU,SAAU,QAAO;AACtC,MAAI;AACF,UAAO,KAAK,MAAM,MAAM;UAClB;AACN;;;CAaJ,KAAK,OAAkC;AACrC,MAAI,UAAU,KAAA,KAAa,UAAU,QAAQ,UAAU,GAAI,QAAO,KAAA;AAClE,MAAI,iBAAiB,KAAM,QAAO;AAClC,MAAI,OAAO,UAAU,SAAU,QAAO,KAAA;EACtC,MAAM,OAAO,IAAI,KAAK,MAAM;AAC5B,MAAI,OAAO,MAAM,KAAK,SAAS,CAAC,CAAE,QAAO,KAAA;EAKzC,MAAM,WAAW,MAAM,MAAM,2BAA2B;AACxD,MAAI,UAAU;GACZ,MAAM,YAAY,OAAO,SAAS,GAAG;GACrC,MAAM,aAAa,OAAO,SAAS,GAAG;GACtC,MAAM,WAAW,OAAO,SAAS,GAAG;GAKpC,MAAM,QAAQ,MAAM,WAAW,MAAM,MAAM,SAAS,IAAI;GACxD,MAAM,aAAa,QAAQ,KAAK,gBAAgB,GAAG,KAAK,aAAa;GACrE,MAAM,cAAc,QAAQ,KAAK,aAAa,GAAG,IAAI,KAAK,UAAU,GAAG;GACvE,MAAM,YAAY,QAAQ,KAAK,YAAY,GAAG,KAAK,SAAS;AAE5D,OAAI,cAAc,cAAc,eAAe,eAAe,aAAa,UACzE;;AAIJ,SAAO;;CAkBT,KAAK,SAAyF;AAC5F,UAAQ,UAAqC;AAC3C,OAAI,UAAU,KAAA,KAAa,UAAU,QAAQ,UAAU,GAAI,QAAO,KAAA;AAClE,OAAI,EAAE,iBAAiB,MAAO,QAAO,KAAA;AAGrC,OAAI,MAAM,SAAS,KAAK,MAAM,SAAS,GAAI,QAAO,KAAA;AAElD,OAAI,SAAS,YAAY,KAAA,KAAa,MAAM,OAAO,QAAQ,QACzD;AAGF,OAAI,SAAS,WAAW,KAAA,KAAa,CAAC,QAAQ,OAAO,SAAS,MAAM,KAAK,CACvE;AAGF,UAAO;;;CAGZ;;;;;;;;;;;;;;;;;;;;;;;ACpOD,IAAa,cAAb,cAAgE,MAAM;CACpE;CACA;CAEA,YAAY,MAAa,MAAgC;AACvD,QAAM,gBAAgB,OAAO;AAC7B,OAAK,OAAO;AACZ,OAAK,OAAO;AACZ,OAAK,OAAO;;;;AA8BhB,SAAS,iBAAiB,QAA6C;AACrE,QACE,OAAO,WAAW,YAClB,WAAW,QACX,eAAe,UACf,OAAQ,OAA4B,aAAa,aAAa;;;;;AA6GlE,eAAe,oBACb,YACe;AACf,KAAI,CAAC,WACH,QAAO,EAAE;AAGX,KAAI,MAAM,QAAQ,WAAW,EAAE;EAC7B,IAAI,SAAS,EAAE;AACf,OAAK,MAAM,MAAM,YAAY;GAC3B,MAAM,SAAS,MAAM,IAAI;AACzB,YAAS;IAAE,GAAG;IAAQ,GAAG;IAAQ;;AAEnC,SAAO;;AAGT,QAAO,MAAM,YAAY;;;;;;AAa3B,SAAS,wBAAwB,OAAsC;AAErE,KAAI,OAAO,MAAM,YAAY,WAC3B,QAAO,MAAM,SAAS,CAAC;AAIzB,KAAI,MAAM,QAAQ;EAChB,MAAM,SAA2B,EAAE;AACnC,OAAK,MAAM,SAAS,MAAM,QAAQ;GAChC,MAAM,OAAO,MAAM,MAAM,KAAK,IAAI,IAAI;AACtC,OAAI,CAAC,OAAO,MAAO,QAAO,QAAQ,EAAE;AACpC,UAAO,MAAM,KAAK,MAAM,QAAQ;;AAElC,SAAO;;AAGT,QAAO,EAAE,OAAO,CAAC,oBAAoB,EAAE;;;;;AAMzC,SAAS,4BAA4B,QAA8D;CACjG,MAAM,SAA2B,EAAE;AACnC,MAAK,MAAM,SAAS,QAAQ;EAC1B,MAAM,OACJ,MAAM,MACF,KAAK,MAAM;AAEX,OAAI,OAAO,MAAM,YAAY,MAAM,QAAQ,SAAS,EAAG,QAAO,OAAO,EAAE,IAAI;AAC3E,UAAO,OAAO,EAAE;IAChB,CACD,KAAK,IAAI,IAAI;AAClB,MAAI,CAAC,OAAO,MAAO,QAAO,QAAQ,EAAE;AACpC,SAAO,MAAM,KAAK,MAAM,QAAQ;;AAElC,QAAO,OAAO,KAAK,OAAO,CAAC,SAAS,IAAI,SAAS,EAAE,OAAO,CAAC,oBAAoB,EAAE;;;;;;;;;AAUnF,SAAgB,kBAAkB,OAAqC;AACrE,KAAI,iBAAiB,YACnB,QAAO,EACL,aAAa;EACX,MAAM,MAAM;EACZ,GAAI,MAAM,OAAO,EAAE,MAAM,MAAM,MAAM,GAAG,EAAE;EAC3C,EACF;AAQH,QAAO,EACL,aAAa;EACX,MAAM;EACN,GAJY,WAAW,IAIR,iBAAiB,QAAQ,EAAE,MAAM,EAAE,SAAS,MAAM,SAAS,EAAE,GAAG,EAAE;EAClF,EACF;;;;;;;;;;;;;;;;;;;;;;AAuBH,SAAgB,mBACd,SAAmC,EAAE,EAChB;CACrB,SAAS,YACP,QACA,IACiB;EACjB,eAAe,cAAc,GAAG,MAA+C;AAC7E,OAAI;IAEF,MAAM,MAAM,MAAM,oBAAoB,OAAO,WAAW;IAGxD,IAAI;AACJ,QAAI,KAAK,WAAW,KAAK,KAAK,cAAc,SAE1C,YAAW,SAAS,cAAc,KAAK,GAAG,GAAG,KAAK;aACzC,KAAK,WAAW,KAAK,KAAK,cAAc,SAKjD,YAAW,SAAS,cAAc,KAAK,GAAG,GAAG,KAAK;QAGlD,YAAW,KAAK;AAIlB,QAAI,OAAO,kBAAkB,KAAA,KAAa,YAAY,OAAO,aAAa,UAAU;KAClF,MAAM,iBAAiB,kBACrB,UACA,OAAO,cACR;AACD,SAAI,eAEF,QAAO;MAAE,kBAAkB;MAAgB,iBADnB,WAAW,SAAS;MACgB;;IAMhE,MAAM,kBAAkB,SAAS,WAAW,SAAS,GAAG,KAAA;IAGxD,IAAI;AACJ,QAAI,OACF,KAAI,iBAAiB,OAAO,EAAE;KAE5B,MAAM,SAAS,OAAO,aAAa,SAAS,SAAS;AACrD,SAAI,kBAAkB,QACpB,OAAM,IAAI,MACR,4FACD;AAEH,SAAI,OAAO,QAAQ;MACjB,MAAM,mBAAmB,4BAA4B,OAAO,OAAO;AACnE,2BAAqB,iBAAiB;AACtC,aAAO;OAAE;OAAkB;OAAiB;;AAE9C,aAAQ,OAAO;eACN,OAAO,OAAO,cAAc,YAAY;KACjD,MAAM,SAAS,OAAO,UAAU,SAAS;AACzC,SAAI,CAAC,OAAO,SAAS;MACnB,MAAM,mBAAmB,wBAAwB,OAAO,MAAM;AAC9D,2BAAqB,iBAAiB;AACtC,aAAO;OAAE;OAAkB;OAAiB;;AAE9C,aAAQ,OAAO;UAEf,KAAI;AACF,aAAQ,OAAO,MAAM,SAAS;aACvB,YAAY;KACnB,MAAM,mBAAmB,wBAAwB,WAA0B;AAC3E,0BAAqB,iBAAiB;AACtC,YAAO;MAAE;MAAkB;MAAiB;;QAIhD,SAAQ;AAKV,WAAO,EAAE,MADI,MAAM,GAAG;KAAE;KAAK;KAAO,CAAC,EACtB;YACR,OAAO;AAKd,QAAI,iBAAiB,kBAAkB,iBAAiB,WACtD,OAAM;AAER,WAAO,kBAAkB,MAAM;;;AAInC,SAAO;;AAGT,QAAO;EACL,OAAe,QAA8B;AAC3C,UAAO,EACL,OAAc,IAA2E;AACvF,WAAO,YAAY,QAAQ,GAAG;MAEjC;;EAEH,OAAc,IAA8E;AAC1F,UAAO,YAAY,KAAA,GAAW,GAA4D;;EAE7F;;;;;;;;;;;;;;;;;;;;AAuBH,SAAgB,UACd,QACA,SACiB;AACjB,QAAO,oBAAoB,CACxB,OAAO,OAAO,CACd,OAAO,OAAO,EAAE,YAAY,QAAQ,MAAM,CAAC;;;;;;AAShD,SAAS,qBAAqB,QAAgC;AAE5D,KAAI,CADU,SAAS,CACX;CAEZ,MAAM,SAAS,OAAO,QAAQ,OAAO,CAClC,KAAK,CAAC,OAAO,cAAc,KAAK,MAAM,IAAI,SAAS,KAAK,KAAK,GAAG,CAChE,KAAK,KAAK;AACb,SAAQ,KAAK,8CAA8C,SAAS;;;;;;AAOtE,SAAS,kBAAkB,OAAgC,OAAwC;CACjG,MAAM,SAA2B,EAAE;CACnC,MAAM,UAAU,KAAK,MAAM,QAAQ,KAAK;CACxC,MAAM,aACJ,SAAS,OAAO,OAAO,GAAG,KAAK,MAAM,SAAS,OAAO,MAAM,CAAC,MAAM,GAAG,QAAQ;AAE/E,MAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,MAAM,CAC9C,KAAI,iBAAiB,QAAQ,MAAM,OAAO,MACxC,QAAO,OAAO,CACZ,SAAS,MAAM,KAAK,KAAK,WAAW,MAAM,KAAK,CAAC,gBAAgB,WAAW,QAC5E;UACQ,MAAM,QAAQ,MAAM,EAAE;EAC/B,MAAM,YAAY,MAAM,QAAQ,SAAS,gBAAgB,QAAQ,KAAK,OAAO,MAAM;AACnF,MAAI,UAAU,SAAS,EACrB,QAAO,OAAO,UAAU,KACrB,MAAY,SAAS,EAAE,KAAK,KAAK,WAAW,EAAE,KAAK,CAAC,gBAAgB,WAAW,QACjF;;AAKP,QAAO,OAAO,KAAK,OAAO,CAAC,SAAS,IAAI,SAAS;;;;;;AAOnD,SAAS,WAAW,OAAqD;AACvE,KAAI,UAAU,QAAQ,UAAU,KAAA,EAAW,QAAO,KAAA;AAClD,KAAI,OAAO,UAAU,SAAU,QAAO,KAAA;CAEtC,MAAM,SAAkC,EAAE;AAC1C,MAAK,MAAM,CAAC,GAAG,MAAM,OAAO,QAAQ,MAAiC,EAAE;AACrE,MAAI,aAAa,KAAM;AACvB,MAAI,MAAM,QAAQ,EAAE,CAClB,QAAO,KAAK,EAAE,QAAQ,SAAS,EAAE,gBAAgB,MAAM;WAC9C,OAAO,MAAM,YAAY,MAAM,QAAQ,EAAE,aAAa,MAC/D,QAAO,KAAK,WAAW,EAAE,IAAI,EAAE;MAE/B,QAAO,KAAK;;AAGhB,QAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AC5aT,SAAgB,eAAqC;AACnD,QAAO,aAAa,UAAU,IAAI"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../src/server/form-data.ts","../../src/server/action-client.ts","../../src/server/form-flash.ts"],"sourcesContent":["/**\n * FormData preprocessing — schema-agnostic conversion of FormData to typed objects.\n *\n * FormData is all strings. Schema validation expects typed values. This module\n * bridges the gap with intelligent coercion that runs *before* schema validation.\n *\n * Inspired by zod-form-data, but schema-agnostic — works with any Standard Schema\n * library (Zod, Valibot, ArkType).\n *\n * See design/08-forms-and-actions.md §\"parseFormData() and coerce helpers\"\n */\n\n// ─── parseFormData ───────────────────────────────────────────────────────\n\n/**\n * Convert FormData into a plain object with intelligent coercion.\n *\n * Handles:\n * - **Duplicate keys → arrays**: `tags=js&tags=ts` → `{ tags: [\"js\", \"ts\"] }`\n * - **Nested dot-paths**: `user.name=Alice` → `{ user: { name: \"Alice\" } }`\n * - **Empty strings → undefined**: Enables `.optional()` semantics in schemas\n * - **Empty Files → undefined**: File inputs with no selection become `undefined`\n * - **Strips `$ACTION_*` fields**: React's internal hidden fields are excluded\n */\nexport function parseFormData(formData: FormData): Record<string, unknown> {\n const flat: Record<string, unknown> = {};\n\n for (const key of new Set(formData.keys())) {\n // Skip React internal fields\n if (key.startsWith('$ACTION_')) continue;\n\n const values = formData.getAll(key);\n const processed = values.map(normalizeValue);\n\n if (processed.length === 1) {\n flat[key] = processed[0];\n } else {\n // Filter out undefined entries from multi-value fields\n flat[key] = processed.filter((v) => v !== undefined);\n }\n }\n\n // Expand dot-notation paths into nested objects\n return expandDotPaths(flat);\n}\n\n/**\n * Normalize a single FormData entry value.\n * - Empty strings → undefined (enables .optional() semantics)\n * - Empty File objects (no selection) → undefined\n * - Everything else passes through as-is\n */\nfunction normalizeValue(value: FormDataEntryValue): unknown {\n if (typeof value === 'string') {\n return value === '' ? undefined : value;\n }\n\n // File input with no selection: browsers submit a File with name=\"\" and size=0\n if (value instanceof File && value.size === 0 && value.name === '') {\n return undefined;\n }\n\n return value;\n}\n\n/**\n * Expand dot-notation keys into nested objects.\n * `{ \"user.name\": \"Alice\", \"user.age\": \"30\" }` → `{ user: { name: \"Alice\", age: \"30\" } }`\n *\n * Keys without dots are left as-is. Bracket notation (e.g. `items[0]`) is NOT\n * supported — use dot notation (`items.0`) instead.\n */\nfunction expandDotPaths(flat: Record<string, unknown>): Record<string, unknown> {\n const result: Record<string, unknown> = {};\n let hasDotPaths = false;\n\n // First pass: check if any keys have dots\n for (const key of Object.keys(flat)) {\n if (key.includes('.')) {\n hasDotPaths = true;\n break;\n }\n }\n\n // Fast path: no dot-notation keys, return as-is\n if (!hasDotPaths) return flat;\n\n for (const [key, value] of Object.entries(flat)) {\n if (!key.includes('.')) {\n result[key] = value;\n continue;\n }\n\n const parts = key.split('.');\n let current: Record<string, unknown> = result;\n\n for (let i = 0; i < parts.length - 1; i++) {\n const part = parts[i];\n if (current[part] === undefined || current[part] === null) {\n current[part] = {};\n }\n // If current[part] is not an object (e.g., a string from a non-dotted key),\n // the dot-path takes precedence\n if (typeof current[part] !== 'object' || current[part] instanceof File) {\n current[part] = {};\n }\n current = current[part] as Record<string, unknown>;\n }\n\n current[parts[parts.length - 1]] = value;\n }\n\n return result;\n}\n\n// ─── Coercion Helpers ────────────────────────────────────────────────────\n\n/**\n * Schema-agnostic coercion primitives for common FormData patterns.\n *\n * These are plain transform functions — they compose with any schema library's\n * `transform`/`preprocess` pipeline:\n *\n * ```ts\n * // Zod\n * z.preprocess(coerce.number, z.number())\n * // Valibot\n * v.pipe(v.unknown(), v.transform(coerce.number), v.number())\n * ```\n */\nexport const coerce = {\n /**\n * Coerce a string to a number.\n * - `\"42\"` → `42`\n * - `\"3.14\"` → `3.14`\n * - `\"\"` / `undefined` / `null` → `undefined`\n * - Non-numeric strings → `undefined` (schema validation will catch this)\n */\n number(value: unknown): number | undefined {\n if (value === undefined || value === null || value === '') return undefined;\n if (typeof value === 'number') return value;\n if (typeof value !== 'string') return undefined;\n const num = Number(value);\n if (Number.isNaN(num)) return undefined;\n return num;\n },\n\n /**\n * Coerce a checkbox value to a boolean.\n * HTML checkboxes submit \"on\" when checked and are absent when unchecked.\n * - `\"on\"` / any truthy string → `true`\n * - `undefined` / `null` / `\"\"` → `false`\n */\n checkbox(value: unknown): boolean {\n if (value === undefined || value === null || value === '') return false;\n if (typeof value === 'boolean') return value;\n // Any non-empty string (typically \"on\") is true\n return typeof value === 'string' && value.length > 0;\n },\n\n /**\n * Parse a JSON string into an object.\n * - Valid JSON string → parsed object\n * - `\"\"` / `undefined` / `null` → `undefined`\n * - Invalid JSON → `undefined` (schema validation will catch this)\n */\n json(value: unknown): unknown {\n if (value === undefined || value === null || value === '') return undefined;\n if (typeof value !== 'string') return value;\n try {\n return JSON.parse(value);\n } catch {\n return undefined;\n }\n },\n\n /**\n * Coerce a date string to a Date object.\n * Handles `<input type=\"date\">` (`\"2024-01-15\"`), `<input type=\"datetime-local\">`\n * (`\"2024-01-15T10:30\"`), and full ISO 8601 strings.\n * - Valid date string → `Date`\n * - `\"\"` / `undefined` / `null` → `undefined`\n * - Invalid date strings → `undefined` (schema validation will catch this)\n * - Impossible dates that `new Date()` silently normalizes (e.g. Feb 31) → `undefined`\n */\n date(value: unknown): Date | undefined {\n if (value === undefined || value === null || value === '') return undefined;\n if (value instanceof Date) return value;\n if (typeof value !== 'string') return undefined;\n const date = new Date(value);\n if (Number.isNaN(date.getTime())) return undefined;\n\n // Overflow detection: extract Y/M/D from the input string and verify\n // they match the parsed Date components. new Date('2024-02-31') silently\n // normalizes to March 2nd — we reject such inputs.\n const ymdMatch = value.match(/^(\\d{4})-(\\d{2})-(\\d{2})/);\n if (ymdMatch) {\n const inputYear = Number(ymdMatch[1]);\n const inputMonth = Number(ymdMatch[2]);\n const inputDay = Number(ymdMatch[3]);\n\n // Use UTC methods for date-only and Z-suffixed strings to avoid\n // timezone offset shifting the day. For datetime-local (no Z suffix),\n // the Date constructor parses in local time, so use local methods.\n const isUTC = value.length === 10 || value.endsWith('Z');\n const parsedYear = isUTC ? date.getUTCFullYear() : date.getFullYear();\n const parsedMonth = isUTC ? date.getUTCMonth() + 1 : date.getMonth() + 1;\n const parsedDay = isUTC ? date.getUTCDate() : date.getDate();\n\n if (inputYear !== parsedYear || inputMonth !== parsedMonth || inputDay !== parsedDay) {\n return undefined;\n }\n }\n\n return date;\n },\n\n /**\n * Create a File coercion function with optional size and mime type validation.\n * Returns the File if valid, `undefined` otherwise.\n *\n * ```ts\n * // Basic — just checks it's a real File\n * z.preprocess(coerce.file(), z.instanceof(File))\n *\n * // With constraints\n * z.preprocess(\n * coerce.file({ maxSize: 5 * 1024 * 1024, accept: ['image/png', 'image/jpeg'] }),\n * z.instanceof(File)\n * )\n * ```\n */\n file(options?: { maxSize?: number; accept?: string[] }): (value: unknown) => File | undefined {\n return (value: unknown): File | undefined => {\n if (value === undefined || value === null || value === '') return undefined;\n if (!(value instanceof File)) return undefined;\n\n // Empty file input (no selection): browsers submit File with name=\"\" and size=0\n if (value.size === 0 && value.name === '') return undefined;\n\n if (options?.maxSize !== undefined && value.size > options.maxSize) {\n return undefined;\n }\n\n if (options?.accept !== undefined && !options.accept.includes(value.type)) {\n return undefined;\n }\n\n return value;\n };\n },\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 for 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 '~standard': {\n validate(value: unknown): 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\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<undefined, TData>;\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<TInput, TData>;\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\nexport type ActionFn<TInput = unknown, TData = 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 (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.js';\nimport { formatSize } from '../utils/format.js';\nimport { isDebug, isDevMode } from './debug.js';\nimport { RedirectSignal, DenySignal } from './primitives.js';\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<TInput, TData> {\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 // 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 const submittedValues = stripFiles(rawInput);\n return { validationErrors: fileSizeErrors, submittedValues };\n }\n }\n\n // Capture submitted values for repopulation on validation failure.\n // Exclude File objects (can't serialize, shouldn't echo back).\n const submittedValues = schema ? stripFiles(rawInput) : 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 (error instanceof RedirectSignal || error instanceof DenySignal) {\n throw error;\n }\n return handleActionError(error);\n }\n }\n\n return actionHandler as ActionFn<TInput, TData>;\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<TInput, TData> {\n return buildAction(schema, fn);\n },\n };\n },\n action<TData>(\n fn: (ctx: ActionContext<TCtx, undefined>) => Promise<TData>\n ): ActionFn<undefined, TData> {\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<TInput, TData> {\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 errors: ValidationErrors = {};\n const limitKb = Math.round(limit / 1024);\n const limitLabel =\n limit >= 1024 * 1024 ? `${Math.round(limit / (1024 * 1024))}MB` : `${limitKb}KB`;\n\n for (const [key, value] of Object.entries(input)) {\n if (value instanceof File && value.size > limit) {\n errors[key] = [\n `File \"${value.name}\" (${formatSize(value.size)}) exceeds the ${limitLabel} limit`,\n ];\n } else if (Array.isArray(value)) {\n const oversized = value.filter((item) => item instanceof File && item.size > limit);\n if (oversized.length > 0) {\n errors[key] = oversized.map(\n (f: File) => `File \"${f.name}\" (${formatSize(f.size)}) exceeds the ${limitLabel} limit`\n );\n }\n }\n }\n\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.filter((item) => !(item instanceof File));\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.js';\nimport { formFlashAls } from './als-registry.js';\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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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@timber-js/app",
3
- "version": "0.2.0-alpha.80",
3
+ "version": "0.2.0-alpha.82",
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",
@@ -77,6 +77,10 @@
77
77
  "types": "./dist/search-params/index.d.ts",
78
78
  "import": "./dist/search-params/index.js"
79
79
  },
80
+ "./segment-params": {
81
+ "types": "./dist/segment-params/index.d.ts",
82
+ "import": "./dist/segment-params/index.js"
83
+ },
80
84
  "./adapters/cloudflare": {
81
85
  "types": "./dist/adapters/cloudflare.d.ts",
82
86
  "import": "./dist/adapters/cloudflare.js"
package/src/cli.ts CHANGED
@@ -110,9 +110,11 @@ export function resolvePreviewStrategy(
110
110
  * Returns the config object with adapter, output, etc.
111
111
  * Returns null if no config file is found.
112
112
  */
113
- async function loadTimberConfig(
114
- root: string
115
- ): Promise<{ adapter?: import('./adapters/types').TimberPlatformAdapter; output?: string } | null> {
113
+ async function loadTimberConfig(root: string): Promise<{
114
+ adapter?: import('./adapters/types').TimberPlatformAdapter;
115
+ output?: string;
116
+ buildDir?: string;
117
+ } | null> {
116
118
  const { existsSync } = await import('node:fs');
117
119
  const { join } = await import('node:path');
118
120
  const { pathToFileURL } = await import('node:url');
@@ -136,7 +138,7 @@ async function loadTimberConfig(
136
138
  * Otherwise falls back to Vite's built-in preview server.
137
139
  */
138
140
  export async function runPreview(options: CommandOptions, _deps?: ViteDeps): Promise<void> {
139
- const { join } = await import('node:path');
141
+ const { join, resolve } = await import('node:path');
140
142
 
141
143
  // Try to load timber config for adapter-specific preview
142
144
  const root = process.cwd();
@@ -144,7 +146,10 @@ export async function runPreview(options: CommandOptions, _deps?: ViteDeps): Pro
144
146
  const adapter = config?.adapter as import('./adapters/types').TimberPlatformAdapter | undefined;
145
147
 
146
148
  if (resolvePreviewStrategy(adapter) === 'adapter') {
147
- const buildDir = join(root, 'dist');
149
+ // Resolve build directory: timber config > default .timber/dist
150
+ const buildDir = config?.buildDir
151
+ ? resolve(root, config.buildDir)
152
+ : join(root, '.timber', 'dist');
148
153
  const timberConfig = { output: (config?.output ?? 'server') as 'server' | 'static' };
149
154
  await adapter!.preview!(timberConfig, buildDir);
150
155
  return;
@@ -12,7 +12,7 @@
12
12
  */
13
13
 
14
14
  import { useActionState as reactUseActionState, useTransition } from 'react';
15
- import type { ActionResult, ValidationErrors } from '../server/action-client';
15
+ import type { ActionFn, ActionResult, InputHint, ValidationErrors } from '../server/action-client';
16
16
  import type { FormFlashData } from '../server/form-flash';
17
17
 
18
18
  // ─── Types ───────────────────────────────────────────────────────────────
@@ -90,15 +90,17 @@ export function useActionState<TData>(
90
90
  * </button>
91
91
  * ```
92
92
  */
93
- export function useFormAction<TData>(
94
- action: (input: unknown) => Promise<ActionResult<TData>>
95
- ): [(input?: unknown) => Promise<ActionResult<TData>>, boolean] {
93
+ export function useFormAction<TInput = unknown, TData = unknown>(
94
+ action: ActionFn<TInput, TData> | ((input: TInput) => Promise<ActionResult<TData>>)
95
+ ): [(input: InputHint<TInput>) => Promise<ActionResult<TData>>, boolean] {
96
96
  const [isPending, startTransition] = useTransition();
97
97
 
98
- const execute = (input?: unknown): Promise<ActionResult<TData>> => {
98
+ const execute = (input: InputHint<TInput>): Promise<ActionResult<TData>> => {
99
99
  return new Promise((resolve) => {
100
100
  startTransition(async () => {
101
- const result = await action(input);
101
+ const result = await (action as (input: InputHint<TInput>) => Promise<ActionResult<TData>>)(
102
+ input
103
+ );
102
104
  resolve(result);
103
105
  });
104
106
  });
@@ -191,6 +191,23 @@ export interface TimberUserConfig {
191
191
  /** Include routes protected by `access.ts`. Default: false. */
192
192
  includeProtected?: boolean;
193
193
  };
194
+ /**
195
+ * Override the build output directory.
196
+ *
197
+ * The resolved path is relative to the project root. Timber config
198
+ * takes precedence over Vite's `build.outDir`. If neither is set,
199
+ * defaults to `.timber/dist`.
200
+ *
201
+ * @example
202
+ * ```ts
203
+ * // timber.config.ts
204
+ * export default defineConfig({
205
+ * buildDir: 'build', // outputs to <root>/build/
206
+ * buildDir: '.timber/dist', // default
207
+ * });
208
+ * ```
209
+ */
210
+ buildDir?: string;
194
211
  topLoader?: {
195
212
  /** Whether the top-loader is enabled. Default: true. */
196
213
  enabled?: boolean;