@timber-js/app 0.2.0-alpha.37 → 0.2.0-alpha.39

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 (68) hide show
  1. package/dist/adapters/nitro.d.ts.map +1 -1
  2. package/dist/adapters/nitro.js +27 -4
  3. package/dist/adapters/nitro.js.map +1 -1
  4. package/dist/cache/index.d.ts +5 -2
  5. package/dist/cache/index.d.ts.map +1 -1
  6. package/dist/cache/index.js +37 -8
  7. package/dist/cache/index.js.map +1 -1
  8. package/dist/cache/singleflight.d.ts +18 -1
  9. package/dist/cache/singleflight.d.ts.map +1 -1
  10. package/dist/cache/timber-cache.d.ts.map +1 -1
  11. package/dist/index.d.ts +12 -0
  12. package/dist/index.d.ts.map +1 -1
  13. package/dist/index.js +53 -4
  14. package/dist/index.js.map +1 -1
  15. package/dist/plugins/dev-error-overlay.d.ts +26 -1
  16. package/dist/plugins/dev-error-overlay.d.ts.map +1 -1
  17. package/dist/plugins/entries.d.ts.map +1 -1
  18. package/dist/server/action-handler.d.ts.map +1 -1
  19. package/dist/server/default-logger.d.ts +22 -0
  20. package/dist/server/default-logger.d.ts.map +1 -0
  21. package/dist/server/flush.d.ts.map +1 -1
  22. package/dist/server/html-injectors.d.ts +2 -2
  23. package/dist/server/html-injectors.d.ts.map +1 -1
  24. package/dist/server/index.d.ts +2 -0
  25. package/dist/server/index.d.ts.map +1 -1
  26. package/dist/server/index.js +135 -24
  27. package/dist/server/index.js.map +1 -1
  28. package/dist/server/logger.d.ts +24 -7
  29. package/dist/server/logger.d.ts.map +1 -1
  30. package/dist/server/node-stream-transforms.d.ts +13 -1
  31. package/dist/server/node-stream-transforms.d.ts.map +1 -1
  32. package/dist/server/render-timeout.d.ts +51 -0
  33. package/dist/server/render-timeout.d.ts.map +1 -0
  34. package/dist/server/route-handler.d.ts.map +1 -1
  35. package/dist/server/rsc-entry/helpers.d.ts +46 -3
  36. package/dist/server/rsc-entry/helpers.d.ts.map +1 -1
  37. package/dist/server/rsc-entry/index.d.ts +6 -1
  38. package/dist/server/rsc-entry/index.d.ts.map +1 -1
  39. package/dist/server/rsc-entry/rsc-payload.d.ts.map +1 -1
  40. package/dist/server/rsc-entry/rsc-stream.d.ts +3 -0
  41. package/dist/server/rsc-entry/rsc-stream.d.ts.map +1 -1
  42. package/dist/server/ssr-entry.d.ts.map +1 -1
  43. package/dist/server/ssr-render.d.ts +2 -0
  44. package/dist/server/ssr-render.d.ts.map +1 -1
  45. package/package.json +1 -1
  46. package/src/adapters/nitro.ts +27 -4
  47. package/src/cache/index.ts +5 -2
  48. package/src/cache/singleflight.ts +62 -4
  49. package/src/cache/timber-cache.ts +17 -16
  50. package/src/index.ts +12 -0
  51. package/src/plugins/dev-error-overlay.ts +70 -1
  52. package/src/plugins/dev-server.ts +38 -4
  53. package/src/plugins/entries.ts +1 -0
  54. package/src/server/action-handler.ts +3 -2
  55. package/src/server/default-logger.ts +95 -0
  56. package/src/server/flush.ts +2 -1
  57. package/src/server/html-injectors.ts +32 -7
  58. package/src/server/index.ts +4 -0
  59. package/src/server/logger.ts +38 -35
  60. package/src/server/node-stream-transforms.ts +51 -14
  61. package/src/server/render-timeout.ts +108 -0
  62. package/src/server/route-handler.ts +2 -1
  63. package/src/server/rsc-entry/helpers.ts +122 -3
  64. package/src/server/rsc-entry/index.ts +34 -4
  65. package/src/server/rsc-entry/rsc-payload.ts +11 -3
  66. package/src/server/rsc-entry/rsc-stream.ts +24 -3
  67. package/src/server/ssr-entry.ts +9 -2
  68. package/src/server/ssr-render.ts +105 -16
@@ -1,9 +1,9 @@
1
1
  /**
2
2
  * Logger — structured logging with environment-aware formatting.
3
3
  *
4
- * timber.js does not ship a logger. Users export any object with
5
- * info/warn/error/debug methods from instrumentation.ts and the framework
6
- * picks it up. Silent if no logger export is present.
4
+ * timber.js ships a DefaultLogger that writes human-readable lines to stderr
5
+ * in production. Users can export a custom logger from instrumentation.ts to
6
+ * replace it with pino, winston, or any TimberLogger-compatible object.
7
7
  *
8
8
  * See design/17-logging.md §"Production Logging"
9
9
  */
@@ -16,14 +16,15 @@ export interface TimberLogger {
16
16
  }
17
17
  /**
18
18
  * Set the user-provided logger. Called by the instrumentation loader
19
- * when it finds a `logger` export in instrumentation.ts.
19
+ * when it finds a `logger` export in instrumentation.ts. Replaces
20
+ * the DefaultLogger entirely.
20
21
  */
21
22
  export declare function setLogger(logger: TimberLogger): void;
22
23
  /**
23
- * Get the current logger, or null if none configured.
24
- * Framework-internal used at framework event points to emit structured logs.
24
+ * Get the current logger. Always non-null returns DefaultLogger when
25
+ * no custom logger is configured.
25
26
  */
26
- export declare function getLogger(): TimberLogger | null;
27
+ export declare function getLogger(): TimberLogger;
27
28
  /** Log a completed request. Level: info. */
