@timber-js/app 0.2.0-alpha.97 → 0.2.0-alpha.98
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/_chunks/{metadata-routes-DS3eKNmf.js → metadata-routes-BU684ls2.js} +1 -1
- package/dist/_chunks/{metadata-routes-DS3eKNmf.js.map → metadata-routes-BU684ls2.js.map} +1 -1
- package/dist/_chunks/segment-classify-BjfuctV2.js +137 -0
- package/dist/_chunks/segment-classify-BjfuctV2.js.map +1 -0
- package/dist/_chunks/{interception-BbqMCVXa.js → walkers-VOXgavMF.js} +61 -85
- package/dist/_chunks/walkers-VOXgavMF.js.map +1 -0
- package/dist/adapters/nitro.d.ts.map +1 -1
- package/dist/adapters/nitro.js +55 -5
- package/dist/adapters/nitro.js.map +1 -1
- package/dist/client/index.js +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +189 -62
- package/dist/index.js.map +1 -1
- package/dist/plugins/build-report.d.ts +6 -4
- package/dist/plugins/build-report.d.ts.map +1 -1
- package/dist/plugins/dev-404-page.d.ts +8 -18
- package/dist/plugins/dev-404-page.d.ts.map +1 -1
- package/dist/routing/index.d.ts +5 -3
- package/dist/routing/index.d.ts.map +1 -1
- package/dist/routing/index.js +3 -3
- package/dist/routing/scanner.d.ts +1 -10
- package/dist/routing/scanner.d.ts.map +1 -1
- package/dist/routing/segment-classify.d.ts +37 -8
- package/dist/routing/segment-classify.d.ts.map +1 -1
- package/dist/routing/types.d.ts +63 -23
- package/dist/routing/types.d.ts.map +1 -1
- package/dist/routing/walkers.d.ts +51 -0
- package/dist/routing/walkers.d.ts.map +1 -0
- package/dist/server/action-handler.d.ts.map +1 -1
- package/dist/server/dev-holding-server.d.ts +4 -2
- package/dist/server/dev-holding-server.d.ts.map +1 -1
- package/dist/server/html-injector-core.d.ts +212 -0
- package/dist/server/html-injector-core.d.ts.map +1 -0
- package/dist/server/html-injectors.d.ts +59 -59
- package/dist/server/html-injectors.d.ts.map +1 -1
- package/dist/server/internal.js +710 -563
- package/dist/server/internal.js.map +1 -1
- package/dist/server/node-stream-transforms.d.ts +46 -49
- package/dist/server/node-stream-transforms.d.ts.map +1 -1
- package/dist/server/pipeline-helpers.d.ts +88 -0
- package/dist/server/pipeline-helpers.d.ts.map +1 -0
- package/dist/server/pipeline-phases.d.ts +97 -0
- package/dist/server/pipeline-phases.d.ts.map +1 -0
- package/dist/server/pipeline.d.ts +53 -32
- package/dist/server/pipeline.d.ts.map +1 -1
- package/dist/server/port-resolution.d.ts +117 -0
- package/dist/server/port-resolution.d.ts.map +1 -0
- package/dist/server/route-matcher.d.ts +20 -47
- package/dist/server/route-matcher.d.ts.map +1 -1
- package/dist/server/rsc-entry/index.d.ts.map +1 -1
- package/dist/server/rsc-entry/wrap-action-dispatch.d.ts +74 -0
- package/dist/server/rsc-entry/wrap-action-dispatch.d.ts.map +1 -0
- package/dist/server/status-code-resolver.d.ts +16 -11
- package/dist/server/status-code-resolver.d.ts.map +1 -1
- package/dist/server/tree-builder.d.ts.map +1 -1
- package/dist/utils/directive-parser.d.ts +0 -45
- package/dist/utils/directive-parser.d.ts.map +1 -1
- package/package.json +7 -6
- package/src/adapters/nitro.ts +55 -5
- package/src/cli.ts +0 -0
- package/src/index.ts +84 -31
- package/src/plugins/build-report.ts +13 -22
- package/src/plugins/dev-404-page.ts +15 -41
- package/src/plugins/routing.ts +14 -12
- package/src/routing/codegen.ts +1 -1
- package/src/routing/convention-lint.ts +4 -4
- package/src/routing/index.ts +5 -3
- package/src/routing/interception.ts +1 -1
- package/src/routing/scanner.ts +17 -93
- package/src/routing/segment-classify.ts +107 -8
- package/src/routing/status-file-lint.ts +3 -3
- package/src/routing/types.ts +63 -23
- package/src/routing/walkers.ts +90 -0
- package/src/server/action-handler.ts +6 -0
- package/src/server/deny-renderer.ts +5 -5
- package/src/server/dev-holding-server.ts +4 -2
- package/src/server/fallback-error.ts +1 -1
- package/src/server/html-injector-core.ts +403 -0
- package/src/server/html-injectors.ts +158 -297
- package/src/server/node-stream-transforms.ts +108 -248
- package/src/server/pipeline-helpers.ts +180 -0
- package/src/server/pipeline-phases.ts +591 -0
- package/src/server/pipeline.ts +76 -539
- package/src/server/port-resolution.ts +215 -0
- package/src/server/route-element-builder.ts +1 -1
- package/src/server/route-matcher.ts +28 -60
- package/src/server/rsc-entry/api-handler.ts +2 -2
- package/src/server/rsc-entry/error-renderer.ts +1 -1
- package/src/server/rsc-entry/index.ts +52 -98
- package/src/server/rsc-entry/wrap-action-dispatch.ts +156 -0
- package/src/server/sitemap-generator.ts +1 -1
- package/src/server/slot-resolver.ts +1 -1
- package/src/server/status-code-resolver.ts +112 -128
- package/src/server/tree-builder.ts +6 -4
- package/src/utils/directive-parser.ts +0 -392
- package/LICENSE +0 -8
- package/dist/_chunks/interception-BbqMCVXa.js.map +0 -1
- package/dist/_chunks/segment-classify-BDNn6EzD.js +0 -65
- package/dist/_chunks/segment-classify-BDNn6EzD.js.map +0 -1
- package/dist/server/manifest-status-resolver.d.ts +0 -58
- package/dist/server/manifest-status-resolver.d.ts.map +0 -1
- package/src/server/manifest-status-resolver.ts +0 -215
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Node.js native stream transforms for SSR HTML post-processing.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* streams
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
4
|
+
* Node.js `Transform` wrappers around the pure helpers in
|
|
5
|
+
* `html-injector-core.ts`. Used on Node.js / Bun where C++-backed
|
|
6
|
+
* native streams are significantly faster than the JS-implemented
|
|
7
|
+
* Web Streams. The Web `TransformStream` equivalents live in
|
|
8
|
+
* `html-injectors.ts` and wrap the same core — keep the two files
|
|
9
|
+
* byte-for-byte equivalent in behavior. If you fix a streaming bug
|
|
10
|
+
* here, it almost certainly belongs in the core (`html-injector-core.ts`),
|
|
11
|
+
* not in this wrapper.
|
|
11
12
|
*
|
|
12
13
|
* Architecture:
|
|
13
14
|
* renderToPipeableStream → pipe(errorHandler) → pipe(headInjector)
|
|
@@ -17,9 +18,7 @@
|
|
|
17
18
|
* Readable.toWeb() conversion for the Response body.
|
|
18
19
|
*/
|
|
19
20
|
import { Transform } from 'node:stream';
|
|
20
|
-
|
|
21
|
-
* Options for the Node.js buffered transform.
|
|
22
|
-
*/
|
|
21
|
+
import { SUFFIX_BYTES } from './html-injector-core.js';
|
|
23
22
|
export interface NodeBufferedTransformOptions {
|
|
24
23
|
/**
|
|
25
24
|
* Flush synchronously once the buffer reaches this many bytes.
|
|
@@ -29,67 +28,65 @@ export interface NodeBufferedTransformOptions {
|
|
|
29
28
|
readonly maxBufferByteLength?: number;
|
|
30
29
|
}
|
|
31
30
|
/**
|
|
32
|
-
* Node.js Transform that buffers incoming chunks and coalesces them
|
|
31
|
+
* Node.js `Transform` that buffers incoming chunks and coalesces them
|
|
33
32
|
* within a single event loop tick.
|
|
34
33
|
*
|
|
35
|
-
* Equivalent to createBufferedTransformStream
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
* could see chunk boundaries in the middle of HTML tags or attributes.
|
|
34
|
+
* Equivalent to `createBufferedTransformStream` in `html-injectors.ts` —
|
|
35
|
+
* the actual buffer math lives in `BufferAggregator`. This wrapper only
|
|
36
|
+
* owns the `setImmediate` scheduling and the push to the Node `Transform`.
|
|
39
37
|
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
* **Not a polling loop.** Uses a single-shot `setImmediate` per flush
|
|
44
|
-
* cycle — no recursive scheduling, no busy-wait. See design/02 §"No Polling".
|
|
45
|
-
*
|
|
46
|
-
* Inspired by Next.js `createBufferedTransformStream`.
|
|
38
|
+
* **Not a polling loop.** Single-shot `setImmediate` per flush cycle —
|
|
39
|
+
* no recursive scheduling. See design/02 §"No Polling".
|
|
47
40
|
*/
|
|
48
41
|
export declare function createNodeBufferedTransform(options?: NodeBufferedTransformOptions): Transform;
|
|
49
42
|
/**
|
|
50
|
-
* Node.js Transform that moves `</body></html>` to the end of the stream.
|
|
43
|
+
* Node.js `Transform` that moves `</body></html>` to the end of the stream.
|
|
51
44
|
*
|
|
52
|
-
* Equivalent to createMoveSuffixStream
|
|
53
|
-
*
|
|
54
|
-
*
|
|
45
|
+
* Equivalent to `createMoveSuffixStream` in `html-injectors.ts`. Strips
|
|
46
|
+
* the suffix on first sight and re-emits it from `flush()`. If the
|
|
47
|
+
* suffix never appeared in the input, it is appended anyway so output
|
|
48
|
+
* is well-formed.
|
|
55
49
|
*/
|
|
56
50
|
export declare function createNodeMoveSuffixTransform(): Transform;
|
|
51
|
+
export { SUFFIX_BYTES };
|
|
57
52
|
/**
|
|
58
|
-
* Node.js Transform that injects HTML content before
|
|
53
|
+
* Node.js `Transform` that injects HTML content before `</head>`.
|
|
59
54
|
*
|
|
60
|
-
* Equivalent to injectHead
|
|
55
|
+
* Equivalent to `injectHead` in `html-injectors.ts`. Streams chunks
|
|
61
56
|
* through immediately, keeping only a small trailing buffer to handle
|
|
62
|
-
*
|
|
57
|
+
* `</head>` split across chunk boundaries.
|
|
63
58
|
*/
|
|
64
59
|
export declare function createNodeHeadInjector(headHtml: string): Transform;
|
|
65
|
-
/**
|
|
66
|
-
* Node.js Transform that merges RSC script tags into the HTML stream.
|
|
67
|
-
*
|
|
68
|
-
* Reads RSC chunks from the provided ReadableStream and injects them
|
|
69
|
-
* as `<script>` tags between HTML chunks. Scripts are buffered in
|
|
70
|
-
* pending[] and only drained from transform() (after a complete HTML
|
|
71
|
-
* chunk) or flush() — never pushed directly from the pull loop.
|
|
72
|
-
*
|
|
73
|
-
* Suffix stripping (</body></html>) is handled upstream by
|
|
74
|
-
* createNodeMoveSuffixTransform. This transform only interleaves
|
|
75
|
-
* RSC scripts at safe chunk boundaries.
|
|
76
|
-
*
|
|
77
|
-
* The RSC stream is a Web ReadableStream (from the tee'd RSC Flight
|
|
78
|
-
* stream). We read from it using the Web API — this is the one bridge
|
|
79
|
-
* point between Web Streams and Node.js streams in the pipeline.
|
|
80
|
-
*/
|
|
81
60
|
/**
|
|
82
61
|
* Options for the Node.js flight injector.
|
|
83
62
|
*/
|
|
84
63
|
export interface NodeFlightInjectorOptions {
|
|
85
64
|
/**
|
|
86
|
-
* Timeout in milliseconds for individual RSC stream reads.
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
65
|
+
* Timeout in milliseconds for individual RSC stream reads. If a single
|
|
66
|
+
* `rscReader.read()` call does not resolve within this duration, the
|
|
67
|
+
* read is aborted and the stream errors with a `RenderTimeoutError`.
|
|
68
|
+
* Default: 30000 (30s).
|
|
90
69
|
*/
|
|
91
70
|
renderTimeoutMs?: number;
|
|
92
71
|
}
|
|
72
|
+
/**
|
|
73
|
+
* Node.js `Transform` that merges RSC script tags into the HTML stream.
|
|
74
|
+
*
|
|
75
|
+
* Equivalent to `createFlightInjectionTransform` in `html-injectors.ts`.
|
|
76
|
+
* The state machine, pending queue, and pull loop all live in
|
|
77
|
+
* `FlightInjectorCore` — this wrapper only shuffles bytes between the
|
|
78
|
+
* core and the Node `Transform.push()` API.
|
|
79
|
+
*
|
|
80
|
+
* Suffix stripping (`</body></html>`) is handled upstream by
|
|
81
|
+
* `createNodeMoveSuffixTransform`. RSC scripts are buffered in
|
|
82
|
+
* `core.pending[]` and only drained from `transform()` (after a complete
|
|
83
|
+
* HTML chunk) or `flush()` — never mid-tag. See design/02
|
|
84
|
+
* §"Scripts at Chunk Boundaries Only".
|
|
85
|
+
*
|
|
86
|
+
* The RSC stream is a Web `ReadableStream` (from the tee'd RSC Flight
|
|
87
|
+
* stream). The core reads from it using the Web API — this is the one
|
|
88
|
+
* bridge point between Web Streams and Node.js streams in the pipeline.
|
|
89
|
+
*/
|
|
93
90
|
export declare function createNodeFlightInjector(rscStream: ReadableStream<Uint8Array> | undefined, options?: NodeFlightInjectorOptions): Transform;
|
|
94
91
|
/**
|
|
95
92
|
* Node.js Transform that catches post-shell streaming errors.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"node-stream-transforms.d.ts","sourceRoot":"","sources":["../../src/server/node-stream-transforms.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"node-stream-transforms.d.ts","sourceRoot":"","sources":["../../src/server/node-stream-transforms.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAGxC,OAAO,EAGL,YAAY,EAOb,MAAM,yBAAyB,CAAC;AAKjC,MAAM,WAAW,4BAA4B;IAC3C;;;;OAIG;IACH,QAAQ,CAAC,mBAAmB,CAAC,EAAE,MAAM,CAAC;CACvC;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,2BAA2B,CAAC,OAAO,GAAE,4BAAiC,GAAG,SAAS,CAsCjG;AAID;;;;;;;GAOG;AACH,wBAAgB,6BAA6B,IAAI,SAAS,CAoBzD;AAGD,OAAO,EAAE,YAAY,EAAE,CAAC;AAIxB;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,CA2BlE;AAID;;GAEG;AACH,MAAM,WAAW,yBAAyB;IACxC;;;;;OAKG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,wBAAwB,CACtC,SAAS,EAAE,cAAc,CAAC,UAAU,CAAC,GAAG,SAAS,EACjD,OAAO,CAAC,EAAE,yBAAyB,GAClC,SAAS,CA6DX;AAOD;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,SAAS,CAwBtE;AAmBD;;;;;;;;;GASG;AACH,wBAAgB,wBAAwB,CACtC,cAAc,EAAE,OAAO,EACvB,eAAe,EAAE,OAAO,GACvB,SAAS,GAAG,IAAI,CA8BlB"}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pipeline helpers — small utility functions used by `pipeline.ts` and
|
|
3
|
+
* `pipeline-phases.ts`. Lifted out of `pipeline.ts` to keep that file
|
|
4
|
+
* focused on the request handler entry point.
|
|
5
|
+
*
|
|
6
|
+
* Each helper is intentionally a free function with no closure capture, so
|
|
7
|
+
* it can be unit-tested in isolation.
|
|
8
|
+
*
|
|
9
|
+
* See design/07-routing.md §"Request Lifecycle".
|
|
10
|
+
*/
|
|
11
|
+
import type { ProxyExport } from './proxy.js';
|
|
12
|
+
import { RedirectSignal } from './primitives.js';
|
|
13
|
+
import type { ProxyConfig } from './pipeline.js';
|
|
14
|
+
/**
|
|
15
|
+
* Shallow merge that skips top-level prototype-polluting keys.
|
|
16
|
+
*
|
|
17
|
+
* This is intentionally NOT a deep sanitizer. It only blocks shallow
|
|
18
|
+
* pollution via top-level `__proto__` / `constructor` / `prototype`
|
|
19
|
+
* keys. The deeper guarantee for segment params comes from merging
|
|
20
|
+
* codec output into a null-prototype target inside coerceSegmentParams().
|
|
21
|
+
*
|
|
22
|
+
* See TIM-655, TIM-855, design/13-security.md
|
|
23
|
+
*/
|
|
24
|
+
export declare function safeMerge(target: Record<string, unknown>, source: Record<string, unknown>): void;
|
|
25
|
+
/**
|
|
26
|
+
* Resolver closure produced once at pipeline construction. The lazy variant
|
|
27
|
+
* still calls `loader()` per-request (HMR relies on re-importing), but the
|
|
28
|
+
* choice of which branch to take is made once, not on every request.
|
|
29
|
+
*/
|
|
30
|
+
export type ProxyResolver = () => ProxyExport | Promise<ProxyExport>;
|
|
31
|
+
/**
|
|
32
|
+
* Build a proxy resolver closure from the declared source. Called exactly
|
|
33
|
+
* once at `createPipeline` setup time, so the hot path sees only the branch
|
|
34
|
+
* that corresponds to this pipeline's configured variant.
|
|
35
|
+
*
|
|
36
|
+
* Returns `null` when the app has no proxy.ts — the hot path short-circuits
|
|
37
|
+
* around `runProxyPhase` entirely in that case.
|
|
38
|
+
*
|
|
39
|
+
* Accepts the sugar form (a bare `ProxyExport` — function or function array)
|
|
40
|
+
* and normalises it to the static variant. Functions and arrays are
|
|
41
|
+
* structurally distinct from the tagged `{ kind: 'lazy', loader }` object,
|
|
42
|
+
* so discrimination is unambiguous.
|
|
43
|
+
*/
|
|
44
|
+
export declare function makeProxyResolver(proxy: ProxyConfig | ProxyExport | undefined): ProxyResolver | null;
|
|
45
|
+
/**
|
|
46
|
+
* Apply all Set-Cookie headers from the cookie jar to a Headers object.
|
|
47
|
+
* Each cookie gets its own Set-Cookie header per RFC 6265 §4.1.
|
|
48
|
+
*/
|
|
49
|
+
export declare function applyCookieJar(headers: Headers): void;
|
|
50
|
+
/**
|
|
51
|
+
* Merge framework-managed response headers onto a terminal response without
|
|
52
|
+
* overwriting headers the terminal response already set itself.
|
|
53
|
+
*/
|
|
54
|
+
export declare function mergeMissingHeaders(target: Headers, source: Headers): void;
|
|
55
|
+
/**
|
|
56
|
+
* Clone a Response into a fresh one whose header bag is guaranteed mutable.
|
|
57
|
+
*
|
|
58
|
+
* `Response.redirect()` and some platform-level passthrough responses (notably
|
|
59
|
+
* on Cloudflare Workers) return objects with frozen header bags. Calling
|
|
60
|
+
* `.set()` or `.append()` on them throws `TypeError: immutable`, which the
|
|
61
|
+
* pipeline can hit when it appends Set-Cookie or Server-Timing entries.
|
|
62
|
+
*
|
|
63
|
+
* The pipeline calls this at the producer sites where user-controlled
|
|
64
|
+
* responses enter the framework — `outcomeToResponse` for all phase outcomes,
|
|
65
|
+
* and `handleRequest` for metadata-route and auto-sitemap user handlers — so
|
|
66
|
+
* downstream code can write headers without runtime feature-detection.
|
|
67
|
+
*
|
|
68
|
+
* The clone is unconditional. This is a deliberate trade: we avoid a
|
|
69
|
+
* try/catch + thrown `TypeError` on every request (the previous probe-based
|
|
70
|
+
* approach paid that cost on the hot path) and accept one cheap Response
|
|
71
|
+
* rewrap at the framework boundary instead.
|
|
72
|
+
*/
|
|
73
|
+
export declare function cloneWithMutableHeaders(response: Response): Response;
|
|
74
|
+
/**
|
|
75
|
+
* Build a redirect Response from a RedirectSignal.
|
|
76
|
+
*
|
|
77
|
+
* For RSC payload requests (client navigation), returns 204 + X-Timber-Redirect
|
|
78
|
+
* so the client router can perform a soft SPA redirect. A raw 302 would be
|
|
79
|
+
* turned into an opaque redirect by fetch({redirect:'manual'}), crashing
|
|
80
|
+
* createFromFetch. See design/19-client-navigation.md.
|
|
81
|
+
*/
|
|
82
|
+
export declare function buildRedirectResponse(signal: RedirectSignal, req: Request, headers: Headers): Response;
|
|
83
|
+
/**
|
|
84
|
+
* Fire the user's onRequestError hook with request context.
|
|
85
|
+
* Extracts request info from the Request object and calls the instrumentation hook.
|
|
86
|
+
*/
|
|
87
|
+
export declare function fireOnRequestError(error: unknown, req: Request, phase: 'proxy' | 'handler' | 'render' | 'action' | 'route'): Promise<void>;
|
|
88
|
+
//# sourceMappingURL=pipeline-helpers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pipeline-helpers.d.ts","sourceRoot":"","sources":["../../src/server/pipeline-helpers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAI9C,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAOjD;;;;;;;;;GASG;AACH,wBAAgB,SAAS,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAMhG;AAID;;;;GAIG;AACH,MAAM,MAAM,aAAa,GAAG,MAAM,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;AAErE;;;;;;;;;;;;GAYG;AACH,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,WAAW,GAAG,WAAW,GAAG,SAAS,GAC3C,aAAa,GAAG,IAAI,CAatB;AAID;;;GAGG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAIrD;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,GAAG,IAAI,CAO1E;AAID;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,QAAQ,GAAG,QAAQ,CAMpE;AAID;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,cAAc,EACtB,GAAG,EAAE,OAAO,EACZ,OAAO,EAAE,OAAO,GACf,QAAQ,CAQV;AAID;;;GAGG;AACH,wBAAsB,kBAAkB,CACtC,KAAK,EAAE,OAAO,EACd,GAAG,EAAE,OAAO,EACZ,KAAK,EAAE,OAAO,GAAG,SAAS,GAAG,QAAQ,GAAG,QAAQ,GAAG,OAAO,GACzD,OAAO,CAAC,IAAI,CAAC,CAYf"}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pipeline phase functions — module-level free functions that take their
|
|
3
|
+
* dependencies as explicit parameters. Each phase returns a `PhaseOutcome`
|
|
4
|
+
* (a discriminated union over response / redirect / deny / error). The
|
|
5
|
+
* terminal `outcomeToResponse` translates outcomes into Responses.
|
|
6
|
+
*
|
|
7
|
+
* Lifted out of `createPipeline` so each phase can be unit-tested in
|
|
8
|
+
* isolation. The lift is mechanical — these functions used to be closures
|
|
9
|
+
* over `config`; they now take `config` as an explicit parameter.
|
|
10
|
+
*
|
|
11
|
+
* See design/07-routing.md §"Request Lifecycle", design/02-rendering-pipeline.md §"Request Flow".
|
|
12
|
+
*/
|
|
13
|
+
import { RedirectSignal, DenySignal } from './primitives.js';
|
|
14
|
+
import { type ProxyResolver } from './pipeline-helpers.js';
|
|
15
|
+
import type { InterceptionContext, PipelineConfig, RouteMatch } from './pipeline.js';
|
|
16
|
+
export type PhaseName = 'proxy' | 'middleware' | 'render';
|
|
17
|
+
export type PhaseOutcome = {
|
|
18
|
+
kind: 'response';
|
|
19
|
+
phase: PhaseName;
|
|
20
|
+
response: Response;
|
|
21
|
+
} | {
|
|
22
|
+
kind: 'redirect';
|
|
23
|
+
phase: PhaseName;
|
|
24
|
+
signal: RedirectSignal;
|
|
25
|
+
} | {
|
|
26
|
+
kind: 'deny';
|
|
27
|
+
phase: PhaseName;
|
|
28
|
+
signal: DenySignal;
|
|
29
|
+
} | {
|
|
30
|
+
kind: 'error';
|
|
31
|
+
phase: PhaseName;
|
|
32
|
+
error: unknown;
|
|
33
|
+
};
|
|
34
|
+
export interface OutcomeContext {
|
|
35
|
+
req: Request;
|
|
36
|
+
method: string;
|
|
37
|
+
path: string;
|
|
38
|
+
responseHeaders?: Headers;
|
|
39
|
+
match?: RouteMatch;
|
|
40
|
+
}
|
|
41
|
+
interface RenderContext {
|
|
42
|
+
canonicalPathname: string;
|
|
43
|
+
interception?: InterceptionContext;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Run segment param coercion on the matched route's segments.
|
|
47
|
+
*
|
|
48
|
+
* Loads params.ts modules from segments that have them, extracts the
|
|
49
|
+
* segmentParams definition, and coerces raw string params through codecs.
|
|
50
|
+
* Throws ParamCoercionError if any codec fails (→ 404).
|
|
51
|
+
*
|
|
52
|
+
* This runs BEFORE middleware, so ctx.segmentParams is already typed.
|
|
53
|
+
* See design/07-routing.md §"Where Coercion Runs"
|
|
54
|
+
*/
|
|
55
|
+
export declare function coerceSegmentParams(match: RouteMatch): Promise<void>;
|
|
56
|
+
/**
|
|
57
|
+
* Run the proxy.ts phase. Calls user proxy code and uses `handleRequest` as
|
|
58
|
+
* the inner `next()` continuation. The proxy resolver was picked at pipeline
|
|
59
|
+
* construction time so the hot path sees no per-request branching on the
|
|
60
|
+
* `ProxyConfig` discriminant.
|
|
61
|
+
*/
|
|
62
|
+
export declare function runProxyPhase(config: PipelineConfig, getProxy: ProxyResolver, req: Request, method: string, path: string): Promise<PhaseOutcome>;
|
|
63
|
+
/**
|
|
64
|
+
* Run the middleware chain phase. If the chain short-circuits with a Response,
|
|
65
|
+
* returns it as a 'response' outcome. Otherwise applies the request header
|
|
66
|
+
* overlay and falls through to the render phase.
|
|
67
|
+
*/
|
|
68
|
+
export declare function runMiddlewarePhase(config: PipelineConfig, req: Request, match: RouteMatch, responseHeaders: Headers, requestHeaderOverlay: Headers, renderContext: RenderContext): Promise<PhaseOutcome>;
|
|
69
|
+
/**
|
|
70
|
+
* Run the render phase. Wraps the configured renderer in a span and a
|
|
71
|
+
* timing scope, and translates thrown signals into outcome variants.
|
|
72
|
+
*/
|
|
73
|
+
export declare function runRenderPhase(config: PipelineConfig, req: Request, match: RouteMatch, responseHeaders: Headers, requestHeaderOverlay: Headers, { canonicalPathname, interception }: RenderContext): Promise<PhaseOutcome>;
|
|
74
|
+
/**
|
|
75
|
+
* Process a single request from canonicalization through phase dispatch.
|
|
76
|
+
*
|
|
77
|
+
* Stages: canonicalize → metadata routes → auto-sitemap → version skew →
|
|
78
|
+
* route match → interception → early hints → param coercion → middleware →
|
|
79
|
+
* render → outcome translation. Pre-routing short-circuits return Responses
|
|
80
|
+
* directly; post-match dispatch goes through `outcomeToResponse`.
|
|
81
|
+
*
|
|
82
|
+
* Used both as the top-level entry (when no proxy.ts is configured) and as
|
|
83
|
+
* the `next()` continuation passed to `runProxy()`.
|
|
84
|
+
*/
|
|
85
|
+
export declare function handleRequest(config: PipelineConfig, req: Request, method: string, path: string): Promise<Response>;
|
|
86
|
+
/**
|
|
87
|
+
* Terminal outcome handler — converts a `PhaseOutcome` into a final
|
|
88
|
+
* `Response`, applying cookies, building redirects, rendering deny pages
|
|
89
|
+
* and fallback error pages, and firing instrumentation hooks.
|
|
90
|
+
*
|
|
91
|
+
* This is the single source of truth for how phase outputs become wire
|
|
92
|
+
* responses; the per-phase try/catch blocks now produce values, not
|
|
93
|
+
* Responses, so the conversion logic lives in exactly one place.
|
|
94
|
+
*/
|
|
95
|
+
export declare function outcomeToResponse(config: PipelineConfig, outcome: PhaseOutcome, ctx: OutcomeContext): Promise<Response>;
|
|
96
|
+
export {};
|
|
97
|
+
//# sourceMappingURL=pipeline-phases.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pipeline-phases.d.ts","sourceRoot":"","sources":["../../src/server/pipeline-phases.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAmBH,OAAO,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAM7D,OAAO,EAOL,KAAK,aAAa,EACnB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,KAAK,EAAE,mBAAmB,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAKrF,MAAM,MAAM,SAAS,GAAG,OAAO,GAAG,YAAY,GAAG,QAAQ,CAAC;AAE1D,MAAM,MAAM,YAAY,GACpB;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,KAAK,EAAE,SAAS,CAAC;IAAC,QAAQ,EAAE,QAAQ,CAAA;CAAE,GAC1D;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,KAAK,EAAE,SAAS,CAAC;IAAC,MAAM,EAAE,cAAc,CAAA;CAAE,GAC9D;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,SAAS,CAAC;IAAC,MAAM,EAAE,UAAU,CAAA;CAAE,GACtD;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,SAAS,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,CAAC;AAExD,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,OAAO,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,KAAK,CAAC,EAAE,UAAU,CAAC;CACpB;AAED,UAAU,aAAa;IACrB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,YAAY,CAAC,EAAE,mBAAmB,CAAC;CACpC;AAID;;;;;;;;;GASG;AACH,wBAAsB,mBAAmB,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CA0C1E;AAID;;;;;GAKG;AACH,wBAAsB,aAAa,CACjC,MAAM,EAAE,cAAc,EACtB,QAAQ,EAAE,aAAa,EACvB,GAAG,EAAE,OAAO,EACZ,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,YAAY,CAAC,CAavB;AAID;;;;GAIG;AACH,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,cAAc,EACtB,GAAG,EAAE,OAAO,EACZ,KAAK,EAAE,UAAU,EACjB,eAAe,EAAE,OAAO,EACxB,oBAAoB,EAAE,OAAO,EAC7B,aAAa,EAAE,aAAa,GAC3B,OAAO,CAAC,YAAY,CAAC,CA6DvB;AAID;;;GAGG;AACH,wBAAsB,cAAc,CAClC,MAAM,EAAE,cAAc,EACtB,GAAG,EAAE,OAAO,EACZ,KAAK,EAAE,UAAU,EACjB,eAAe,EAAE,OAAO,EACxB,oBAAoB,EAAE,OAAO,EAC7B,EAAE,iBAAiB,EAAE,YAAY,EAAE,EAAE,aAAa,GACjD,OAAO,CAAC,YAAY,CAAC,CAkBvB;AAID;;;;;;;;;;GAUG;AACH,wBAAsB,aAAa,CACjC,MAAM,EAAE,cAAc,EACtB,GAAG,EAAE,OAAO,EACZ,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,QAAQ,CAAC,CAsMnB;AAID;;;;;;;;GAQG;AACH,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,cAAc,EACtB,OAAO,EAAE,YAAY,EACrB,GAAG,EAAE,cAAc,GAClB,OAAO,CAAC,QAAQ,CAAC,CA2FnB"}
|
|
@@ -4,30 +4,34 @@
|
|
|
4
4
|
* Pipeline stages (in order):
|
|
5
5
|
* proxy.ts → canonicalize → route match → 103 Early Hints → middleware.ts → render
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
7
|
+
* The phase functions live in `pipeline-phases.ts` so each phase can be
|
|
8
|
+
* tested in isolation. The terminal `outcomeToResponse` translator and
|
|
9
|
+
* stateless helpers live in `pipeline-phases.ts` and `pipeline-helpers.ts`
|
|
10
|
+
* respectively. This file owns only the public type surface and the
|
|
11
|
+
* `createPipeline` entry point: trace ID setup, request-context ALS,
|
|
12
|
+
* Server-Timing wrapping, and the activeRequests counter.
|
|
9
13
|
*
|
|
10
14
|
* See design/07-routing.md §"Request Lifecycle", design/02-rendering-pipeline.md §"Request Flow",
|
|
11
15
|
* and design/17-logging.md §"Production Logging"
|
|
12
16
|
*/
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
17
|
+
import type { ProxyExport } from './proxy.js';
|
|
18
|
+
import type { MiddlewareFn } from './middleware-runner.js';
|
|
15
19
|
import { DenySignal } from './primitives.js';
|
|
16
|
-
import type {
|
|
20
|
+
import type { ManifestSegmentNode } from './route-matcher.js';
|
|
21
|
+
export { safeMerge } from './pipeline-helpers.js';
|
|
22
|
+
export { coerceSegmentParams } from './pipeline-phases.js';
|
|
17
23
|
/**
|
|
18
|
-
*
|
|
24
|
+
* Result of matching a canonical pathname against the route tree.
|
|
19
25
|
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
26
|
+
* `segments` is the runtime (`ManifestFile`-specialized) shape — the same
|
|
27
|
+
* nodes carried in the virtual route manifest. TIM-863 unified this: the
|
|
28
|
+
* matcher produces `ManifestSegmentNode[]` directly and every consumer
|
|
29
|
+
* (render, slots, params coercion, early hints, deny fallback) sees the
|
|
30
|
+
* same structural type with no `as unknown as` laundering.
|
|
25
31
|
*/
|
|
26
|
-
export declare function safeMerge(target: Record<string, unknown>, source: Record<string, unknown>): void;
|
|
27
|
-
/** Result of matching a canonical pathname against the route tree. */
|
|
28
32
|
export interface RouteMatch {
|
|
29
33
|
/** The matched segment chain from root to leaf. */
|
|
30
|
-
segments:
|
|
34
|
+
segments: ManifestSegmentNode[];
|
|
31
35
|
/** Extracted segment params (catch-all segments produce string[]). */
|
|
32
36
|
segmentParams: Record<string, string | string[]>;
|
|
33
37
|
/** Middleware chain from the segment tree, ordered root-to-leaf. */
|
|
@@ -46,13 +50,37 @@ export interface InterceptionContext {
|
|
|
46
50
|
export type RouteRenderer = (req: Request, match: RouteMatch, responseHeaders: Headers, requestHeaderOverlay: Headers, interception?: InterceptionContext) => Response | Promise<Response>;
|
|
47
51
|
/** Function that sends 103 Early Hints for a matched route. */
|
|
48
52
|
export type EarlyHintsEmitter = (match: RouteMatch, req: Request, responseHeaders: Headers) => void | Promise<void>;
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
53
|
+
/**
|
|
54
|
+
* Proxy source — a tagged union so the choice between "already-resolved
|
|
55
|
+
* export" and "lazy HMR-friendly loader" is encoded in the type, not
|
|
56
|
+
* inferred per-request.
|
|
57
|
+
*
|
|
58
|
+
* - `static` — the proxy export is already resolved (production, tests).
|
|
59
|
+
* - `lazy` — a loader is called per-request for HMR freshness (dev).
|
|
60
|
+
*
|
|
61
|
+
* `PipelineConfig.proxy` also accepts a bare `ProxyExport` (a function or
|
|
62
|
+
* function array) as shorthand for the static variant — convenient for tests
|
|
63
|
+
* that construct a `createPipeline` config inline. Omit the field entirely
|
|
64
|
+
* when the app has no `proxy.ts`.
|
|
65
|
+
*
|
|
66
|
+
* See design/07-routing.md §"proxy.ts — Global Middleware".
|
|
67
|
+
*/
|
|
68
|
+
export type ProxyConfig = {
|
|
69
|
+
kind: 'static';
|
|
70
|
+
export: ProxyExport;
|
|
71
|
+
} | {
|
|
72
|
+
kind: 'lazy';
|
|
73
|
+
loader: () => Promise<{
|
|
54
74
|
default: ProxyExport;
|
|
55
75
|
}>;
|
|
76
|
+
};
|
|
77
|
+
export interface PipelineConfig {
|
|
78
|
+
/**
|
|
79
|
+
* proxy.ts source. Undefined if the app has no proxy.ts. Accepts either a
|
|
80
|
+
* tagged `ProxyConfig` (canonical) or a bare `ProxyExport` as sugar for the
|
|
81
|
+
* static variant.
|
|
82
|
+
*/
|
|
83
|
+
proxy?: ProxyConfig | ProxyExport;
|
|
56
84
|
/** Route matcher — resolves a canonical pathname to a RouteMatch. */
|
|
57
85
|
matchRoute: RouteMatcher;
|
|
58
86
|
/** Metadata route matcher — resolves metadata route pathnames (sitemap.xml, robots.txt, etc.) */
|
|
@@ -130,22 +158,15 @@ export interface PipelineConfig {
|
|
|
130
158
|
*/
|
|
131
159
|
match?: RouteMatch) => Response | Promise<Response>;
|
|
132
160
|
}
|
|
133
|
-
/**
|
|
134
|
-
* Run segment param coercion on the matched route's segments.
|
|
135
|
-
*
|
|
136
|
-
* Loads params.ts modules from segments that have them, extracts the
|
|
137
|
-
* segmentParams definition, and coerces raw string params through codecs.
|
|
138
|
-
* Throws ParamCoercionError if any codec fails (→ 404).
|
|
139
|
-
*
|
|
140
|
-
* This runs BEFORE middleware, so ctx.segmentParams is already typed.
|
|
141
|
-
* See design/07-routing.md §"Where Coercion Runs"
|
|
142
|
-
*/
|
|
143
|
-
export declare function coerceSegmentParams(match: RouteMatch): Promise<void>;
|
|
144
161
|
/**
|
|
145
162
|
* Create the request handler from a pipeline configuration.
|
|
146
163
|
*
|
|
147
|
-
* Returns a function that processes an incoming Request through all pipeline
|
|
148
|
-
* and produces a Response. This is the top-level entry point for the
|
|
164
|
+
* Returns a function that processes an incoming Request through all pipeline
|
|
165
|
+
* stages and produces a Response. This is the top-level entry point for the
|
|
166
|
+
* server. The body is intentionally small — phase logic lives in
|
|
167
|
+
* `pipeline-phases.ts`. This function only owns the per-request setup that
|
|
168
|
+
* has to wrap the entire dispatch: trace ID, request context ALS, span
|
|
169
|
+
* scope, Server-Timing header emission, and the active-request counter.
|
|
149
170
|
*/
|
|
150
171
|
export declare function createPipeline(config: PipelineConfig): (req: Request) => Promise<Response>;
|
|
151
172
|
//# sourceMappingURL=pipeline.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pipeline.d.ts","sourceRoot":"","sources":["../../src/server/pipeline.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"pipeline.d.ts","sourceRoot":"","sources":["../../src/server/pipeline.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Port resolution for the dev server, Vite preview, and the Node
|
|
3
|
+
* production preview server.
|
|
4
|
+
*
|
|
5
|
+
* Behavior (see TIM-842):
|
|
6
|
+
*
|
|
7
|
+
* 1. Default port is **3000** for both dev and prod.
|
|
8
|
+
* 2. If the user did NOT set an explicit port, auto-bump from 3000
|
|
9
|
+
* until a free port is found (3000 → 3001 → 3002 → …).
|
|
10
|
+
* 3. If the user DID set an explicit port (via `--port`, `PORT` env
|
|
11
|
+
* var, or `vite.config.ts` `server.port`), use it as-is and let
|
|
12
|
+
* the bind fail loudly on conflict (`strictPort: true`).
|
|
13
|
+
*
|
|
14
|
+
* The port-bump probe is performed by binding the **actual** server
|
|
15
|
+
* (e.g. the dev holding server) — not a throwaway probe — so there is
|
|
16
|
+
* no time-of-check / time-of-use race between probing and listening.
|
|
17
|
+
*
|
|
18
|
+
* Design doc: 21-dev-server.md §"Default Port and Auto-Bump".
|
|
19
|
+
*/
|
|
20
|
+
/** Default port used by `timber dev` and `timber preview`. */
|
|
21
|
+
export declare const DEFAULT_PORT = 3000;
|
|
22
|
+
/**
|
|
23
|
+
* A function that attempts to bind to a given port.
|
|
24
|
+
*
|
|
25
|
+
* Resolves with the bound port on success. Rejects with an error
|
|
26
|
+
* (typically with `code === 'EADDRINUSE'`) on failure.
|
|
27
|
+
*/
|
|
28
|
+
export type ListenFn = (port: number) => Promise<number>;
|
|
29
|
+
export interface ResolvePortInput {
|
|
30
|
+
/**
|
|
31
|
+
* Port read from `vite.config.ts` `server.port` or the `--port` CLI
|
|
32
|
+
* flag (Vite merges `--port` into `userConfig.server.port` before
|
|
33
|
+
* plugins see it).
|
|
34
|
+
*/
|
|
35
|
+
configPort?: number | undefined;
|
|
36
|
+
/** Raw value of `process.env.PORT`. */
|
|
37
|
+
envPort?: string | undefined;
|
|
38
|
+
/** Default port to use when neither `configPort` nor `envPort` is set. */
|
|
39
|
+
defaultPort?: number;
|
|
40
|
+
}
|
|
41
|
+
export interface ResolvedPortInput {
|
|
42
|
+
/** Port to start binding from. */
|
|
43
|
+
port: number;
|
|
44
|
+
/**
|
|
45
|
+
* `true` if the port came from an explicit user override (config or
|
|
46
|
+
* env). When `true`, callers must NOT auto-bump and must surface
|
|
47
|
+
* `EADDRINUSE` to the user.
|
|
48
|
+
*/
|
|
49
|
+
explicit: boolean;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Pure: compute the starting port from config / env / defaults.
|
|
53
|
+
*
|
|
54
|
+
* Performs no I/O — pair with {@link bindWithBump} to actually listen.
|
|
55
|
+
*/
|
|
56
|
+
export declare function resolveStartPort(input: ResolvePortInput): ResolvedPortInput;
|
|
57
|
+
export interface BindWithBumpOptions {
|
|
58
|
+
/** First port to attempt. */
|
|
59
|
+
startPort: number;
|
|
60
|
+
/**
|
|
61
|
+
* If `true`, increment the port and retry on `EADDRINUSE`. If
|
|
62
|
+
* `false`, attempt once and let the error propagate.
|
|
63
|
+
*/
|
|
64
|
+
autoBump: boolean;
|
|
65
|
+
/** Maximum number of port attempts when `autoBump` is `true`. */
|
|
66
|
+
maxAttempts?: number;
|
|
67
|
+
}
|
|
68
|
+
export interface BindWithBumpResult {
|
|
69
|
+
/** Port that was actually bound. */
|
|
70
|
+
port: number;
|
|
71
|
+
/** `true` if the bound port differs from `startPort`. */
|
|
72
|
+
bumped: boolean;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Bind a server starting at `startPort`, optionally bumping the port
|
|
76
|
+
* on `EADDRINUSE` until a free one is found.
|
|
77
|
+
*
|
|
78
|
+
* Use this with the actual server you intend to keep listening (e.g.
|
|
79
|
+
* the dev holding server). Pairing the probe with the real listen
|
|
80
|
+
* eliminates the TOCTOU race that a throwaway probe would introduce.
|
|
81
|
+
*/
|
|
82
|
+
export declare function bindWithBump(listen: ListenFn, options: BindWithBumpOptions): Promise<BindWithBumpResult>;
|
|
83
|
+
/** True if `err` is a Node `EADDRINUSE` error from `server.listen()`. */
|
|
84
|
+
export declare function isAddrInUse(err: unknown): boolean;
|
|
85
|
+
export interface StartDevServerPortInput {
|
|
86
|
+
/** Resolved port from `userConfig.server?.port` (or `--port`), or undefined. */
|
|
87
|
+
configPort: number | undefined;
|
|
88
|
+
/** Raw `process.env.PORT` value. */
|
|
89
|
+
envPort: string | undefined;
|
|
90
|
+
/** ListenFn for the holding server (or any pre-bind probe target). */
|
|
91
|
+
listen: ListenFn;
|
|
92
|
+
/** Logger — defaults to `console`. Injected for tests. */
|
|
93
|
+
log?: (msg: string) => void;
|
|
94
|
+
warn?: (msg: string) => void;
|
|
95
|
+
}
|
|
96
|
+
export interface StartDevServerPortResult {
|
|
97
|
+
/** Port chosen for both the holding server and Vite's dev server. */
|
|
98
|
+
port: number;
|
|
99
|
+
/** True if the user explicitly set the port (=> Vite must `strictPort: true`). */
|
|
100
|
+
explicit: boolean;
|
|
101
|
+
/** True if the holding server actually bound the port. */
|
|
102
|
+
bound: boolean;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Run the full dev-server port resolution + holding-server bind sequence.
|
|
106
|
+
*
|
|
107
|
+
* Resolves the port from config / env / default, attempts to bind the
|
|
108
|
+
* holding server (auto-bumping when the port came from the default),
|
|
109
|
+
* and logs the chosen URL. On a clean failure for an explicit port, it
|
|
110
|
+
* warns and falls back to the requested port so Vite can surface the
|
|
111
|
+
* conflict via `strictPort: true`.
|
|
112
|
+
*
|
|
113
|
+
* Extracted from `index.ts` so the rootSync `config()` hook stays
|
|
114
|
+
* focused on plugin assembly.
|
|
115
|
+
*/
|
|
116
|
+
export declare function startDevServerPort(input: StartDevServerPortInput): Promise<StartDevServerPortResult>;
|
|
117
|
+
//# sourceMappingURL=port-resolution.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"port-resolution.d.ts","sourceRoot":"","sources":["../../src/server/port-resolution.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,8DAA8D;AAC9D,eAAO,MAAM,YAAY,OAAO,CAAC;AAEjC;;;;;GAKG;AACH,MAAM,MAAM,QAAQ,GAAG,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;AAIzD,MAAM,WAAW,gBAAgB;IAC/B;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAChC,uCAAuC;IACvC,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7B,0EAA0E;IAC1E,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,iBAAiB;IAChC,kCAAkC;IAClC,IAAI,EAAE,MAAM,CAAC;IACb;;;;OAIG;IACH,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,gBAAgB,GAAG,iBAAiB,CAiB3E;AAID,MAAM,WAAW,mBAAmB;IAClC,6BAA6B;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB;;;OAGG;IACH,QAAQ,EAAE,OAAO,CAAC;IAClB,iEAAiE;IACjE,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,kBAAkB;IACjC,oCAAoC;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,yDAAyD;IACzD,MAAM,EAAE,OAAO,CAAC;CACjB;AAED;;;;;;;GAOG;AACH,wBAAsB,YAAY,CAChC,MAAM,EAAE,QAAQ,EAChB,OAAO,EAAE,mBAAmB,GAC3B,OAAO,CAAC,kBAAkB,CAAC,CAkB7B;AAED,yEAAyE;AACzE,wBAAgB,WAAW,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAIjD;AAID,MAAM,WAAW,uBAAuB;IACtC,gFAAgF;IAChF,UAAU,EAAE,MAAM,GAAG,SAAS,CAAC;IAC/B,oCAAoC;IACpC,OAAO,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B,sEAAsE;IACtE,MAAM,EAAE,QAAQ,CAAC;IACjB,0DAA0D;IAC1D,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5B,IAAI,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;CAC9B;AAED,MAAM,WAAW,wBAAwB;IACvC,qEAAqE;IACrE,IAAI,EAAE,MAAM,CAAC;IACb,kFAAkF;IAClF,QAAQ,EAAE,OAAO,CAAC;IAClB,0DAA0D;IAC1D,KAAK,EAAE,OAAO,CAAC;CAChB;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,kBAAkB,CACtC,KAAK,EAAE,uBAAuB,GAC7B,OAAO,CAAC,wBAAwB,CAAC,CAqCnC"}
|