@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.
- package/dist/adapters/nitro.d.ts.map +1 -1
- package/dist/adapters/nitro.js +27 -4
- package/dist/adapters/nitro.js.map +1 -1
- package/dist/cache/index.d.ts +5 -2
- package/dist/cache/index.d.ts.map +1 -1
- package/dist/cache/index.js +37 -8
- package/dist/cache/index.js.map +1 -1
- package/dist/cache/singleflight.d.ts +18 -1
- package/dist/cache/singleflight.d.ts.map +1 -1
- package/dist/cache/timber-cache.d.ts.map +1 -1
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +53 -4
- package/dist/index.js.map +1 -1
- package/dist/plugins/dev-error-overlay.d.ts +26 -1
- package/dist/plugins/dev-error-overlay.d.ts.map +1 -1
- package/dist/plugins/entries.d.ts.map +1 -1
- package/dist/server/action-handler.d.ts.map +1 -1
- package/dist/server/default-logger.d.ts +22 -0
- package/dist/server/default-logger.d.ts.map +1 -0
- package/dist/server/flush.d.ts.map +1 -1
- package/dist/server/html-injectors.d.ts +2 -2
- package/dist/server/html-injectors.d.ts.map +1 -1
- package/dist/server/index.d.ts +2 -0
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +135 -24
- package/dist/server/index.js.map +1 -1
- package/dist/server/logger.d.ts +24 -7
- package/dist/server/logger.d.ts.map +1 -1
- package/dist/server/node-stream-transforms.d.ts +13 -1
- package/dist/server/node-stream-transforms.d.ts.map +1 -1
- package/dist/server/render-timeout.d.ts +51 -0
- package/dist/server/render-timeout.d.ts.map +1 -0
- package/dist/server/route-handler.d.ts.map +1 -1
- package/dist/server/rsc-entry/helpers.d.ts +46 -3
- package/dist/server/rsc-entry/helpers.d.ts.map +1 -1
- package/dist/server/rsc-entry/index.d.ts +6 -1
- package/dist/server/rsc-entry/index.d.ts.map +1 -1
- package/dist/server/rsc-entry/rsc-payload.d.ts.map +1 -1
- package/dist/server/rsc-entry/rsc-stream.d.ts +3 -0
- package/dist/server/rsc-entry/rsc-stream.d.ts.map +1 -1
- package/dist/server/ssr-entry.d.ts.map +1 -1
- package/dist/server/ssr-render.d.ts +2 -0
- package/dist/server/ssr-render.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/adapters/nitro.ts +27 -4
- package/src/cache/index.ts +5 -2
- package/src/cache/singleflight.ts +62 -4
- package/src/cache/timber-cache.ts +17 -16
- package/src/index.ts +12 -0
- package/src/plugins/dev-error-overlay.ts +70 -1
- package/src/plugins/dev-server.ts +38 -4
- package/src/plugins/entries.ts +1 -0
- package/src/server/action-handler.ts +3 -2
- package/src/server/default-logger.ts +95 -0
- package/src/server/flush.ts +2 -1
- package/src/server/html-injectors.ts +32 -7
- package/src/server/index.ts +4 -0
- package/src/server/logger.ts +38 -35
- package/src/server/node-stream-transforms.ts +51 -14
- package/src/server/render-timeout.ts +108 -0
- package/src/server/route-handler.ts +2 -1
- package/src/server/rsc-entry/helpers.ts +122 -3
- package/src/server/rsc-entry/index.ts +34 -4
- package/src/server/rsc-entry/rsc-payload.ts +11 -3
- package/src/server/rsc-entry/rsc-stream.ts +24 -3
- package/src/server/ssr-entry.ts +9 -2
- package/src/server/ssr-render.ts +105 -16
package/dist/server/logger.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Logger — structured logging with environment-aware formatting.
|
|
3
3
|
*
|
|
4
|
-
* timber.js
|
|
5
|
-
*
|
|
6
|
-
*
|
|
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
|
|
24
|
-
*
|
|
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
|
|
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;
|
|
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
|
-
|
|
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;
|
|
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;
|
|
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
|
|
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":"
|
|
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,
|
|
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;
|
|
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,
|
|
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;
|
|
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.
|
|
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",
|
package/src/adapters/nitro.ts
CHANGED
|
@@ -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
|
-
|
|
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)
|
|
583
|
+
if (done) break;
|
|
572
584
|
res.write(value);
|
|
573
585
|
}
|
|
574
|
-
}
|
|
575
|
-
|
|
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
|
}
|
package/src/cache/index.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
23
|
-
|
|
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
|
|
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
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
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
|
|
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. */
|