@timber-js/app 0.2.0-alpha.85 → 0.2.0-alpha.87

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.
@@ -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;AAggBD,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.85",
3
+ "version": "0.2.0-alpha.87",
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,6 +110,11 @@
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
+ },
113
118
  "dependencies": {
114
119
  "@opentelemetry/api": "^1.9.1",
115
120
  "@opentelemetry/context-async-hooks": "^2.6.1",
@@ -152,9 +157,5 @@
152
157
  },
153
158
  "engines": {
154
159
  "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"
159
160
  }
160
- }
161
+ }
package/src/cli.ts CHANGED
File without changes
@@ -234,6 +234,49 @@ export function formatRscDebugContext(components: RscDebugComponentInfo[]): stri
234
234
  return lines.join('\n');
235
235
  }
236
236
 
237
+ // ─── Module Runner Offset ───────────────────────────────────────────────────
238
+
239
+ /**
240
+ * Dynamically compute the line offset that Vite's module runner adds
241
+ * when wrapping modules in an async function.
242
+ *
243
+ * Vite's `calculateOffsetOnce()` uses the same technique: create a new
244
+ * AsyncFunction, throw from line 1, and check where the engine reports
245
+ * the error. The difference between the reported line and 1 is the offset.
246
+ *
247
+ * This is engine-dependent (currently 2 on Node 18-22) and could change
248
+ * in future Node.js or V8 versions. Computing it at runtime ensures we
249
+ * always match the actual behavior.
250
+ */
251
+ let _cachedOffset: number | null = null;
252
+
253
+ export function calculateModuleRunnerOffset(): number {
254
+ if (_cachedOffset !== null) return _cachedOffset;
255
+
256
+ try {
257
+ // Parse the AsyncFunction source to count wrapper lines before the body.
258
+ // AsyncFunction.toString() returns something like:
259
+ // "async function anonymous(\n) {\nBODY\n}"
260
+ // The number of newlines before BODY is the offset Vite's module runner adds.
261
+ // eslint-disable-next-line no-new-func -- intentional: mirrors Vite's technique
262
+ const AsyncFunction = async function () {}.constructor as typeof Function;
263
+ const wrapper = new AsyncFunction('BODY');
264
+ const src = wrapper.toString();
265
+ const bodyIndex = src.indexOf('BODY');
266
+ if (bodyIndex === -1) {
267
+ _cachedOffset = 2; // fallback
268
+ return _cachedOffset;
269
+ }
270
+ const beforeBody = src.slice(0, bodyIndex);
271
+ const newlineCount = (beforeBody.match(/\n/g) || []).length;
272
+ _cachedOffset = newlineCount;
273
+ } catch {
274
+ _cachedOffset = 2; // fallback to known-good value
275
+ }
276
+
277
+ return _cachedOffset;
278
+ }
279
+
237
280
  // ─── Stack Trace Source-Mapping ──────────────────────────────────────────────
238
281
 
239
282
  /**
@@ -310,9 +353,10 @@ function rewriteStacktrace(
310
353
  const rawSourceMap = mod?.transformResult?.map;
311
354
  if (!rawSourceMap) return input;
312
355
 
313
- // Vite's module runner adds a 2-line offset for the async wrapper.
314
- // This matches Vite's internal `calculateOffsetOnce()` behavior.
315
- const OFFSET = 2;
356
+ // Vite's module runner wraps each module in an async function,
357
+ // adding N lines before the module body. The offset is computed
358
+ // dynamically to match the actual engine behavior (see TIM-783).
359
+ const OFFSET = calculateModuleRunnerOffset();
316
360
  const origLine = Number(lineStr) - OFFSET;
317
361
  const origCol = Number(colStr) - 1;
318
362
  if (origLine <= 0 || origCol < 0) return input;
@@ -344,6 +388,26 @@ function rewriteStacktrace(
344
388
  return { stack: result, changed };
345
389
  }
346
390
 
391
+ // ─── Error Source-Mapping (exported for use by fallback-error.ts) ────────────
392
+
393
+ /**
394
+ * Source-map an error's stack trace using the Vite dev server's module graph.
395
+ *
396
+ * Exported so that the fallback error renderer (fallback-error.ts) can
397
+ * source-map errors before rendering the dev error page. Without this,
398
+ * the dev error page shows transpiled positions (e.g. page.tsx:1:1 instead
399
+ * of page.tsx:4:9).
400
+ *
401
+ * See TIM-811.
402
+ */
403
+ export function fixErrorStacktrace(server: ViteDevServer, error: Error, phase?: ErrorPhase): void {
404
+ fixStacktraceForEnvironment(
405
+ server,
406
+ error,
407
+ phase ?? classifyErrorPhase(error, server.config.root)
408
+ );
409
+ }
410
+
347
411
  // ─── Overlay Integration ────────────────────────────────────────────────────
