@timber-js/app 0.2.0-alpha.84 → 0.2.0-alpha.86

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 (73) hide show
  1. package/LICENSE +8 -0
  2. package/dist/_chunks/{actions-YHRCboUO.js → actions-DLnUaR65.js} +2 -2
  3. package/dist/_chunks/{actions-YHRCboUO.js.map → actions-DLnUaR65.js.map} +1 -1
  4. package/dist/_chunks/{chunk-DYhsFzuS.js → chunk-BYIpzuS7.js} +7 -1
  5. package/dist/_chunks/{define-cookie-C9pquwOg.js → define-cookie-BowvzoP0.js} +4 -4
  6. package/dist/_chunks/{define-cookie-C9pquwOg.js.map → define-cookie-BowvzoP0.js.map} +1 -1
  7. package/dist/_chunks/{request-context-Dl0hXED3.js → request-context-CK5tZqIP.js} +2 -2
  8. package/dist/_chunks/{request-context-Dl0hXED3.js.map → request-context-CK5tZqIP.js.map} +1 -1
  9. package/dist/client/form.d.ts +4 -1
  10. package/dist/client/form.d.ts.map +1 -1
  11. package/dist/client/index.js +2 -2
  12. package/dist/client/index.js.map +1 -1
  13. package/dist/config-validation.d.ts +51 -0
  14. package/dist/config-validation.d.ts.map +1 -0
  15. package/dist/cookies/index.js +1 -1
  16. package/dist/index.d.ts.map +1 -1
  17. package/dist/index.js +1185 -51
  18. package/dist/index.js.map +1 -1
  19. package/dist/plugins/dev-404-page.d.ts +56 -0
  20. package/dist/plugins/dev-404-page.d.ts.map +1 -0
  21. package/dist/plugins/dev-error-overlay.d.ts +25 -11
  22. package/dist/plugins/dev-error-overlay.d.ts.map +1 -1
  23. package/dist/plugins/dev-error-page.d.ts +58 -0
  24. package/dist/plugins/dev-error-page.d.ts.map +1 -0
  25. package/dist/plugins/dev-server.d.ts.map +1 -1
  26. package/dist/plugins/dev-terminal-error.d.ts +28 -0
  27. package/dist/plugins/dev-terminal-error.d.ts.map +1 -0
  28. package/dist/plugins/entries.d.ts.map +1 -1
  29. package/dist/plugins/fonts.d.ts +4 -0
  30. package/dist/plugins/fonts.d.ts.map +1 -1
  31. package/dist/plugins/routing.d.ts.map +1 -1
  32. package/dist/routing/convention-lint.d.ts +41 -0
  33. package/dist/routing/convention-lint.d.ts.map +1 -0
  34. package/dist/server/action-client.d.ts +13 -5
  35. package/dist/server/action-client.d.ts.map +1 -1
  36. package/dist/server/dev-source-map.d.ts +22 -0
  37. package/dist/server/dev-source-map.d.ts.map +1 -0
  38. package/dist/server/fallback-error.d.ts +9 -5
  39. package/dist/server/fallback-error.d.ts.map +1 -1
  40. package/dist/server/index.js +2 -2
  41. package/dist/server/index.js.map +1 -1
  42. package/dist/server/internal.js +21 -4
  43. package/dist/server/internal.js.map +1 -1
  44. package/dist/server/pipeline.d.ts +10 -0
  45. package/dist/server/pipeline.d.ts.map +1 -1
  46. package/dist/server/rsc-entry/error-renderer.d.ts.map +1 -1
  47. package/dist/server/rsc-entry/index.d.ts +11 -0
  48. package/dist/server/rsc-entry/index.d.ts.map +1 -1
  49. package/dist/server/rsc-entry/rsc-stream.d.ts +10 -0
  50. package/dist/server/rsc-entry/rsc-stream.d.ts.map +1 -1
  51. package/dist/server/rsc-entry/ssr-renderer.d.ts.map +1 -1
  52. package/package.json +6 -7
  53. package/src/cli.ts +0 -0
  54. package/src/client/form.tsx +10 -5
  55. package/src/config-validation.ts +299 -0
  56. package/src/index.ts +17 -0
  57. package/src/plugins/dev-404-page.ts +418 -0
  58. package/src/plugins/dev-error-overlay.ts +185 -54
  59. package/src/plugins/dev-error-page.ts +536 -0
  60. package/src/plugins/dev-server.ts +76 -10
  61. package/src/plugins/dev-terminal-error.ts +217 -0
  62. package/src/plugins/entries.ts +3 -0
  63. package/src/plugins/fonts.ts +3 -2
  64. package/src/plugins/routing.ts +37 -5
  65. package/src/routing/convention-lint.ts +356 -0
  66. package/src/server/action-client.ts +17 -9
  67. package/src/server/dev-source-map.ts +31 -0
  68. package/src/server/fallback-error.ts +44 -88
  69. package/src/server/pipeline.ts +34 -4
  70. package/src/server/rsc-entry/error-renderer.ts +5 -0
  71. package/src/server/rsc-entry/index.ts +88 -2
  72. package/src/server/rsc-entry/rsc-stream.ts +16 -0
  73. package/src/server/rsc-entry/ssr-renderer.ts +6 -3