28
29
  export declare function logRequestCompleted(data: {
29
30
  method: string;
@@ -69,6 +70,22 @@ export declare function logRenderError(data: {
69
70
  export declare function logProxyError(data: {
70
71
  error: unknown;
71
72
  }): void;
73
+ /** Log unhandled error in server action. Level: error. */
74
+ export declare function logActionError(data: {
75
+ method: string;
76
+ path: string;
77
+ error: unknown;
78
+ }): void;
79
+ /** Log unhandled error in route handler. Level: error. */
80
+ export declare function logRouteError(data: {
81
+ method: string;
82
+ path: string;
83
+ error: unknown;
84
+ }): void;
85
+ /** Log SSR streaming error (post-shell). Level: error. */
86
+ export declare function logStreamingError(data: {
87
+ error: unknown;
88
+ }): void;
72
89
  /** Log waitUntil() adapter missing (once at startup). Level: warn. */
73
90
  export declare function logWaitUntilUnsupported(): void;
74
91
  /** Log waitUntil() promise rejection. Level: warn. */
@@ -1 +1 @@
1
- {"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../src/server/logger.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAQH,6FAA6F;AAC7F,MAAM,WAAW,YAAY;IAC3B,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IACxD,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IACxD,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IACzD,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;CAC1D;AAMD;;;GAGG;AACH,wBAAgB,SAAS,CAAC,MAAM,EAAE,YAAY,GAAG,IAAI,CAEpD;AAED;;;GAGG;AACH,wBAAgB,SAAS,IAAI,YAAY,GAAG,IAAI,CAE/C;AAsBD,4CAA4C;AAC5C,wBAAgB,mBAAmB,CAAC,IAAI,EAAE;IACxC,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,uFAAuF;IACvF,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,GAAG,IAAI,CAEP;AAED,0CAA0C;AAC1C,wBAAgB,kBAAkB,CAAC,IAAI,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAE/E;AAED,+CAA+C;AAC/C,wBAAgB,cAAc,CAAC,IAAI,EAAE;IACnC,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,sFAAsF;IACtF,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,GAAG,IAAI,CAEP;AAED,kDAAkD;AAClD,wBAAgB,yBAAyB,CAAC,IAAI,EAAE;IAC9C,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;CAChB,GAAG,IAAI,CAEP;AAED,6DAA6D;AAC7D,wBAAgB,kBAAkB,CAAC,IAAI,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,GAAG,IAAI,CAM/F;AAED,sDAAsD;AACtD,wBAAgB,cAAc,CAAC,IAAI,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,GAAG,IAAI,CAQ3F;AAED,iDAAiD;AACjD,wBAAgB,aAAa,CAAC,IAAI,EAAE;IAAE,KAAK,EAAE,OAAO,CAAA;CAAE,GAAG,IAAI,CAM5D;AAED,sEAAsE;AACtE,wBAAgB,uBAAuB,IAAI,IAAI,CAE9C;AAED,sDAAsD;AACtD,wBAAgB,oBAAoB,CAAC,IAAI,EAAE;IAAE,KAAK,EAAE,OAAO,CAAA;CAAE,GAAG,IAAI,CAEnE;AAED,6DAA6D;AAC7D,wBAAgB,mBAAmB,CAAC,IAAI,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,GAAG,IAAI,CAEpF;AAED,oCAAoC;AACpC,wBAAgB,YAAY,CAAC,IAAI,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAE7D"}
1
+ {"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../src/server/logger.ts"],"names":[],"mappings":"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"}
@@ -42,7 +42,19 @@ export declare function createNodeHeadInjector(headHtml: string): Transform;
42
42
  * stream). We read from it using the Web API — this is the one bridge
43
43
  * point between Web Streams and Node.js streams in the pipeline.
44
44
  */
45
- export declare function createNodeFlightInjector(rscStream: ReadableStream<Uint8Array> | undefined): Transform;
45
+ /**
46
+ * Options for the Node.js flight injector.
47
+ */
48
+ export interface NodeFlightInjectorOptions {
49
+ /**
50
+ * Timeout in milliseconds for individual RSC stream reads.
51
+ * If a single `rscReader.read()` call does not resolve within
52
+ * this duration, the read is aborted and the stream errors with
53
+ * a RenderTimeoutError. Default: 30000 (30s).
54
+ */
55
+ renderTimeoutMs?: number;
56
+ }
57
+ export declare function createNodeFlightInjector(rscStream: ReadableStream<Uint8Array> | undefined, options?: NodeFlightInjectorOptions): Transform;
46
58
  /**
47
59
  * Node.js Transform that catches post-shell streaming errors.
48
60
  *
@@ -1 +1 @@
1
- {"version":3,"file":"node-stream-transforms.d.ts","sourceRoot":"","sources":["../../src/server/node-stream-transforms.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAgBxC;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,CA+ClE;AAID;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,wBAAwB,CACtC,SAAS,EAAE,cAAc,CAAC,UAAU,CAAC,GAAG,SAAS,GAChD,SAAS,CAkIX;AAOD;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,SAAS,CAwBtE;AAoBD;;;;;;;;;GASG;AACH,wBAAgB,wBAAwB,CACtC,cAAc,EAAE,OAAO,EACvB,eAAe,EAAE,OAAO,GACvB,SAAS,GAAG,IAAI,CA8BlB"}
1
+ {"version":3,"file":"node-stream-transforms.d.ts","sourceRoot":"","sources":["../../src/server/node-stream-transforms.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAkBxC;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,CA+ClE;AAID;;;;;;;;;;;;;;;;GAgBG;AACH;;GAEG;AACH,MAAM,WAAW,yBAAyB;IACxC;;;;;OAKG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,wBAAgB,wBAAwB,CACtC,SAAS,EAAE,cAAc,CAAC,UAAU,CAAC,GAAG,SAAS,EACjD,OAAO,CAAC,EAAE,yBAAyB,GAClC,SAAS,CAuJX;AAOD;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,SAAS,CAwBtE;AAoBD;;;;;;;;;GASG;AACH,wBAAgB,wBAAwB,CACtC,cAAc,EAAE,OAAO,EACvB,eAAe,EAAE,OAAO,GACvB,SAAS,GAAG,IAAI,CA8BlB"}
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Render timeout utilities for SSR streaming pipeline.
3
+ *
4
+ * Provides a RenderTimeoutError class and a helper to create
5
+ * timeout-guarded AbortSignals. Used to defend against hung RSC
6
+ * streams and infinite SSR renders.
7
+ *
8
+ * Design doc: 02-rendering-pipeline.md §"Streaming Constraints"
9
+ */
10
+ /**
11
+ * Error thrown when an SSR render or RSC stream read exceeds the
12
+ * configured timeout. Callers can check `instanceof RenderTimeoutError`
13
+ * to distinguish timeout from other errors and return a 504 or close
14
+ * the connection cleanly.
15
+ */
16
+ export declare class RenderTimeoutError extends Error {
17
+ readonly timeoutMs: number;
18
+ constructor(timeoutMs: number, context?: string);
19
+ }
20
+ /**
21
+ * Result of createRenderTimeout — an AbortSignal that fires after
22
+ * the given duration, plus a cancel function to clear the timer
23
+ * when the render completes normally.
24
+ */
25
+ export interface RenderTimeout {
26
+ /** AbortSignal that aborts after timeoutMs. */
27
+ signal: AbortSignal;
28
+ /** Cancel the timeout timer. Call this when the render completes. */
29
+ cancel: () => void;
30
+ }
31
+ /**
32
+ * Create a render timeout that aborts after the given duration.
33
+ *
34
+ * Returns an AbortSignal and a cancel function. The signal fires
35
+ * with a RenderTimeoutError as the abort reason after `timeoutMs`.
36
+ * Call `cancel()` when the render completes to prevent the timeout
37
+ * from firing.
38
+ *
39
+ * If an existing `parentSignal` is provided, the returned signal
40
+ * aborts when either the parent signal or the timeout fires —
41
+ * whichever comes first.
42
+ */
43
+ export declare function createRenderTimeout(timeoutMs: number, parentSignal?: AbortSignal): RenderTimeout;
44
+ /**
45
+ * Race a promise against a timeout. Rejects with RenderTimeoutError
46
+ * if the promise does not resolve within `timeoutMs`.
47
+ *
48
+ * Used to guard individual `rscReader.read()` calls inside pullLoop.
49
+ */
50
+ export declare function withTimeout<T>(promise: Promise<T>, timeoutMs: number, context?: string): Promise<T>;
51
+ //# sourceMappingURL=render-timeout.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"render-timeout.d.ts","sourceRoot":"","sources":["../../src/server/render-timeout.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH;;;;;GAKG;AACH,qBAAa,kBAAmB,SAAQ,KAAK;IAC3C,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;gBAEf,SAAS,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM;CAQhD;AAED;;;;GAIG;AACH,MAAM,WAAW,aAAa;IAC5B,+CAA+C;IAC/C,MAAM,EAAE,WAAW,CAAC;IACpB,qEAAqE;IACrE,MAAM,EAAE,MAAM,IAAI,CAAC;CACpB;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,WAAW,GAAG,aAAa,CA+BhG;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,CAAC,EAC3B,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,EACnB,SAAS,EAAE,MAAM,EACjB,OAAO,CAAC,EAAE,MAAM,GACf,OAAO,CAAC,CAAC,CAAC,CAWZ"}
@@ -1 +1 @@
1
- {"version":3,"file":"route-handler.d.ts","sourceRoot":"","sources":["../../src/server/route-handler.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAI/C,+DAA+D;AAC/D,MAAM,MAAM,UAAU,GAAG,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,OAAO,GAAG,QAAQ,GAAG,MAAM,GAAG,SAAS,CAAC;AAE1F,2DAA2D;AAC3D,MAAM,MAAM,YAAY,GAAG,CAAC,GAAG,EAAE,YAAY,KAAK,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;AAE/E,wEAAwE;AACxE,MAAM,MAAM,WAAW,GAAG;KACvB,CAAC,IAAI,UAAU,CAAC,CAAC,EAAE,YAAY;CACjC,CAAC;AAOF;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,WAAW,GAAG,UAAU,EAAE,CAyBpE;AAID;;;;;GAKG;AACH,wBAAsB,kBAAkB,CAAC,GAAG,EAAE,WAAW,EAAE,GAAG,EAAE,YAAY,GAAG,OAAO,CAAC,QAAQ,CAAC,CAyC/F"}
1
+ {"version":3,"file":"route-handler.d.ts","sourceRoot":"","sources":["../../src/server/route-handler.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAK/C,+DAA+D;AAC/D,MAAM,MAAM,UAAU,GAAG,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,OAAO,GAAG,QAAQ,GAAG,MAAM,GAAG,SAAS,CAAC;AAE1F,2DAA2D;AAC3D,MAAM,MAAM,YAAY,GAAG,CAAC,GAAG,EAAE,YAAY,KAAK,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;AAE/E,wEAAwE;AACxE,MAAM,MAAM,WAAW,GAAG;KACvB,CAAC,IAAI,UAAU,CAAC,CAAC,EAAE,YAAY;CACjC,CAAC;AAOF;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,WAAW,GAAG,UAAU,EAAE,CAyBpE;AAID;;;;;GAKG;AACH,wBAAsB,kBAAkB,CAAC,GAAG,EAAE,WAAW,EAAE,GAAG,EAAE,YAAY,GAAG,OAAO,CAAC,QAAQ,CAAC,CAyC/F"}
@@ -17,14 +17,57 @@ export declare const RSC_CONTENT_TYPE = "text/x-component";
17
17
  * stream that we drain and discard.
18
18
  *
19
19
  * See design/13-security.md §"Server component source leak"
20
- *
21
- * TODO: In the future, expose this debug data to the browser in dev mode
22
- * for inline error overlays (e.g. component stack traces).
23
20
  */
24
21
  export declare function createDebugChannelSink(): {
25
22
  readable: ReadableStream;
26
23
  writable: WritableStream;
27
24
  };
25
+ /**
26
+ * Parsed component debug info extracted from the Flight debug channel.
27
+ *
28
+ * Contains only component names, environment labels, and stack frames —
29
+ * never source code or props. See design/13-security.md §"Server source
30
+ * never reaches the client".
31
+ */
32
+ export interface DebugComponentEntry {
33
+ name: string;
34
+ env: string | null;
35
+ key: string | null;
36
+ stack: unknown[] | null;
37
+ }
38
+ /**
39
+ * A debug channel that collects Flight debug rows instead of discarding them.
40
+ *
41
+ * Used in dev mode to capture server component tree information for the
42
+ * Vite error overlay. The collector provides the same `{ readable, writable }`
43
+ * shape as the discard sink, plus methods to retrieve collected data.
44
+ *
45
+ * Security: only component names, environments, and stack frames are
46
+ * extracted — props and source code are stripped. In production builds,
47
+ * use `createDebugChannelSink()` instead (this function is never called).
48
+ */
49
+ export interface DebugChannelCollector {
50
+ readable: ReadableStream;
51
+ writable: WritableStream;
52
+ /** Get the raw collected text from the debug channel. */
53
+ getCollectedText(): string;
54
+ /** Get parsed component entries (names, stacks — no props or source). */
55
+ getComponents(): DebugComponentEntry[];
56
+ }
57
+ export declare function createDebugChannelCollector(): DebugChannelCollector;
58
+ /**
59
+ * Parse React Flight debug rows into component entries.
60
+ *
61
+ * The Flight debug channel writes rows in `hexId:json\n` format. Each row
62
+ * with a JSON object containing a `name` field is a component debug info
63
+ * entry. Rows without `name` (timing rows, reference rows like `D"$id"`)
64
+ * are skipped.
65
+ *
66
+ * Security: `props` are explicitly stripped from parsed entries — they may
67
+ * contain rendered output or user data. Only `name`, `env`, `key`, and
68
+ * `stack` are retained.
69
+ */
70
+ export declare function parseDebugRows(text: string): DebugComponentEntry[];
28
71
  /**
29
72
  * Build segment metadata for the X-Timber-Segments response header.
30
73
  * Describes the rendered segment chain with async status, enabling
@@ -1 +1 @@
1
- {"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../../src/server/rsc-entry/helpers.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AACrE,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAExD,+DAA+D;AAC/D,eAAO,MAAM,gBAAgB,qBAAqB,CAAC;AAEnD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,sBAAsB,IAAI;IAAE,QAAQ,EAAE,cAAc,CAAC;IAAC,QAAQ,EAAE,cAAc,CAAA;CAAE,CAQ/F;AAED;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,mBAAmB,EAAE,EAC/B,gBAAgB,EAAE,KAAK,CAAC;IACtB,SAAS,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,CAAC;IAC3C,OAAO,EAAE,mBAAmB,CAAC;CAC9B,CAAC,GACD,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,CAAC,CA0B3C;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAGzD;AAED;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CACnC,GAAG,EAAE,OAAO,EACZ,MAAM,EAAE,cAAc,EACtB,eAAe,EAAE,OAAO,GACvB,QAAQ,CAOV;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAIpD;AAED,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAM9C;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAW1E"}
1
+ {"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../../src/server/rsc-entry/helpers.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AACrE,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAExD,+DAA+D;AAC/D,eAAO,MAAM,gBAAgB,qBAAqB,CAAC;AAEnD;;;;;;;;;;GAUG;AACH,wBAAgB,sBAAsB,IAAI;IAAE,QAAQ,EAAE,cAAc,CAAC;IAAC,QAAQ,EAAE,cAAc,CAAA;CAAE,CAQ/F;AAID;;;;;;GAMG;AACH,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACnB,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACnB,KAAK,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC;CACzB;AAED;;;;;;;;;;GAUG;AACH,MAAM,WAAW,qBAAqB;IACpC,QAAQ,EAAE,cAAc,CAAC;IACzB,QAAQ,EAAE,cAAc,CAAC;IACzB,yDAAyD;IACzD,gBAAgB,IAAI,MAAM,CAAC;IAC3B,yEAAyE;IACzE,aAAa,IAAI,mBAAmB,EAAE,CAAC;CACxC;AAED,wBAAgB,2BAA2B,IAAI,qBAAqB,CAkCnE;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,mBAAmB,EAAE,CAoClE;AAED;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,mBAAmB,EAAE,EAC/B,gBAAgB,EAAE,KAAK,CAAC;IACtB,SAAS,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,CAAC;IAC3C,OAAO,EAAE,mBAAmB,CAAC;CAC9B,CAAC,GACD,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,CAAC,CA0B3C;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAGzD;AAED;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CACnC,GAAG,EAAE,OAAO,EACZ,MAAM,EAAE,cAAc,EACtB,eAAe,EAAE,OAAO,GACvB,QAAQ,CAOV;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAIpD;AAED,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAM9C;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAW1E"}
@@ -1,10 +1,15 @@
1
+ import { type DebugComponentEntry } from './helpers.js';
1
2
  /**
2
3
  * Set the dev pipeline error handler.
3
4
  *
4
5
  * Called by the dev server after importing this module to wire pipeline
5
6
  * errors into the Vite browser error overlay. No-op in production.
7
+ *
8
+ * The handler receives an optional third argument with RSC debug component
9
+ * info — component names, environments, and stack frames from the Flight
10
+ * debug channel. This is only populated for render-phase errors.
6
11
  */
7
- export declare function setDevPipelineErrorHandler(handler: (error: Error, phase: string) => void): void;
12
+ export declare function setDevPipelineErrorHandler(handler: (error: Error, phase: string, debugComponents?: DebugComponentEntry[]) => void): void;
8
13
  export { runWithEarlyHintsSender } from '#/server/early-hints-sender.js';
9
14
  export { runWithWaitUntil } from '#/server/waituntil-bridge.js';
10
15
  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":"AA+FA;;;;;GAKG;AACH,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,GAAG,IAAI,CAE/F;AA6aD,OAAO,EAAE,uBAAuB,EAAE,MAAM,gCAAgC,CAAC;AAIzE,OAAO,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;8BA5R3C,OAAO,KAAG,OAAO,CAAC,QAAQ,CAAC;AA8RhD,wBAAiE"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/server/rsc-entry/index.ts"],"names":[],"mappings":"AA8DA,OAAO,EAKL,KAAK,mBAAmB,EACzB,MAAM,cAAc,CAAC;AAiCtB;;;;;;;;;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;AA+bD,OAAO,EAAE,uBAAuB,EAAE,MAAM,gCAAgC,CAAC;AAIzE,OAAO,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;8BAhS3C,OAAO,KAAG,OAAO,CAAC,QAAQ,CAAC;AAkShD,wBAAiE"}
@@ -1 +1 @@
1
- {"version":3,"file":"rsc-payload.d.ts","sourceRoot":"","sources":["../../../src/server/rsc-entry/rsc-payload.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAIH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAEvD,OAAO,KAAK,EAAE,WAAW,EAAE,oBAAoB,EAAE,MAAM,mCAAmC,CAAC;AAC3F,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AAQrE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAErD;;;;;;;;GAQG;AACH,wBAAsB,uBAAuB,CAC3C,GAAG,EAAE,OAAO,EACZ,SAAS,EAAE,cAAc,CAAC,UAAU,CAAC,EACrC,OAAO,EAAE,aAAa,EACtB,QAAQ,EAAE,mBAAmB,EAAE,EAC/B,gBAAgB,EAAE,oBAAoB,EAAE,EACxC,YAAY,EAAE,WAAW,EAAE,EAC3B,KAAK,EAAE,UAAU,EACjB,eAAe,EAAE,OAAO,EACxB,eAAe,CAAC,EAAE,MAAM,EAAE,GACzB,OAAO,CAAC,QAAQ,CAAC,CA0HnB"}
1
+ {"version":3,"file":"rsc-payload.d.ts","sourceRoot":"","sources":["../../../src/server/rsc-entry/rsc-payload.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAIH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAEvD,OAAO,KAAK,EAAE,WAAW,EAAE,oBAAoB,EAAE,MAAM,mCAAmC,CAAC;AAC3F,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AAQrE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAErD;;;;;;;;GAQG;AACH,wBAAsB,uBAAuB,CAC3C,GAAG,EAAE,OAAO,EACZ,SAAS,EAAE,cAAc,CAAC,UAAU,CAAC,EACrC,OAAO,EAAE,aAAa,EACtB,QAAQ,EAAE,mBAAmB,EAAE,EAC/B,gBAAgB,EAAE,oBAAoB,EAAE,EACxC,YAAY,EAAE,WAAW,EAAE,EAC3B,KAAK,EAAE,UAAU,EACjB,eAAe,EAAE,OAAO,EACxB,eAAe,CAAC,EAAE,MAAM,EAAE,GACzB,OAAO,CAAC,QAAQ,CAAC,CAkInB"}
@@ -10,6 +10,7 @@
10
10
  * 13-security.md §"Errors don't leak"
11
11
  */
12
12
  import { DenySignal, RedirectSignal } from '#/server/primitives.js';
13
+ import { type DebugComponentEntry } from './helpers.js';
13
14
  /**
14
15
  * Mutable signal state captured during RSC rendering.
15
16
  *
@@ -33,6 +34,8 @@ export interface RenderSignals {
33
34
  export interface RscStreamResult {
34
35
  rscStream: ReadableStream<Uint8Array> | undefined;
35
36
  signals: RenderSignals;
37
+ /** Dev-only: server component debug info from the Flight debug channel. */
38
+ getDebugComponents?: () => DebugComponentEntry[];
36
39
  }
37
40
  /**
38
41
  * Render a React element tree to an RSC Flight stream.
@@ -1 +1 @@
1
- {"version":3,"file":"rsc-stream.d.ts","sourceRoot":"","sources":["../../../src/server/rsc-entry/rsc-stream.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAKH,OAAO,EAAE,UAAU,EAAE,cAAc,EAAe,MAAM,wBAAwB,CAAC;AAMjF;;;;;;;;;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;CACxB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,KAAK,CAAC,YAAY,EAAE,GAAG,EAAE,OAAO,GAAG,eAAe,CA4G1F"}
1
+ {"version":3,"file":"rsc-stream.d.ts","sourceRoot":"","sources":["../../../src/server/rsc-entry/rsc-stream.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAKH,OAAO,EAAE,UAAU,EAAE,cAAc,EAAe,MAAM,wBAAwB,CAAC;AAGjF,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,CAyH1F"}
@@ -1 +1 @@
1
- {"version":3,"file":"ssr-entry.d.ts","sourceRoot":"","sources":["../../src/server/ssr-entry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AA0EH;;;;;GAKG;AACH,MAAM,WAAW,UAAU;IACzB,6BAA6B;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,mEAAmE;IACnE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,CAAC;IAC1C,iCAAiC;IACjC,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACrC,qCAAqC;IACrC,UAAU,EAAE,MAAM,CAAC;IACnB,6CAA6C;IAC7C,eAAe,EAAE,OAAO,CAAC;IACzB,0DAA0D;IAC1D,QAAQ,EAAE,MAAM,CAAC;IACjB,8EAA8E;IAC9E,sBAAsB,EAAE,MAAM,CAAC;IAC/B,qEAAqE;IACrE,SAAS,CAAC,EAAE,cAAc,CAAC,UAAU,CAAC,CAAC;IACvC;;;0DAGsD;IACtD,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B;;iFAE6E;IAC7E,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB;4DACwD;IACxD,OAAO,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC9B;;;;;OAKG;IACH,sBAAsB,CAAC,EAAE,OAAO,CAAC;IAEjC;;;;;;;OAOG;IACH,WAAW,CAAC,EAAE;QACZ,gFAAgF;QAChF,QAAQ,EAAE,MAAM,CAAC;QACjB,uDAAuD;QACvD,OAAO,EAAE,MAAM,CAAC;QAChB,gEAAgE;QAChE,MAAM,EAAE,MAAM,CAAC;QACf,wEAAwE;QACxE,UAAU,EAAE,MAAM,CAAC;QACnB,+CAA+C;QAC/C,OAAO,EAAE,MAAM,CAAC;QAChB,sDAAsD;QACtD,WAAW,EAAE,OAAO,CAAC;KACtB,CAAC;CACH;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAsB,SAAS,CAC7B,SAAS,EAAE,cAAc,CAAC,UAAU,CAAC,EACrC,UAAU,EAAE,UAAU,GACrB,OAAO,CAAC,QAAQ,CAAC,CAyJnB;AAED,eAAe,SAAS,CAAC"}
1
+ {"version":3,"file":"ssr-entry.d.ts","sourceRoot":"","sources":["../../src/server/ssr-entry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AA0EH;;;;;GAKG;AACH,MAAM,WAAW,UAAU;IACzB,6BAA6B;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,mEAAmE;IACnE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,CAAC;IAC1C,iCAAiC;IACjC,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACrC,qCAAqC;IACrC,UAAU,EAAE,MAAM,CAAC;IACnB,6CAA6C;IAC7C,eAAe,EAAE,OAAO,CAAC;IACzB,0DAA0D;IAC1D,QAAQ,EAAE,MAAM,CAAC;IACjB,8EAA8E;IAC9E,sBAAsB,EAAE,MAAM,CAAC;IAC/B,qEAAqE;IACrE,SAAS,CAAC,EAAE,cAAc,CAAC,UAAU,CAAC,CAAC;IACvC;;;0DAGsD;IACtD,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B;;iFAE6E;IAC7E,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB;4DACwD;IACxD,OAAO,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC9B;;;;;OAKG;IACH,sBAAsB,CAAC,EAAE,OAAO,CAAC;IAEjC;;;;;;;OAOG;IACH,WAAW,CAAC,EAAE;QACZ,gFAAgF;QAChF,QAAQ,EAAE,MAAM,CAAC;QACjB,uDAAuD;QACvD,OAAO,EAAE,MAAM,CAAC;QAChB,gEAAgE;QAChE,MAAM,EAAE,MAAM,CAAC;QACf,wEAAwE;QACxE,UAAU,EAAE,MAAM,CAAC;QACnB,+CAA+C;QAC/C,OAAO,EAAE,MAAM,CAAC;QAChB,sDAAsD;QACtD,WAAW,EAAE,OAAO,CAAC;KACtB,CAAC;CACH;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAsB,SAAS,CAC7B,SAAS,EAAE,cAAc,CAAC,UAAU,CAAC,EACrC,UAAU,EAAE,UAAU,GACrB,OAAO,CAAC,QAAQ,CAAC,CAgKnB;AAED,eAAe,SAAS,CAAC"}
@@ -38,6 +38,7 @@ export declare function renderSsrStream(element: ReactNode, options?: {
38
38
  bootstrapScriptContent?: string;
39
39
  deferSuspenseFor?: number;
40
40
  signal?: AbortSignal;
41
+ renderTimeoutMs?: number;
41
42
  }): Promise<ReadableStream<Uint8Array>>;
42
43
  /** Whether the current platform uses native Node.js streams for SSR. */
43
44
  export declare const useNodeStreams: boolean;
@@ -52,6 +53,7 @@ export declare function renderSsrNodeStream(element: ReactNode, options?: {
52
53
  bootstrapScriptContent?: string;
53
54
  deferSuspenseFor?: number;
54
55
  signal?: AbortSignal;
56
+ renderTimeoutMs?: number;
55
57
  }): Promise<import('node:stream').Readable>;
56
58
  /** Convert a Node.js Readable to a Web ReadableStream (zero-copy bridge). */
57
59
  export declare function nodeReadableToWeb(readable: import('node:stream').Readable): ReadableStream<Uint8Array>;
@@ -1 +1 @@
1
- {"version":3,"file":"ssr-render.d.ts","sourceRoot":"","sources":["../../src/server/ssr-render.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAqEvC;;;;;;;;;;;;;GAaG;AACH,wBAAsB,eAAe,CACnC,OAAO,EAAE,SAAS,EAClB,OAAO,CAAC,EAAE;IAAE,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,WAAW,CAAA;CAAE,GAC7F,OAAO,CAAC,cAAc,CAAC,UAAU,CAAC,CAAC,CAErC;AAED,wEAAwE;AACxE,eAAO,MAAM,cAAc,SAAkB,CAAC;AAU9C;;;;;;GAMG;AACH,wBAAsB,mBAAmB,CACvC,OAAO,EAAE,SAAS,EAClB,OAAO,CAAC,EAAE;IAAE,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,WAAW,CAAA;CAAE,GAC7F,OAAO,CAAC,OAAO,aAAa,EAAE,QAAQ,CAAC,CAkDzC;AAED,6EAA6E;AAC7E,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,OAAO,aAAa,EAAE,QAAQ,GACvC,cAAc,CAAC,UAAU,CAAC,CAE5B;AA0CD;;;;;;;;;;;;GAYG;AACH,2CAA2C;AAC3C,wBAAgB,2BAA2B,CACzC,MAAM,EAAE,cAAc,CAAC,UAAU,CAAC,EAClC,MAAM,CAAC,EAAE,WAAW,GACnB,cAAc,CAAC,UAAU,CAAC,CA2B5B;AAWD;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAC9B,UAAU,EAAE,cAAc,CAAC,UAAU,CAAC,EACtC,UAAU,EAAE,MAAM,EAClB,eAAe,EAAE,OAAO,GACvB,QAAQ,CASV"}
1
+ {"version":3,"file":"ssr-render.d.ts","sourceRoot":"","sources":["../../src/server/ssr-render.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAsEvC;;;;;;;;;;;;;GAaG;AACH,wBAAsB,eAAe,CACnC,OAAO,EAAE,SAAS,EAClB,OAAO,CAAC,EAAE;IACR,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B,GACA,OAAO,CAAC,cAAc,CAAC,UAAU,CAAC,CAAC,CAErC;AAED,wEAAwE;AACxE,eAAO,MAAM,cAAc,SAAkB,CAAC;AAU9C;;;;;;GAMG;AACH,wBAAsB,mBAAmB,CACvC,OAAO,EAAE,SAAS,EAClB,OAAO,CAAC,EAAE;IACR,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B,GACA,OAAO,CAAC,OAAO,aAAa,EAAE,QAAQ,CAAC,CAsFzC;AAED,6EAA6E;AAC7E,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,OAAO,aAAa,EAAE,QAAQ,GACvC,cAAc,CAAC,UAAU,CAAC,CAE5B;AAoFD;;;;;;;;;;;;GAYG;AACH,2CAA2C;AAC3C,wBAAgB,2BAA2B,CACzC,MAAM,EAAE,cAAc,CAAC,UAAU,CAAC,EAClC,MAAM,CAAC,EAAE,WAAW,GACnB,cAAc,CAAC,UAAU,CAAC,CA2B5B;AAWD;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAC9B,UAAU,EAAE,cAAc,CAAC,UAAU,CAAC,EACtC,UAAU,EAAE,MAAM,EAClB,eAAe,EAAE,OAAO,GACvB,QAAQ,CASV"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@timber-js/app",
3
- "version": "0.2.0-alpha.37",
3
+ "version": "0.2.0-alpha.39",
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",
@@ -565,14 +565,37 @@ const server = createServer(async (req, res) => {
565
565
 
566
566
  if (webResponse.body) {
567
567
  const reader = webResponse.body.getReader();
568
- const pump = async () => {
568
+
569
+ // Cancel the reader when the client disconnects. This causes any
570
+ // pending reader.read() to reject, breaking the pump loop. Critical
571
+ // for SSE and other infinite streams — without this, disconnected
572
+ // clients leak readers.
573
+ let clientDisconnected = false;
574
+ const onClose = () => {
575
+ clientDisconnected = true;
576
+ reader.cancel('Client disconnected').catch(() => {});
577
+ };
578
+ res.on('close', onClose);
579
+
580
+ try {
569
581
  while (true) {
570
582
  const { done, value } = await reader.read();
571
- if (done) { res.end(); return; }
583
+ if (done) break;
572
584
  res.write(value);
573
585
  }
574
- };
575
- await pump();
586
+ } catch (err) {
587
+ // reader.cancel() from the close handler causes read() to reject.
588
+ // This is expected on client disconnect — not an error.
589
+ if (!clientDisconnected) {
590
+ throw err;
591
+ }
592
+ } finally {
593
+ res.off('close', onClose);
594
+ reader.releaseLock();
595
+ if (!res.writableEnded) {
596
+ res.end();
597
+ }
598
+ }
576
599
  } else {
577
600
  res.end();
578
601
  }
@@ -12,6 +12,9 @@ export interface CacheOptions<Fn extends (...args: any[]) => any> {
12
12
  key?: (...args: Parameters<Fn>) => string;
13
13
  staleWhileRevalidate?: boolean;
14
14
  tags?: string[] | ((...args: Parameters<Fn>) => string[]);
15
+ /** Timeout (ms) for singleflight-coalesced calls. Prevents hung fn() from
16
+ * permanently blocking all future callers for the same cache key. See TIM-518. */
17
+ timeoutMs?: number;
15
18
  }
16
19
 
17
20
  export interface MemoryCacheHandlerOptions {
@@ -87,5 +90,5 @@ export { createCache } from './timber-cache';
87
90
  export { registerCachedFunction } from './register-cached-function';
88
91
  export type { RegisterCachedFunctionOptions } from './register-cached-function';
89
92
  export { stableStringify } from './stable-stringify';
90
- export { createSingleflight } from './singleflight';
91
- export type { Singleflight } from './singleflight';
93
+ export { createSingleflight, SingleflightTimeoutError } from './singleflight';
94
+ export type { Singleflight, SingleflightOptions } from './singleflight';
@@ -3,24 +3,82 @@
3
3
  * execution. All callers receive the same result (or error).
4
4
  *
5
5
  * Per-process, in-memory. Each process coalesces independently.
6
+ *
7
+ * An optional `timeoutMs` prevents hung `fn()` calls from permanently
8
+ * blocking all future callers for that key. When set, `fn()` is raced
9
+ * against a timeout — if the timeout fires first, the promise rejects
10
+ * with `SingleflightTimeoutError`, `finally` cleans up the key, and
11
+ * subsequent callers can retry. See TIM-518.
6
12
  */
13
+
14
+ export interface SingleflightOptions {
15
+ /** Maximum time (ms) a coalesced call may run before being rejected. */
16
+ timeoutMs?: number;
17
+ }
18
+
7
19
  export interface Singleflight {
8
20
  do<T>(key: string, fn: () => Promise<T>): Promise<T>;
9
21
  }
10
22
 
11
- export function createSingleflight(): Singleflight {
23
+ /**
24
+ * Error thrown when a singleflight call exceeds `timeoutMs`.
25
+ * Exported so callers can distinguish timeout from other errors.
26
+ */
27
+ export class SingleflightTimeoutError extends Error {
28
+ constructor(key: string, timeoutMs: number) {
29
+ super(`Singleflight timeout: key "${key}" exceeded ${timeoutMs}ms`);
30
+ this.name = 'SingleflightTimeoutError';
31
+ }
32
+ }
33
+
34
+ export function createSingleflight(opts?: SingleflightOptions): Singleflight {
12
35
  const inflight = new Map<string, Promise<unknown>>();
36
+ const timeoutMs = opts?.timeoutMs;
13
37
 
14
38
  return {
15
39
  do<T>(key: string, fn: () => Promise<T>): Promise<T> {
16
40
  const existing = inflight.get(key);
17
41
  if (existing) return existing as Promise<T>;
18
42
 
19
- const promise = fn().finally(() => {
43
+ let promise: Promise<T>;
44
+
45
+ if (timeoutMs != null && timeoutMs > 0) {
46
+ // Race fn() against a timeout to prevent hung calls from
47
+ // permanently blocking the key. See TIM-518.
48
+ promise = new Promise<T>((resolve, reject) => {
49
+ const timer = setTimeout(
50
+ () => reject(new SingleflightTimeoutError(key, timeoutMs)),
51
+ timeoutMs
52
+ );
53
+ // Wrap in try/catch so a synchronous throw from fn()
54
+ // (e.g. argument validation) still clears the timer.
55
+ // Without this, the timer leaks until expiry.
56
+ try {
57
+ fn().then(
58
+ (value) => {
59
+ clearTimeout(timer);
60
+ resolve(value);
61
+ },
62
+ (err) => {
63
+ clearTimeout(timer);
64
+ reject(err);
65
+ }
66
+ );
67
+ } catch (err) {
68
+ clearTimeout(timer);
69
+ reject(err);
70
+ }
71
+ });
72
+ } else {
73
+ promise = fn();
74
+ }
75
+
76
+ const tracked = promise.finally(() => {
20
77
  inflight.delete(key);
21
78
  });
22
- inflight.set(key, promise);
23
- return promise;
79
+
80
+ inflight.set(key, tracked);
81
+ return tracked as Promise<T>;
24
82
  },
25
83
  };
26
84
  }
@@ -4,7 +4,7 @@ import { createSingleflight } from './singleflight';
4
4
  import { addSpanEventSync } from '#/server/tracing.js';
5
5
  import { fnv1aHash } from './fast-hash.js';
6
6
 
7
- const singleflight = createSingleflight();
7
+ const defaultSingleflight = createSingleflight();
8
8
 
9
9
  /**
10
10
  * Generate a cache key from function identity and serialized args.
@@ -55,6 +55,9 @@ export function createCache<Fn extends (...args: any[]) => Promise<any>>(
55
55
  handler: CacheHandler
56
56
  ): (...args: Parameters<Fn>) => Promise<Awaited<ReturnType<Fn>>> {
57
57
  const fnId = `timber-cache:${fnIdCounter++}`;
58
+ const sf = opts.timeoutMs
59
+ ? createSingleflight({ timeoutMs: opts.timeoutMs })
60
+ : defaultSingleflight;
58
61
 
59
62
  return async (...args: Parameters<Fn>): Promise<Awaited<ReturnType<Fn>>> => {
60
63
  const key = opts.key ? opts.key(...args) : defaultKeyGenerator(fnId, args);
@@ -80,25 +83,23 @@ export function createCache<Fn extends (...args: any[]) => Promise<any>>(
80
83
  stale: true,
81
84
  });
82
85
  // Serve stale immediately, trigger background refetch
83
- singleflight
84
- .do(`swr:${key}`, async () => {
85
- try {
86
- const fresh = await fn(...args);
87
- const tags = resolveTags(opts, args);
88
- await handler.set(key, fresh, { ttl: opts.ttl, tags });
89
- } catch {
90
- // Failed refetch stale entry continues to be served.
91
- // Error is swallowed per design doc: "Error is logged."
92
- }
93
- })
94
- .catch(() => {
95
- // Singleflight promise rejection handled — stale continues.
96
- });
86
+ sf.do(`swr:${key}`, async () => {
87
+ try {
88
+ const fresh = await fn(...args);
89
+ const tags = resolveTags(opts, args);
90
+ await handler.set(key, fresh, { ttl: opts.ttl, tags });
91
+ } catch {
92
+ // Failed refetch — stale entry continues to be served.
93
+ // Error is swallowed per design doc: "Error is logged."
94
+ }
95
+ }).catch(() => {
96
+ // Singleflight promise rejection handled — stale continues.
97
+ });
97
98
  return cached.value as Awaited<ReturnType<Fn>>;
98
99
  }
99
100
 
100
101
  // Cache miss (or stale without SWR) — execute with singleflight
101
- const result = await singleflight.do(key, () => fn(...args));
102
+ const result = await sf.do(key, () => fn(...args));
102
103
  const tags = resolveTags(opts, args);
103
104
  await handler.set(key, result, { ttl: opts.ttl, tags });
104
105
 
package/src/index.ts CHANGED
@@ -90,6 +90,18 @@ export interface TimberUserConfig {
90
90
  * See design/17-logging.md §"slowRequestMs".
91
91
  */
92
92
  slowRequestMs?: number;
93
+ /**
94
+ * Render timeout in milliseconds. If an SSR render or RSC stream read
95
+ * does not complete within this duration, the render is aborted and
96
+ * the connection is closed. Protects against hung fetches and Suspense
97
+ * components that never resolve.
98
+ *
99
+ * Set to 0 to disable (not recommended in production).
100
+ * Default: 30000 (30 seconds).
101
+ *
102
+ * See design/02-rendering-pipeline.md §"Streaming Constraints".
103
+ */
104
+ renderTimeoutMs?: number;
93
105
  /** Dev-mode options. These have no effect in production builds. */
94
106
  dev?: {
95
107
  /** Threshold in ms to highlight slow phases in dev logging output. Default: 200. */