348
412
 
349
413
  /**
@@ -20,6 +20,7 @@ import { setViteServer } from '../server/dev-warnings.js';
20
20
  import {
21
21
  sendErrorToOverlay,
22
22
  classifyErrorPhase,
23
+ fixErrorStacktrace,
23
24
  parseFirstAppFrame,
24
25
  type ErrorPhase,
25
26
  } from './dev-error-overlay.js';
@@ -213,6 +214,18 @@ function createTimberMiddleware(server: ViteDevServer, projectRoot: string) {
213
214
  );
214
215
  });
215
216
  }
217
+
218
+ // Wire source-map handler so error pages show original positions.
219
+ // fixErrorStacktrace rewrites the error's stack trace in-place using
220
+ // the Vite dev server's module graph. See TIM-811.
221
+ const setSourceMap = rscModule.setDevSourceMapHandler as
222
+ | ((fn: (error: Error) => void) => void)
223
+ | undefined;
224
+ if (typeof setSourceMap === 'function') {
225
+ setSourceMap((error: Error) => {
226
+ fixErrorStacktrace(server, error);
227
+ });
228
+ }
216
229
  } catch (error) {
217
230
  // Module transform error — syntax error, missing import, etc.
218
231
  // Vite may already show its own overlay for these, but we still
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Dev-only error source-mapping bridge.
3
+ *
4
+ * Stores a callback that rewrites an error's stack trace using the Vite
5
+ * dev server's module graph. Set by the RSC entry on startup (via
6
+ * setDevSourceMapHandler), consumed by error renderers before generating
7
+ * error pages.
8
+ *
9
+ * In production, the callback is never set — all calls are no-ops.
10
+ *
11
+ * See TIM-811.
12
+ */
13
+
14
+ let _sourceMapError: ((error: Error) => void) | undefined;
15
+
16
+ /**
17
+ * Set the source-map callback. Called once during dev server initialization.
18
+ */
19
+ export function setSourceMapCallback(fn: (error: Error) => void): void {
20
+ _sourceMapError = fn;
21
+ }
22
+
23
+ /**
24
+ * Source-map an error's stack trace in-place if the callback is available.
25
+ * No-op in production or before the dev server initializes.
26
+ */
27
+ export function sourceMapError(error: unknown): void {
28
+ if (_sourceMapError && error instanceof Error) {
29
+ _sourceMapError(error);
30
+ }
31
+ }
@@ -17,6 +17,7 @@ import type { LayoutEntry } from './deny-renderer.js';
17
17
  import type { GlobalErrorFile } from './rsc-entry/error-renderer.js';
18
18
  import { logRenderError } from './logger.js';
19
19
  import { loadModule } from './safe-load.js';
20
+ import { sourceMapError } from './dev-source-map.js';
20
21
 
21
22
  /**
22
23
  * Render a fallback error page when the render pipeline throws.
@@ -34,6 +35,10 @@ export async function renderFallbackError(
34
35
  globalError?: GlobalErrorFile,
35
36
  projectRoot?: string
36
37
  ): Promise<Response> {
38
+ // Source-map the error's stack trace in dev mode so the error page
39
+ // shows original source positions. See TIM-811.
40
+ sourceMapError(error);
41
+
37
42
  if (isDev) {
38
43
  return renderDevErrorPage(error, projectRoot);
39
44
  }
@@ -168,6 +168,7 @@ export interface PipelineConfig {
168
168
  * Undefined in production — zero overhead.
169
169
  */