@@ -12,6 +12,7 @@
12
12
  */
13
13
  import { type ProxyExport } from './proxy.js';
14
14
  import { type MiddlewareFn } from './middleware-runner.js';
15
+ import { DenySignal } from './primitives.js';
15
16
  import type { SegmentNode } from '../routing/types.js';
16
17
  /**
17
18
  * Shallow merge that skips prototype-polluting keys.
@@ -110,6 +111,15 @@ export interface PipelineConfig {
110
111
  * `new Response(null, { status: 500 })`.
111
112
  */
112
113
  renderFallbackError?: (error: unknown, req: Request, responseHeaders: Headers) => Response | Promise<Response>;
114
+ /**
115
+ * Fallback deny page renderer — called when a DenySignal escapes from
116
+ * middleware or the render phase. Renders the appropriate status-code
117
+ * page (403.tsx, 404.tsx, etc.) instead of returning a bare empty response.
118
+ *
119
+ * If this function throws, the pipeline falls back to a bare
120
+ * `new Response(null, { status: denyStatus })`.
121
+ */
122
+ renderDenyFallback?: (deny: DenySignal, req: Request, responseHeaders: Headers) => Response | Promise<Response>;
113
123
  }
114
124
  /**
115
125
  * Run segment param coercion on the matched route's segments.
@@ -1 +1 @@
1
- {"version":3,"file":"pipeline.d.ts","sourceRoot":"","sources":["../../src/server/pipeline.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"pipeline.d.ts","sourceRoot":"","sources":["../../src/server/pipeline.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"error-renderer.d.ts","sourceRoot":"","sources":["../../../src/server/rsc-entry/error-renderer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAKH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAEjD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAE/D,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAGlE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAWvD;;;;GAIG;AACH,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC;IAC7B,QAAQ,EAAE,MAAM,CAAC;CAClB;AAqHD;;;;;;GAMG;AACH,wBAAsB,eAAe,CACnC,KAAK,EAAE,OAAO,EACd,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,mBAAmB,EAAE,EAC/B,gBAAgB,EAAE,WAAW,EAAE,EAC/B,GAAG,EAAE,OAAO,EACZ,KAAK,EAAE,UAAU,EACjB,eAAe,EAAE,OAAO,EACxB,eAAe,EAAE,qBAAqB,EACtC,WAAW,CAAC,EAAE,eAAe,GAC5B,OAAO,CAAC,QAAQ,CAAC,CAoCnB;AAqID;;;;;;GAMG;AACH,wBAAsB,iBAAiB,CACrC,GAAG,EAAE,OAAO,EACZ,WAAW,EAAE,mBAAmB,EAChC,eAAe,EAAE,OAAO,EACxB,eAAe,EAAE,qBAAqB,GACrC,OAAO,CAAC,QAAQ,CAAC,CA6BnB"}
1
+ {"version":3,"file":"error-renderer.d.ts","sourceRoot":"","sources":["../../../src/server/rsc-entry/error-renderer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAKH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAEjD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAE/D,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAGlE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAYvD;;;;GAIG;AACH,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC;IAC7B,QAAQ,EAAE,MAAM,CAAC;CAClB;AAqHD;;;;;;GAMG;AACH,wBAAsB,eAAe,CACnC,KAAK,EAAE,OAAO,EACd,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,mBAAmB,EAAE,EAC/B,gBAAgB,EAAE,WAAW,EAAE,EAC/B,GAAG,EAAE,OAAO,EACZ,KAAK,EAAE,UAAU,EACjB,eAAe,EAAE,OAAO,EACxB,eAAe,EAAE,qBAAqB,EACtC,WAAW,CAAC,EAAE,eAAe,GAC5B,OAAO,CAAC,QAAQ,CAAC,CAwCnB;AAqID;;;;;;GAMG;AACH,wBAAsB,iBAAiB,CACrC,GAAG,EAAE,OAAO,EACZ,WAAW,EAAE,mBAAmB,EAChC,eAAe,EAAE,OAAO,EACxB,eAAe,EAAE,qBAAqB,GACrC,OAAO,CAAC,QAAQ,CAAC,CA6BnB"}
@@ -10,6 +10,17 @@ import { type DebugComponentEntry } from './helpers.js';
10
10
  * debug channel. This is only populated for render-phase errors.
11
11
  */
12
12
  export declare function setDevPipelineErrorHandler(handler: (error: Error, phase: string, debugComponents?: DebugComponentEntry[]) => void): void;
13
+ /**
14
+ * Set the dev source-map handler.
15
+ *
16
+ * Called by the dev server after importing this module to wire Vite's
17
+ * module graph source-mapping into the error rendering pipeline. Errors
18
+ * rendered by renderErrorPage / renderFallbackError will have their
19
+ * stack traces rewritten to show original source positions.
20
+ *
21
+ * No-op in production.
22
+ */
23
+ export declare function setDevSourceMapHandler(handler: (error: Error) => void): void;
13
24
  export { runWithEarlyHintsSender } from '../early-hints-sender.js';
14
25
  export { runWithWaitUntil } from '../waituntil-bridge.js';
15
26
  declare const _default: (req: Request) => Promise<Response>;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/server/rsc-entry/index.ts"],"names":[],"mappings":"AA0DA,OAAO,EAKL,KAAK,mBAAmB,EACzB,MAAM,cAAc,CAAC;AAmCtB;;;;;;;;;GASG;AACH,wBAAgB,0BAA0B,CACxC,OAAO,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,eAAe,CAAC,EAAE,mBAAmB,EAAE,KAAK,IAAI,GACtF,IAAI,CAEN;AAgeD,OAAO,EAAE,uBAAuB,EAAE,MAAM,0BAA0B,CAAC;AAInE,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;8BA3SrC,OAAO,KAAG,OAAO,CAAC,QAAQ,CAAC;AA6ShD,wBAAiE"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/server/rsc-entry/index.ts"],"names":[],"mappings":"AA4DA,OAAO,EAKL,KAAK,mBAAmB,EACzB,MAAM,cAAc,CAAC;AAoCtB;;;;;;;;;GASG;AACH,wBAAgB,0BAA0B,CACxC,OAAO,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,eAAe,CAAC,EAAE,mBAAmB,EAAE,KAAK,IAAI,GACtF,IAAI,CAEN;AAED;;;;;;;;;GASG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,GAAG,IAAI,CAE5E;AAqiBD,OAAO,EAAE,uBAAuB,EAAE,MAAM,0BAA0B,CAAC;AAInE,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;8BA3SrC,OAAO,KAAG,OAAO,CAAC,QAAQ,CAAC;AA6ShD,wBAAiE"}
@@ -28,6 +28,16 @@ export interface RenderSignals {
28
28
  error: unknown;
29
29
  status: number;
30
30
  } | null;
31
+ /**
32
+ * The last unhandled error seen by RSC onError that isn't a signal.
33
+ * Used as a fallback when SSR fails (SsrStreamError) but no structured
34
+ * signal was captured — provides the original error for the error page
35
+ * instead of relying on SsrStreamError.cause extraction.
36
+ *
37
+ * NOT used for page-level error detection (that would break Suspense
38
+ * error isolation). Only consumed when SSR actually fails.
39
+ */
40
+ lastUnhandledError: unknown | null;
31
41
  /** Callback fired when a redirect or deny signal is captured in onError. */
32
42
  onSignal?: () => void;
33
43
  }