170
170
  onPipelineError?: (error: Error, phase: string) => void;
171
+
171
172
  /**
172
173
  * Fallback error renderer — called when a catastrophic error escapes the
173
174
  * render phase. Produces an HTML Response instead of a bare empty 500.
@@ -184,6 +185,19 @@ export interface PipelineConfig {
184
185
  req: Request,
185
186
  responseHeaders: Headers
186
187
  ) => Response | Promise<Response>;
188
+ /**
189
+ * Fallback deny page renderer — called when a DenySignal escapes from
190
+ * middleware or the render phase. Renders the appropriate status-code
191
+ * page (403.tsx, 404.tsx, etc.) instead of returning a bare empty response.
192
+ *
193
+ * If this function throws, the pipeline falls back to a bare
194
+ * `new Response(null, { status: denyStatus })`.
195
+ */
196
+ renderDenyFallback?: (
197
+ deny: DenySignal,
198
+ req: Request,
199
+ responseHeaders: Headers
200
+ ) => Response | Promise<Response>;
187
201
  }
188
202
 
189
203
  // ─── Param Coercion ────────────────────────────────────────────────────────
@@ -624,9 +638,18 @@ export function createPipeline(config: PipelineConfig): (req: Request) => Promis
624
638
  applyCookieJar(responseHeaders);
625
639
  return buildRedirectResponse(error, req, responseHeaders);
626
640
  }
627
- // DenySignal from middleware → HTTP deny status
641
+ // DenySignal from middleware → render deny page with correct status code.
642
+ // Previously returned bare Response(null) — now renders 403.tsx etc.
628
643
  if (error instanceof DenySignal) {
629
- return new Response(null, { status: error.status });
644
+ applyCookieJar(responseHeaders);
645
+ if (config.renderDenyFallback) {
646
+ try {
647
+ return await config.renderDenyFallback(error, req, responseHeaders);
648
+ } catch {
649
+ // Deny page rendering failed — fall through to bare response
650
+ }
651
+ }
652
+ return new Response(null, { status: error.status, headers: responseHeaders });
630
653
  }
631
654
  // Middleware throw → HTTP 500 (middleware runs before rendering,
632
655
  // no error boundary to catch it)
@@ -655,9 +678,16 @@ export function createPipeline(config: PipelineConfig): (req: Request) => Promis
655
678
  return response;