@@ -1 +1 @@
1
- {"version":3,"file":"rsc-stream.d.ts","sourceRoot":"","sources":["../../../src/server/rsc-entry/rsc-stream.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAOH,OAAO,EAAE,UAAU,EAAE,cAAc,EAAe,MAAM,kBAAkB,CAAC;AAG3E,OAAO,EAIL,KAAK,mBAAmB,EACzB,MAAM,cAAc,CAAC;AAItB;;;;;;;;;GASG;AACH,MAAM,WAAW,aAAa;IAC5B,UAAU,EAAE,UAAU,GAAG,IAAI,CAAC;IAC9B,cAAc,EAAE,cAAc,GAAG,IAAI,CAAC;IACtC,WAAW,EAAE;QAAE,KAAK,EAAE,OAAO,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IACvD,4EAA4E;IAC5E,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;CACvB;AAED,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,cAAc,CAAC,UAAU,CAAC,GAAG,SAAS,CAAC;IAClD,OAAO,EAAE,aAAa,CAAC;IACvB,2EAA2E;IAC3E,kBAAkB,CAAC,EAAE,MAAM,mBAAmB,EAAE,CAAC;CAClD;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,KAAK,CAAC,YAAY,EAAE,GAAG,EAAE,OAAO,GAAG,eAAe,CAsJ1F"}
1
+ {"version":3,"file":"rsc-stream.d.ts","sourceRoot":"","sources":["../../../src/server/rsc-entry/rsc-stream.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAOH,OAAO,EAAE,UAAU,EAAE,cAAc,EAAe,MAAM,kBAAkB,CAAC;AAG3E,OAAO,EAIL,KAAK,mBAAmB,EACzB,MAAM,cAAc,CAAC;AAItB;;;;;;;;;GASG;AACH,MAAM,WAAW,aAAa;IAC5B,UAAU,EAAE,UAAU,GAAG,IAAI,CAAC;IAC9B,cAAc,EAAE,cAAc,GAAG,IAAI,CAAC;IACtC,WAAW,EAAE;QAAE,KAAK,EAAE,OAAO,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IACvD;;;;;;;;OAQG;IACH,kBAAkB,EAAE,OAAO,GAAG,IAAI,CAAC;IACnC,4EAA4E;IAC5E,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;CACvB;AAED,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,cAAc,CAAC,UAAU,CAAC,GAAG,SAAS,CAAC;IAClD,OAAO,EAAE,aAAa,CAAC;IACvB,2EAA2E;IAC3E,kBAAkB,CAAC,EAAE,MAAM,mBAAmB,EAAE,CAAC;CAClD;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,KAAK,CAAC,YAAY,EAAE,GAAG,EAAE,OAAO,GAAG,eAAe,CA4J1F"}
@@ -1 +1 @@
1
- {"version":3,"file":"ssr-renderer.d.ts","sourceRoot":"","sources":["../../../src/server/rsc-entry/ssr-renderer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAIlE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAEjD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAC;AACxE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAW/D,OAAO,EAAmB,KAAK,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAE5E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAarD;;;;;;;;GAQG;AACH,eAAO,MAAM,gCAAgC,EAAE;IAAE,KAAK,EAAE,MAAM,CAAA;CAAiB,CAAC;AAEhF,UAAU,gBAAgB;IACxB,GAAG,EAAE,OAAO,CAAC;IACb,SAAS,EAAE,cAAc,CAAC,UAAU,CAAC,CAAC;IACtC,OAAO,EAAE,aAAa,CAAC;IACvB,QAAQ,EAAE,mBAAmB,EAAE,CAAC;IAChC,gBAAgB,EAAE,oBAAoB,EAAE,CAAC;IACzC,KAAK,EAAE,UAAU,CAAC;IAClB,eAAe,EAAE,OAAO,CAAC;IACzB,eAAe,EAAE,qBAAqB,CAAC;IACvC,gBAAgB,EAAE,OAAO,CAAC;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,gBAAgB,EAAE,MAAM,CAAC;IACzB,wDAAwD;IACxD,WAAW,CAAC,EAAE,eAAe,CAAC;CAC/B;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAsB,iBAAiB,CAAC,IAAI,EAAE,gBAAgB,GAAG,OAAO,CAAC,QAAQ,CAAC,CA8QjF"}
1
+ {"version":3,"file":"ssr-renderer.d.ts","sourceRoot":"","sources":["../../../src/server/rsc-entry/ssr-renderer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAIlE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAEjD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAC;AACxE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAW/D,OAAO,EAAmB,KAAK,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAE5E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAarD;;;;;;;;GAQG;AACH,eAAO,MAAM,gCAAgC,EAAE;IAAE,KAAK,EAAE,MAAM,CAAA;CAAiB,CAAC;AAEhF,UAAU,gBAAgB;IACxB,GAAG,EAAE,OAAO,CAAC;IACb,SAAS,EAAE,cAAc,CAAC,UAAU,CAAC,CAAC;IACtC,OAAO,EAAE,aAAa,CAAC;IACvB,QAAQ,EAAE,mBAAmB,EAAE,CAAC;IAChC,gBAAgB,EAAE,oBAAoB,EAAE,CAAC;IACzC,KAAK,EAAE,UAAU,CAAC;IAClB,eAAe,EAAE,OAAO,CAAC;IACzB,eAAe,EAAE,qBAAqB,CAAC;IACvC,gBAAgB,EAAE,OAAO,CAAC;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,gBAAgB,EAAE,MAAM,CAAC;IACzB,wDAAwD;IACxD,WAAW,CAAC,EAAE,eAAe,CAAC;CAC/B;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAsB,iBAAiB,CAAC,IAAI,EAAE,gBAAgB,GAAG,OAAO,CAAC,QAAQ,CAAC,CAiRjF"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@timber-js/app",
3
- "version": "0.2.0-alpha.84",
3
+ "version": "0.2.0-alpha.86",
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",
@@ -110,11 +110,6 @@
110
110
  "publishConfig": {
111
111
  "access": "public"
112
112
  },
113
- "scripts": {
114
- "build": "vite build --config vite.lib.config.ts && tsc --emitDeclarationOnly --project tsconfig.json --outDir dist",
115
- "typecheck": "tsgo --noEmit",
116
- "prepublishOnly": "pnpm run build"
117
- },
118
113
  "dependencies": {
119
114
  "@opentelemetry/api": "^1.9.1",
120
115
  "@opentelemetry/context-async-hooks": "^2.6.1",
@@ -157,5 +152,9 @@
157
152
  },
158
153
  "engines": {
159
154
  "node": ">=22.12.0"
155
+ },
156
+ "scripts": {
157
+ "build": "vite build --config vite.lib.config.ts && tsc --emitDeclarationOnly --project tsconfig.json --outDir dist",
158
+ "typecheck": "tsgo --noEmit"
160
159
  }
161
- }
160
+ }
package/src/cli.ts CHANGED
File without changes
@@ -90,16 +90,21 @@ export function useActionState<TData>(
90
90
  * </button>
91
91
  * ```
92
92
  */
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] {
93
+ export function useFormAction<TData = unknown, TInput = unknown>(
94
+ action: ActionFn<TData, TInput> | ((input: TInput) => Promise<ActionResult<TData>>)
95
+ ): [
96
+ (
97
+ ...args: undefined extends TInput ? [input?: InputHint<TInput>] : [input: InputHint<TInput>]
98
+ ) => Promise<ActionResult<TData>>,
99
+ boolean,
100
+ ] {
96
101
  const [isPending, startTransition] = useTransition();
97
102
 
98
- const execute = (input: InputHint<TInput>): Promise<ActionResult<TData>> => {
103
+ const execute = (input?: InputHint<TInput>): Promise<ActionResult<TData>> => {
99
104
  return new Promise((resolve) => {
100
105
  startTransition(async () => {
101
106
  const result = await (action as (input: InputHint<TInput>) => Promise<ActionResult<TData>>)(
102
- input
107
+ input as InputHint<TInput>
103
108
  );
104
109
  resolve(result);
105
110
  });
@@ -0,0 +1,299 @@
1
+ /**
2
+ * Config validation — validates timber.config.ts at startup.
3
+ *
4
+ * Runs in the plugin's configResolved hook (once, at startup/build).
5
+ * Each check produces a clear error message with the invalid value,
6
+ * what's expected, and how to fix it.
7
+ *
8
+ * Design doc: 18-build-system.md
9
+ */
10
+
11
+ import type { TimberUserConfig } from './config-types.js';
12
+
13
+ // ─── Types ──────────────────────────────────────────────────────────────────
14
+
15
+ export interface ConfigError {
16
+ field: string;
17
+ message: string;
18
+ value?: unknown;
19
+ suggestion?: string;
20
+ }
21
+
22
+ // ─── Validation ─────────────────────────────────────────────────────────────
23
+
24
+ /**
25
+ * Validate a TimberUserConfig object.
26
+ *
27
+ * Returns an array of errors. Empty array means the config is valid.
28
+ * Does not throw — the caller decides how to surface errors.
29
+ */
30
+ export function validateConfig(config: TimberUserConfig): ConfigError[] {
31
+ const errors: ConfigError[] = [];
32
+
33
+ // output
34
+ if (config.output !== undefined && config.output !== 'server' && config.output !== 'static') {
35
+ errors.push({
36
+ field: 'output',
37
+ message: `Invalid output mode: "${String(config.output)}". Must be "server" or "static".`,
38
+ value: config.output,
39
+ suggestion: 'Use output: "server" (default) or output: "static" for static site generation.',
40
+ });
41
+ }
42
+
43
+ // pageExtensions
44
+ if (config.pageExtensions !== undefined) {
45
+ if (!Array.isArray(config.pageExtensions)) {
46
+ errors.push({
47
+ field: 'pageExtensions',
48
+ message: 'pageExtensions must be an array of strings.',
49
+ value: config.pageExtensions,
50
+ suggestion: 'Example: pageExtensions: ["tsx", "ts", "jsx", "js", "mdx"]',
51
+ });
52
+ } else {
53
+ for (const ext of config.pageExtensions) {
54
+ if (typeof ext !== 'string') {
55
+ errors.push({
56
+ field: 'pageExtensions',
57
+ message: `pageExtensions contains a non-string value: ${JSON.stringify(ext)}`,
58
+ value: ext,
59
+ });
60
+ break;
61
+ }
62
+ if (ext.startsWith('.')) {
63
+ errors.push({
64
+ field: 'pageExtensions',
65
+ message: `pageExtensions should not include the leading dot: "${ext}"`,
66
+ value: ext,
67
+ suggestion: `Use "${ext.slice(1)}" instead of "${ext}".`,
68
+ });
69
+ break;
70
+ }
71
+ }
72
+ }
73
+ }
74
+
75
+ // slowRequestMs
76
+ if (config.slowRequestMs !== undefined) {
77
+ if (typeof config.slowRequestMs !== 'number' || config.slowRequestMs < 0) {
78
+ errors.push({
79
+ field: 'slowRequestMs',
80
+ message: `slowRequestMs must be a non-negative number (got ${JSON.stringify(config.slowRequestMs)}).`,
81
+ value: config.slowRequestMs,
82
+ suggestion: 'Use slowRequestMs: 3000 (default) or slowRequestMs: 0 to disable.',
83
+ });
84
+ }
85
+ }
86
+
87
+ // renderTimeoutMs
88
+ if (config.renderTimeoutMs !== undefined) {
89
+ if (typeof config.renderTimeoutMs !== 'number' || config.renderTimeoutMs < 0) {
90
+ errors.push({
91
+ field: 'renderTimeoutMs',
92
+ message: `renderTimeoutMs must be a non-negative number (got ${JSON.stringify(config.renderTimeoutMs)}).`,
93
+ value: config.renderTimeoutMs,
94
+ suggestion: 'Use renderTimeoutMs: 30000 (default) or renderTimeoutMs: 0 to disable.',
95
+ });
96
+ }
97
+ }
98
+
99
+ // serverTiming
100
+ if (config.serverTiming !== undefined) {
101
+ if (
102
+ config.serverTiming !== 'detailed' &&
103
+ config.serverTiming !== 'total' &&
104
+ config.serverTiming !== false
105
+ ) {
106
+ errors.push({
107
+ field: 'serverTiming',
108
+ message: `Invalid serverTiming value: ${JSON.stringify(config.serverTiming)}.`,
109
+ value: config.serverTiming,
110
+ suggestion: 'Use "detailed", "total", or false.',
111
+ });
112
+ }
113
+ }
114
+
115
+ // devBrowserLogs
116
+ if (config.devBrowserLogs !== undefined) {
117
+ const valid = ['error', 'warn', 'info', 'none'];
118
+ if (!valid.includes(config.devBrowserLogs)) {
119
+ errors.push({
120
+ field: 'devBrowserLogs',
121
+ message: `Invalid devBrowserLogs value: ${JSON.stringify(config.devBrowserLogs)}.`,
122
+ value: config.devBrowserLogs,
123
+ suggestion: `Use one of: ${valid.map((v) => `"${v}"`).join(', ')}.`,
124
+ });
125
+ }
126
+ }
127
+
128
+ // sitemap
129
+ if (config.sitemap != null && typeof config.sitemap === 'object') {
130
+ if (config.sitemap.enabled && !config.sitemap.baseUrl) {
131
+ errors.push({
132
+ field: 'sitemap.baseUrl',
133
+ message: 'sitemap.baseUrl is required when sitemap is enabled.',
134
+ suggestion: 'Add sitemap: { enabled: true, baseUrl: "https://example.com" }.',
135
+ });
136
+ }
137
+ if (
138
+ config.sitemap.defaultPriority !== undefined &&
139
+ (config.sitemap.defaultPriority < 0 || config.sitemap.defaultPriority > 1)
140
+ ) {
141
+ errors.push({
142
+ field: 'sitemap.defaultPriority',
143
+ message: `sitemap.defaultPriority must be between 0.0 and 1.0 (got ${config.sitemap.defaultPriority}).`,
144
+ value: config.sitemap.defaultPriority,
145
+ });
146
+ }
147
+ }
148
+
149
+ // Unknown top-level keys
150
+ const knownKeys = new Set([
151
+ 'output',
152
+ 'debug',
153
+ 'clientJavascript',
154
+ 'adapter',
155
+ 'cacheHandler',
156
+ 'allowedOrigins',
157
+ 'csrf',
158
+ 'limits',
159
+ 'pageExtensions',
160
+ 'slowRequestMs',
161
+ 'renderTimeoutMs',
162
+ 'devBrowserLogs',
163
+ 'dev',
164
+ 'serverTiming',
165
+ 'appDir',
166
+ 'mdx',
167
+ 'actionEncryption',
168
+ 'reactCompiler',
169
+ 'sitemap',
170
+ 'buildDir',
171
+ 'topLoader',
172
+ ]);
173
+
174
+ for (const key of Object.keys(config)) {
175
+ if (!knownKeys.has(key)) {
176
+ errors.push({
177
+ field: key,
178
+ message: `Unknown config option: "${key}".`,
179
+ suggestion: `Check for typos. Known options: ${[...knownKeys].sort().join(', ')}.`,
180
+ });
181
+ }
182
+ }
183
+
184
+ return errors;
185
+ }
186
+
187
+ // ─── Formatting ─────────────────────────────────────────────────────────────
188
+
189
+ const RED = '\x1b[31m';
190
+ const BOLD = '\x1b[1m';
191
+ const DIM = '\x1b[2m';
192
+ const RESET = '\x1b[0m';
193
+
194
+ /**
195
+ * Format config errors for terminal output.
196
+ */
197
+ export function formatConfigErrors(errors: ConfigError[]): string {
198
+ if (errors.length === 0) return '';
199
+
200
+ const lines: string[] = [];
201
+ lines.push(
202
+ `${RED}${BOLD}[timber]${RESET} ${RED}${errors.length} config error${errors.length !== 1 ? 's' : ''} in timber.config.ts:${RESET}`
203
+ );
204
+
205
+ for (const err of errors) {
206
+ lines.push('');
207
+ lines.push(` ${RED}✗${RESET} ${BOLD}${err.field}${RESET}: ${err.message}`);
208
+ if (err.suggestion) {
209
+ lines.push(` ${DIM}${err.suggestion}${RESET}`);
210
+ }
211
+ }
212
+
213
+ return lines.join('\n');
214
+ }
215
+
216
+ // ─── Virtual Module Name Mapping ────────────────────────────────────────────
217
+
218
+ const VIRTUAL_MODULE_NAMES: Record<string, string> = {
219
+ 'virtual:timber-rsc-entry': 'RSC entry (server component handler)',
220
+ 'virtual:timber-ssr-entry': 'SSR entry (HTML renderer)',
221
+ 'virtual:timber-browser-entry': 'Browser entry (client hydration)',
222
+ 'virtual:timber-config': 'Runtime config (timber.config.ts)',
223
+ 'virtual:timber-route-manifest': 'Route manifest (app/ file tree)',
224
+ 'virtual:timber-instrumentation': 'Instrumentation (instrumentation.ts)',
225
+ 'virtual:timber-cache-handler': 'Cache handler (cacheHandler config)',
226
+ 'virtual:timber-build-manifest': 'Build manifest (asset mapping)',
227
+ };
228
+
229
+ /**
230
+ * Add timber-specific context to an error message that references virtual modules.
231
+ *
232
+ * If the error message contains a `virtual:timber-*` ID, appends a
233
+ * human-readable explanation. Does not replace the original message.
234
+ */
235
+ export function addVirtualModuleContext(errorMessage: string): string {
236
+ for (const [id, name] of Object.entries(VIRTUAL_MODULE_NAMES)) {
237
+ if (errorMessage.includes(id)) {
238
+ return `${errorMessage}\n\n [timber] This error references "${id}" — timber's ${name}.\n This is an internal module. The issue is likely in your app code or timber configuration.`;
239
+ }
240
+ }
241
+ return errorMessage;
242
+ }
243
+
244
+ // ─── Peer Dependency Check ──────────────────────────────────────────────────
245
+
246
+ interface PeerDepResult {
247
+ name: string;
248
+ status: 'ok' | 'missing';
249
+ }
250
+
251
+ /**
252
+ * Required (non-optional) peer dependencies that must be installed.
253
+ */
254
+ const REQUIRED_PEERS = ['react', 'react-dom', '@vitejs/plugin-react', '@vitejs/plugin-rsc'];
255
+
256
+ /**
257
+ * Check that required peer dependencies are installed.
258
+ *
259
+ * Uses require.resolve with a try/catch — no fs scanning.
260
+ * Returns only the missing packages.
261
+ */
262
+ export function checkPeerDependencies(projectRoot: string): PeerDepResult[] {
263
+ const results: PeerDepResult[] = [];
264
+
265
+ for (const name of REQUIRED_PEERS) {
266
+ try {
267
+ // Use createRequire from the project root to resolve as the user would
268
+ const { createRequire } = require('node:module');
269
+ const userRequire = createRequire(`${projectRoot}/package.json`);
270
+ userRequire.resolve(name);
271
+ results.push({ name, status: 'ok' });
272
+ } catch {
273
+ results.push({ name, status: 'missing' });
274
+ }
275
+ }
276
+
277
+ return results;
278
+ }
279
+
280
+ /**
281
+ * Format missing peer dependencies as an actionable warning.
282
+ */
283
+ export function formatMissingPeers(results: PeerDepResult[]): string {
284
+ const missing = results.filter((r) => r.status === 'missing');
285
+ if (missing.length === 0) return '';
286
+
287
+ const names = missing.map((r) => r.name);
288
+ const lines: string[] = [];
289
+ lines.push(`${RED}${BOLD}[timber]${RESET} ${RED}Missing required dependencies:${RESET}`);
290
+ lines.push('');
291
+ for (const name of names) {
292
+ lines.push(` ${RED}✗${RESET} ${name}`);
293
+ }
294
+ lines.push('');
295
+ lines.push(` ${DIM}Install with:${RESET}`);
296
+ lines.push(` ${BOLD}pnpm add ${names.join(' ')}${RESET}`);
297
+
298
+ return lines.join('\n');
299
+ }
package/src/index.ts CHANGED
@@ -366,6 +366,23 @@ export function timber(config?: TimberUserConfig): PluginOption[] {
366
366
  // Start the overall dev server setup timer — ends in timber-dev-server
367
367
  ctx.timer.start('dev-server-setup');
368
368
  }
369
+
370
+ // Validate config and check peer dependencies at startup.
371
+ // Errors are logged to stderr but don't block startup — Vite may
372
+ // still work partially, and the errors guide the user to fix things.
373
+ const { validateConfig, formatConfigErrors, checkPeerDependencies, formatMissingPeers } =
374
+ require('./config-validation.js') as typeof import('./config-validation.js');
375
+
376
+ const configErrors = validateConfig(ctx.config);
377
+ if (configErrors.length > 0) {
378
+ process.stderr.write(`${formatConfigErrors(configErrors)}\n\n`);
379
+ }
380
+
381
+ const peerResults = checkPeerDependencies(ctx.root);
382
+ const peerWarning = formatMissingPeers(peerResults);
383
+ if (peerWarning) {
384
+ process.stderr.write(`${peerWarning}\n\n`);
385
+ }
369
386
  },
370
387
  };
371
388