656
679
  } catch (error) {
657
680
  // DenySignal leaked from render (e.g. notFound() in metadata()).
658
- // Return the deny status code instead of 500.
681
+ // Render the deny page with the correct status code.
659
682
  if (error instanceof DenySignal) {
660
- return new Response(null, { status: error.status });
683
+ if (config.renderDenyFallback) {
684
+ try {
685
+ return await config.renderDenyFallback(error, req, responseHeaders);
686
+ } catch {
687
+ // Deny page rendering failed — fall through to bare response
688
+ }
689
+ }
690
+ return new Response(null, { status: error.status, headers: responseHeaders });
661
691
  }
662
692
  // RedirectSignal leaked from render — honour the redirect
663
693
  if (error instanceof RedirectSignal) {
@@ -32,6 +32,7 @@ import { isDevMode } from '../debug.js';
32
32
  import { ErrorReconstituter } from '../../client/error-reconstituter.js';
33
33
  import type { SerializableError } from '../../client/error-reconstituter.js';
34
34
  import { loadModule } from '../safe-load.js';
35
+ import { sourceMapError } from '../dev-source-map.js';
35
36
 
36
37
  /**
37
38
  * A manifest file reference with lazy import and path.
@@ -176,6 +177,10 @@ export async function renderErrorPage(
176
177
  clientBootstrap: ClientBootstrapConfig,
177
178
  globalError?: GlobalErrorFile
178
179
  ): Promise<Response> {
180
+ // Source-map the error's stack trace in dev mode so the error page
181
+ // shows original source positions instead of transpiled/bundled ones.
182
+ // See TIM-811.
183
+ sourceMapError(error);
179
184
  // Walk segments from leaf to root to find the error component
180
185
  const resolution = await resolveErrorFile(status, segments);
181
186
 
@@ -54,6 +54,8 @@ import { initDevTracing } from '../tracing.js';
54
54
 
55
55
  import { renderFallbackError as renderFallback } from '../fallback-error.js';
56
56
  import { loadInstrumentation } from '../instrumentation.js';
57
+ import { loadModule } from '../safe-load.js';
58
+ import { logRenderError } from '../logger.js';
57
59
  import { handleApiRoute } from './api-handler.js';
58
60
  import { renderErrorPage, renderNoMatchPage } from './error-renderer.js';
59
61
  import {
@@ -69,6 +71,7 @@ import { renderRscStream } from './rsc-stream.js';
69
71
  import { renderSsrResponse } from './ssr-renderer.js';
70
72
  import { callSsr } from './ssr-bridge.js';
71
73
  import { isDebug, isDevMode, setDebugFromConfig } from '../debug.js';
74
+ import { setSourceMapCallback } from '../dev-source-map.js';
72
75
  import { recordTiming } from '../server-timing.js';
73
76
  import { requestContextAls } from '../als-registry.js';
74
77
  import { createAutoSitemapHandler } from '../sitemap-handler.js';
@@ -113,6 +116,20 @@ export function setDevPipelineErrorHandler(
113
116
  _devPipelineErrorHandler = handler;
114
117
  }
115
118
 
119
+ /**
120
+ * Set the dev source-map handler.
121
+ *
122
+ * Called by the dev server after importing this module to wire Vite's
123
+ * module graph source-mapping into the error rendering pipeline. Errors
124
+ * rendered by renderErrorPage / renderFallbackError will have their
125
+ * stack traces rewritten to show original source positions.
126
+ *
127
+ * No-op in production.
128
+ */
129
+ export function setDevSourceMapHandler(handler: (error: Error) => void): void {
130
+ setSourceMapCallback(handler);
131
+ }
132
+
116
133
  // Dev-only: debug components getter is stored per-request in ALS
117
134
  // (RequestContextStore.debugComponentsGetter) to avoid cross-request
118
135
  // race conditions. See TIM-557.
@@ -295,6 +312,7 @@ async function createRequestHandler(manifest: typeof routeManifest, runtimeConfi
295
312
  }
296
313
  }
297
314
  : undefined,
315
+
298
316
  renderFallbackError: (error, req, responseHeaders) =>
299
317
  renderFallback(
300
318
  error,
@@ -310,6 +328,42 @@ async function createRequestHandler(manifest: typeof routeManifest, runtimeConfi
310
328
  // CWD differs from project root (e.g., monorepo custom root) (TIM-807).
311
329
  isDev ? manifest.root : undefined
312
330
  ),
331
+ renderDenyFallback: async (deny, req, responseHeaders) => {
332
+ // Render the deny page (403.tsx, 404.tsx, etc.) for DenySignals
333
+ // that escape from middleware or the render phase. Uses the root
334
+ // segment to resolve status-code files and layout wrapping.
335
+ const segments = [
336
+ manifest.root,
337
+ ] as unknown as import('../route-matcher.js').ManifestSegmentNode[];
338
+ const layoutComponents: LayoutEntry[] = [];
339
+ try {
340
+ if (manifest.root.layout) {
341
+ const mod = await loadModule(manifest.root.layout);
342
+ if (mod.default) {
343
+ layoutComponents.push({
344
+ component: mod.default as (...args: unknown[]) => unknown,
345
+ segment:
346
+ manifest.root as unknown as import('../route-matcher.js').ManifestSegmentNode,
347
+ });
348
+ }
349
+ }
350
+ } catch (layoutError) {
351
+ // Layout failed to load — proceed without it.
352
+ logRenderError({ method: req.method, path: new URL(req.url).pathname, error: layoutError });
353
+ }
354
+ const match = { segments: segments as never, segmentParams: {}, middlewareChain: [] };
355
+ return renderDenyPage(
356
+ deny,
357
+ segments,
358
+ layoutComponents,
359
+ req,
360
+ match,
361
+ responseHeaders,
362
+ clientBootstrap,
363
+ createDebugChannelSink,
364
+ callSsr
365
+ );
366
+ },
313
367
  // Auto-generated sitemap handler — enabled when sitemap.enabled is true
314
368
  // and no user-authored sitemap exists at the app root.
315
369
  // See design/16-metadata.md §"Auto-generated Sitemap"
@@ -41,6 +41,16 @@ export interface RenderSignals {
41
41
  denySignal: DenySignal | null;
42
42
  redirectSignal: RedirectSignal | null;
43
43
  renderError: { error: unknown; status: number } | null;
44
+ /**
45
+ * The last unhandled error seen by RSC onError that isn't a signal.
46
+ * Used as a fallback when SSR fails (SsrStreamError) but no structured
47
+ * signal was captured — provides the original error for the error page
48
+ * instead of relying on SsrStreamError.cause extraction.
49
+ *
50
+ * NOT used for page-level error detection (that would break Suspense
51
+ * error isolation). Only consumed when SSR actually fails.
52
+ */
53
+ lastUnhandledError: unknown | null;
44
54
  /** Callback fired when a redirect or deny signal is captured in onError. */
45
55
  onSignal?: () => void;
46
56
  }
@@ -68,6 +78,7 @@ export function renderRscStream(element: React.ReactElement, req: Request): RscS
68
78
  denySignal: null,
69
79
  redirectSignal: null,
70
80
  renderError: null,
81
+ lastUnhandledError: null,
71
82
  };
72
83
 
73
84
  let rscStream: ReadableStream<Uint8Array> | undefined;
@@ -150,6 +161,11 @@ export function renderRscStream(element: React.ReactElement, req: Request): RscS
150
161
  // Only track as renderError if no Suspense boundary contains it —
151
162
  // React will call onShellError for truly unrecoverable errors.
152
163
 
164
+ // Track the last unhandled error so the pipeline can use it
165
+ // if SSR fails (SsrStreamError). This is NOT used for page-level
166
+ // error detection — only as a fallback when SSR actually fails.
167
+ signals.lastUnhandledError = error;
168
+
153
169
  // Return a digest so React emits a per-row error in the Flight
154
170
  // stream instead of leaving the lazy reference unresolved.
155
171
  //
@@ -337,10 +337,13 @@ export async function renderSsrResponse(opts: SsrRenderOptions): Promise<Respons
337
337
  );
338
338
  }
339
339
  // No captured signal — unhandled error in the RSC stream.
340
- // Render the error page using the original error (cause).
341
- const cause = (ssrError as { cause?: unknown }).cause ?? ssrError;
340
+ // Use the lastUnhandledError from RSC onError if available (more
341
+ // reliable than extracting SsrStreamError.cause). Falls back to
342
+ // cause extraction for backward compatibility.
343
+ const originalError =
344
+ signals.lastUnhandledError ?? (ssrError as { cause?: unknown }).cause ?? ssrError;
342
345
  return renderErrorPage(
343
- cause,
346
+ originalError,
344
347
  500,
345
348
  segments,
346
349
  layoutComponents as LayoutEntry[],
package/LICENSE DELETED
@@ -1,8 +0,0 @@
1
- DONTFUCKINGUSE LICENSE
2
-
3
- Copyright (c) 2025 Daniel Saewitz
4
-
5
- This software may not be used, copied, modified, merged, published,
6
- distributed, sublicensed, or sold by anyone other than the copyright holder.
7
-
8
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND.