@rangojs/router 0.0.0-experimental.150 → 0.0.0-experimental.152

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.
@@ -206,6 +206,11 @@ export async function matchForPrerender<TEnv = any>(
206
206
  pathname,
207
207
  searchParams: new URLSearchParams(),
208
208
  _variables: variables,
209
+ build: true,
210
+ // Inert here: the prerender-collect / static-render pass has no live
211
+ // PPR-shell decision to gate; dynamic() reaches the shell axis only on a
212
+ // live request or a shell capture.
213
+ dynamic: () => {},
209
214
  get: ((keyOrVar: any) => contextGet(variables, keyOrVar)) as any,
210
215
  set: ((keyOrVar: any, value: any) => {
211
216
  contextSet(variables, keyOrVar, value);
@@ -476,6 +481,8 @@ export async function renderStaticSegment<TEnv = any>(
476
481
  pathname: "/",
477
482
  searchParams: syntheticUrl.searchParams,
478
483
  _variables: {},
484
+ build: true,
485
+ dynamic: () => {},
479
486
  get: () => undefined as any,
480
487
  set: () => {},
481
488
  params: {},
@@ -366,12 +366,6 @@ export interface RangoInternal<
366
366
  */
367
367
  readonly telemetry?: TelemetrySink;
368
368
 
369
- /**
370
- * Whether ?__debug_manifest is allowed in production.
371
- * Always enabled in development.
372
- */
373
- readonly allowDebugManifest: boolean;
374
-
375
369
  /**
376
370
  * Resolved timeout configuration (merged from shorthand + structured).
377
371
  */
@@ -126,14 +126,6 @@ export interface RangoOptions<TEnv = any> {
126
126
  */
127
127
  debugPerformance?: boolean;
128
128
 
129
- /**
130
- * Allow the `?__debug_manifest` query parameter to return route manifest data as JSON.
131
- * In development mode this is always enabled regardless of this setting.
132
- * Defaults to false. Set to true to enable in production.
133
- * @internal
134
- */
135
- allowDebugManifest?: boolean;
136
-
137
129
  /**
138
130
  * DEVELOPMENT/TEST ONLY. Emit an `X-Rango-Cache` response header describing
139
131
  * the cache status of the matched route, for use by testing primitives such
package/src/router.ts CHANGED
@@ -158,7 +158,6 @@ export function createRouter<TEnv = any>(
158
158
  prefetchConcurrency: prefetchConcurrencyOption,
159
159
  stateCookiePrefix: stateCookiePrefixOption,
160
160
  warmup: warmupOption,
161
- allowDebugManifest: allowDebugManifestOption = false,
162
161
  telemetry: telemetrySink,
163
162
  tracing: tracingOption,
164
163
  ssr: ssrOption,
@@ -1021,9 +1020,6 @@ export function createRouter<TEnv = any>(
1021
1020
  // undefined when unconfigured and call sites gate on truthiness.
1022
1021
  telemetry: telemetrySink,
1023
1022
 
1024
- // Expose debug manifest flag for handler
1025
- allowDebugManifest: allowDebugManifestOption,
1026
-
1027
1023
  // Expose origin check configuration for handler (default: enabled)
1028
1024
  originCheck: originCheckOption ?? true,
1029
1025
 
@@ -25,10 +25,14 @@ import type {
25
25
  SSRModule,
26
26
  } from "./types.js";
27
27
  import {
28
+ RSC_FLIGHT_HTML_PHASES,
29
+ RSC_FLIGHT_ONLY_PHASES,
28
30
  createResponseWithMergedHeaders,
29
31
  finalizeResponse,
30
32
  interceptRedirectForPartial,
31
33
  buildRouteMiddlewareEntries,
34
+ observeRscHtmlStage,
35
+ renderRscFlightStage,
32
36
  } from "./helpers.js";
33
37
  import { guardOutgoingRedirect } from "./redirect-guard.js";
34
38
  import { resolvedHandleStream } from "../handles/deferred-resolution.js";
@@ -52,8 +56,6 @@ import {
52
56
  import { contextSet } from "../context-var.js";
53
57
  import {
54
58
  hasCachedManifest,
55
- getRouteTrie,
56
- getPrecomputedEntries,
57
59
  waitForManifestReady,
58
60
  getRouterManifest,
59
61
  getRouterTrie,
@@ -301,17 +303,26 @@ export function createRSCHandler<
301
303
  ...(locationState && { locationState }),
302
304
  },
303
305
  };
304
- const rscStream = renderToReadableStream<RscPayload>(redirectPayload, {
305
- onError: (error: unknown) => {
306
- const reqCtx = _getRequestContext<TEnv>();
307
- if (!reqCtx) return;
308
- callOnError(error, "rendering", {
309
- request: reqCtx.request,
310
- url: reqCtx.url,
311
- env: reqCtx.env,
312
- });
313
- },
314
- });
306
+ const reqCtx = _getRequestContext<TEnv>();
307
+ const rscStream = reqCtx
308
+ ? renderRscFlightStage(
309
+ {
310
+ ctx: { renderToReadableStream, callOnError },
311
+ request: reqCtx.request,
312
+ url: reqCtx.url,
313
+ env: reqCtx.env,
314
+ payload: redirectPayload,
315
+ tracking: {
316
+ mode: reqCtx.url.searchParams.has("_rsc_action")
317
+ ? "action-revalidation"
318
+ : "partial",
319
+ routeKey: reqCtx._routeName,
320
+ phases: RSC_FLIGHT_ONLY_PHASES,
321
+ },
322
+ },
323
+ performance.now(),
324
+ ).stream
325
+ : renderToReadableStream<RscPayload>(redirectPayload);
315
326
  return createResponseWithMergedHeaders(rscStream, {
316
327
  status: 200,
317
328
  headers: { "content-type": "text/x-component;charset=utf-8" },
@@ -632,35 +643,6 @@ export function createRSCHandler<
632
643
  ): Promise<Response> {
633
644
  const handlerTiming: string[] = variables.__handlerTiming || [];
634
645
 
635
- // Debug manifest endpoint: handled before classification since it
636
- // doesn't need a route match and needs trie access from the closure.
637
- const isDev = process.env.NODE_ENV !== "production";
638
- if (
639
- url.searchParams.has("__debug_manifest") &&
640
- (isDev || router.allowDebugManifest)
641
- ) {
642
- const trie = getRouterTrie(router.id) ?? getRouteTrie();
643
- const routeManifest = getRequiredRouteMap();
644
- const { extractAncestryFromTrie } =
645
- await import("../build/route-trie.js");
646
- return new Response(
647
- JSON.stringify(
648
- {
649
- routerId: router.id,
650
- routeManifest,
651
- routeAncestry: trie ? extractAncestryFromTrie(trie) : {},
652
- routeTrie: trie,
653
- precomputedEntries: getPrecomputedEntries(),
654
- },
655
- null,
656
- 2,
657
- ),
658
- {
659
- headers: { "Content-Type": "application/json" },
660
- },
661
- );
662
- }
663
-
664
646
  // ---- 1. Classify ----
665
647
  // classifyRequest may throw RouteNotFoundError for unknown routes.
666
648
  // In that case, fall through to a full-render plan so the pipeline
@@ -1171,13 +1153,31 @@ export function createRSCHandler<
1171
1153
  },
1172
1154
  };
1173
1155
 
1174
- const rscStream = renderToReadableStream(payload, {
1175
- onError: (error: unknown) => {
1176
- callOnError(error, "rendering", { request, url, env });
1156
+ const isNotFoundFlightResponse = isRscRequest(
1157
+ request,
1158
+ url,
1159
+ isPartial,
1160
+ );
1161
+ const notFoundStageTracking = {
1162
+ mode: isPartial ? ("partial" as const) : ("full" as const),
1163
+ routeKey,
1164
+ phases: isNotFoundFlightResponse
1165
+ ? RSC_FLIGHT_ONLY_PHASES
1166
+ : RSC_FLIGHT_HTML_PHASES,
1167
+ };
1168
+ const rscStream = renderRscFlightStage(
1169
+ {
1170
+ ctx: { renderToReadableStream, callOnError },
1171
+ request,
1172
+ url,
1173
+ env,
1174
+ payload,
1175
+ tracking: notFoundStageTracking,
1177
1176
  },
1178
- });
1177
+ performance.now(),
1178
+ ).stream;
1179
1179
 
1180
- if (isRscRequest(request, url, isPartial)) {
1180
+ if (isNotFoundFlightResponse) {
1181
1181
  return createResponseWithMergedHeaders(rscStream, {
1182
1182
  status: 404,
1183
1183
  headers: {
@@ -1196,10 +1196,14 @@ export function createRSCHandler<
1196
1196
  url,
1197
1197
  getRequestContext()._metricsStore,
1198
1198
  );
1199
- const htmlStream = await ssrModule.renderHTML(rscStream, {
1200
- nonce,
1201
- streamMode,
1202
- });
1199
+ const htmlStream = await observeRscHtmlStage(
1200
+ { url, tracking: notFoundStageTracking },
1201
+ () =>
1202
+ ssrModule.renderHTML(rscStream, {
1203
+ nonce,
1204
+ streamMode,
1205
+ }),
1206
+ );
1203
1207
 
1204
1208
  return createResponseWithMergedHeaders(htmlStream, {
1205
1209
  status: 404,
@@ -18,7 +18,468 @@ import {
18
18
  } from "../redirect-origin.js";
19
19
  import type { MiddlewareEntry, MiddlewareFn } from "../router/middleware.js";
20
20
  import { formatCacheSignalHeader } from "../router/telemetry.js";
21
+ import { appendMetric } from "../router/metrics.js";
21
22
  import type { RscPayload } from "./types.js";
23
+ import type { HandlerContext } from "./handler-context.js";
24
+
25
+ export interface RscRenderStageInput<TEnv> {
26
+ ctx: Pick<HandlerContext<TEnv>, "renderToReadableStream" | "callOnError">;
27
+ request: Request;
28
+ url: URL;
29
+ env: TEnv;
30
+ payload: RscPayload;
31
+ init: ResponseInit;
32
+ temporaryReferences?: unknown;
33
+ recordSerializeMetric?: boolean;
34
+ tracking?: RscRenderStageTracking;
35
+ }
36
+
37
+ export type RscRenderMode =
38
+ | "unknown"
39
+ | "full"
40
+ | "partial"
41
+ | "action-revalidation"
42
+ | "progressive-enhancement"
43
+ | "progressive-enhancement-error";
44
+
45
+ export type RscRenderPhase = "payload" | "flight" | "html" | "response";
46
+
47
+ export const RSC_RENDER_FLIGHT_RESPONSE_PHASES: readonly RscRenderPhase[] = [
48
+ "payload",
49
+ "flight",
50
+ "response",
51
+ ];
52
+
53
+ export const RSC_RENDER_HTML_RESPONSE_PHASES: readonly RscRenderPhase[] = [
54
+ "payload",
55
+ "flight",
56
+ "html",
57
+ "response",
58
+ ];
59
+
60
+ export const RSC_FLIGHT_ONLY_PHASES: readonly RscRenderPhase[] = ["flight"];
61
+
62
+ export const RSC_FLIGHT_HTML_PHASES: readonly RscRenderPhase[] = [
63
+ "flight",
64
+ "html",
65
+ ];
66
+
67
+ export interface RscRenderStageProgress {
68
+ completed: number;
69
+ total: number;
70
+ }
71
+
72
+ export interface RscRenderStageContext {
73
+ mode: RscRenderMode;
74
+ phase: RscRenderPhase;
75
+ pathname: string;
76
+ progress: RscRenderStageProgress;
77
+ startedAt: number;
78
+ phaseStartedAt: number;
79
+ routeKey?: string;
80
+ actionId?: string;
81
+ }
82
+
83
+ export type RscRenderStageEvent =
84
+ | {
85
+ type: "stage:yield";
86
+ context: RscRenderStageContext;
87
+ }
88
+ | {
89
+ type: "stage:start";
90
+ context: RscRenderStageContext;
91
+ }
92
+ | {
93
+ type: "stage:complete";
94
+ context: RscRenderStageContext;
95
+ durationMs: number;
96
+ }
97
+ | {
98
+ type: "stage:error";
99
+ context: RscRenderStageContext;
100
+ durationMs: number;
101
+ error: unknown;
102
+ };
103
+
104
+ export interface RscRenderStageTracking {
105
+ mode?: RscRenderMode;
106
+ routeKey?: string;
107
+ actionId?: string;
108
+ phases?: readonly RscRenderPhase[];
109
+ totalStages?: number;
110
+ onEvent?: (event: RscRenderStageEvent) => void;
111
+ }
112
+
113
+ export interface RscFlightStageInput<TEnv> {
114
+ ctx: Pick<HandlerContext<TEnv>, "renderToReadableStream" | "callOnError">;
115
+ request: Request;
116
+ url: URL;
117
+ env: TEnv;
118
+ payload: RscPayload;
119
+ temporaryReferences?: unknown;
120
+ recordSerializeMetric?: boolean;
121
+ tracking?: RscRenderStageTracking;
122
+ }
123
+
124
+ export interface RscHtmlStageInput {
125
+ url: URL;
126
+ tracking?: RscRenderStageTracking;
127
+ }
128
+
129
+ export type RscRenderStage =
130
+ | {
131
+ type: "payload";
132
+ payload: RscPayload;
133
+ init: ResponseInit;
134
+ context: RscRenderStageContext;
135
+ }
136
+ | {
137
+ type: "flight";
138
+ payload: RscPayload;
139
+ stream: ReadableStream<Uint8Array>;
140
+ init: ResponseInit;
141
+ durationMs: number;
142
+ context: RscRenderStageContext;
143
+ };
144
+
145
+ export type RscFlightStage = Extract<RscRenderStage, { type: "flight" }>;
146
+
147
+ export interface RscRenderStageControl {
148
+ payload?: RscPayload;
149
+ init?: ResponseInit;
150
+ body?: BodyInit | null;
151
+ }
152
+
153
+ interface RscFlightStageResult {
154
+ stage: RscFlightStage;
155
+ control: RscRenderStageControl | undefined;
156
+ }
157
+
158
+ type RscRenderStageSink = NonNullable<RscRenderStageTracking["onEvent"]>;
159
+
160
+ function rscStagePhases(
161
+ tracking: RscRenderStageTracking | undefined,
162
+ ): readonly RscRenderPhase[] {
163
+ const phases = tracking?.phases;
164
+ if (phases && phases.length > 0) return phases;
165
+ const total = tracking?.totalStages;
166
+ if (total === 1) return RSC_FLIGHT_ONLY_PHASES;
167
+ if (total === 2) return RSC_FLIGHT_HTML_PHASES;
168
+ if (total === 4) return RSC_RENDER_HTML_RESPONSE_PHASES;
169
+ return RSC_RENDER_FLIGHT_RESPONSE_PHASES;
170
+ }
171
+
172
+ function createRscStageContext(
173
+ input: { url: URL; tracking?: RscRenderStageTracking },
174
+ phase: RscRenderPhase,
175
+ startedAt: number,
176
+ phaseStartedAt: number,
177
+ ): RscRenderStageContext {
178
+ const phases = rscStagePhases(input.tracking);
179
+ const phaseIndex = phases.indexOf(phase);
180
+ const total = phases.length;
181
+ const completed = phaseIndex >= 0 ? phaseIndex + 1 : total;
182
+ return {
183
+ mode: input.tracking?.mode ?? "unknown",
184
+ phase,
185
+ pathname: input.url.pathname,
186
+ progress: { completed, total },
187
+ startedAt,
188
+ phaseStartedAt,
189
+ ...(input.tracking?.routeKey && { routeKey: input.tracking.routeKey }),
190
+ ...(input.tracking?.actionId && { actionId: input.tracking.actionId }),
191
+ };
192
+ }
193
+
194
+ function getRscStageSink(input: {
195
+ tracking?: RscRenderStageTracking;
196
+ }): RscRenderStageSink | undefined {
197
+ return input.tracking?.onEvent;
198
+ }
199
+
200
+ function emitRscStageEvent(
201
+ sink: RscRenderStageSink | undefined,
202
+ createEvent: () => RscRenderStageEvent,
203
+ ): void {
204
+ if (!sink) return;
205
+ try {
206
+ sink(createEvent());
207
+ } catch (error) {
208
+ if (process.env.NODE_ENV !== "production") {
209
+ console.error("[RSC] render stage event sink failed:", error);
210
+ }
211
+ }
212
+ }
213
+
214
+ export function renderRscFlightStage<TEnv>(
215
+ input: RscFlightStageInput<TEnv>,
216
+ startedAt: number,
217
+ init: ResponseInit = {},
218
+ ): RscFlightStage {
219
+ const phaseStartedAt = performance.now();
220
+ const context = createRscStageContext(
221
+ input,
222
+ "flight",
223
+ startedAt,
224
+ phaseStartedAt,
225
+ );
226
+ const sink = getRscStageSink(input);
227
+ emitRscStageEvent(sink, () => ({ type: "stage:start", context }));
228
+
229
+ try {
230
+ const stream = input.ctx.renderToReadableStream<RscPayload>(input.payload, {
231
+ temporaryReferences: input.temporaryReferences,
232
+ onError: (error: unknown) => {
233
+ input.ctx.callOnError(error, "rendering", {
234
+ request: input.request,
235
+ url: input.url,
236
+ env: input.env,
237
+ });
238
+ },
239
+ });
240
+ const durationMs = performance.now() - phaseStartedAt;
241
+ if (input.recordSerializeMetric === true) {
242
+ appendMetric(
243
+ _getRequestContext()?._metricsStore,
244
+ "rsc-serialize",
245
+ phaseStartedAt,
246
+ durationMs,
247
+ );
248
+ }
249
+ emitRscStageEvent(sink, () => ({
250
+ type: "stage:complete",
251
+ context,
252
+ durationMs,
253
+ }));
254
+
255
+ return {
256
+ type: "flight" as const,
257
+ payload: input.payload,
258
+ stream,
259
+ init,
260
+ durationMs,
261
+ context,
262
+ };
263
+ } catch (error) {
264
+ emitRscStageEvent(sink, () => ({
265
+ type: "stage:error",
266
+ context,
267
+ durationMs: performance.now() - phaseStartedAt,
268
+ error,
269
+ }));
270
+ throw error;
271
+ }
272
+ }
273
+
274
+ async function* createRscFlightStages<TEnv>(
275
+ input: RscRenderStageInput<TEnv>,
276
+ payload: RscPayload,
277
+ init: ResponseInit,
278
+ startedAt: number,
279
+ ): AsyncGenerator<
280
+ RscFlightStage,
281
+ RscFlightStageResult,
282
+ RscRenderStageControl | undefined
283
+ > {
284
+ const stage = renderRscFlightStage(
285
+ {
286
+ ctx: input.ctx,
287
+ request: input.request,
288
+ url: input.url,
289
+ env: input.env,
290
+ payload,
291
+ temporaryReferences: input.temporaryReferences,
292
+ recordSerializeMetric: input.recordSerializeMetric ?? true,
293
+ tracking: input.tracking,
294
+ },
295
+ startedAt,
296
+ init,
297
+ );
298
+ const sink = getRscStageSink(input);
299
+ emitRscStageEvent(sink, () => ({
300
+ type: "stage:yield",
301
+ context: stage.context,
302
+ }));
303
+ const control = yield stage;
304
+ return { stage, control };
305
+ }
306
+
307
+ export async function observeRscHtmlStage<T>(
308
+ input: RscHtmlStageInput,
309
+ fn: () => Promise<T>,
310
+ ): Promise<T> {
311
+ const startedAt = performance.now();
312
+ const sink = getRscStageSink(input);
313
+ const context = sink
314
+ ? createRscStageContext(input, "html", startedAt, startedAt)
315
+ : undefined;
316
+ emitRscStageEvent(sink, () => ({
317
+ type: "stage:start",
318
+ context: context!,
319
+ }));
320
+ try {
321
+ const result = await fn();
322
+ emitRscStageEvent(sink, () => ({
323
+ type: "stage:complete",
324
+ context: context!,
325
+ durationMs: performance.now() - startedAt,
326
+ }));
327
+ return result;
328
+ } catch (error) {
329
+ emitRscStageEvent(sink, () => ({
330
+ type: "stage:error",
331
+ context: context!,
332
+ durationMs: performance.now() - startedAt,
333
+ error,
334
+ }));
335
+ throw error;
336
+ }
337
+ }
338
+
339
+ export function createRscStageDebugSink(
340
+ log: (message: string, details?: Record<string, unknown>) => void = (
341
+ message,
342
+ details,
343
+ ) => console.debug(message, details),
344
+ ): (event: RscRenderStageEvent) => void {
345
+ return (event) => {
346
+ const { context } = event;
347
+ log(`[RSC][stage] ${event.type} ${context.phase}`, {
348
+ mode: context.mode,
349
+ pathname: context.pathname,
350
+ routeKey: context.routeKey,
351
+ actionId: context.actionId,
352
+ progress: `${context.progress.completed}/${context.progress.total}`,
353
+ ...("durationMs" in event && { durationMs: event.durationMs }),
354
+ ...("error" in event && { error: event.error }),
355
+ });
356
+ };
357
+ }
358
+
359
+ /**
360
+ * Stage the common RSC render flow as a resumable async generator:
361
+ * payload inspection/mutation -> Flight stream creation -> response finalization.
362
+ * HTML callers pause at the Flight stage, render SSR from that stream, then
363
+ * resume with the HTML body/init so header merging still happens once.
364
+ */
365
+ export async function* createRscRenderStages<TEnv>(
366
+ input: RscRenderStageInput<TEnv>,
367
+ ): AsyncGenerator<RscRenderStage, Response, RscRenderStageControl | undefined> {
368
+ const startedAt = performance.now();
369
+ let payload = input.payload;
370
+ let init = input.init;
371
+
372
+ const payloadContext = createRscStageContext(
373
+ input,
374
+ "payload",
375
+ startedAt,
376
+ startedAt,
377
+ );
378
+ const sink = getRscStageSink(input);
379
+ emitRscStageEvent(sink, () => ({
380
+ type: "stage:yield",
381
+ context: payloadContext,
382
+ }));
383
+ const payloadControl = yield {
384
+ type: "payload" as const,
385
+ payload,
386
+ init,
387
+ context: payloadContext,
388
+ };
389
+ payload = payloadControl?.payload ?? payload;
390
+ init = payloadControl?.init ?? init;
391
+
392
+ const flightResult = yield* createRscFlightStages(
393
+ input,
394
+ payload,
395
+ init,
396
+ startedAt,
397
+ );
398
+ const flightControl = flightResult.control;
399
+
400
+ const body =
401
+ flightControl && "body" in flightControl
402
+ ? flightControl.body!
403
+ : flightResult.stage.stream;
404
+
405
+ const phaseStartedAt = performance.now();
406
+ const responseContext = sink
407
+ ? createRscStageContext(input, "response", startedAt, phaseStartedAt)
408
+ : undefined;
409
+ emitRscStageEvent(sink, () => ({
410
+ type: "stage:start",
411
+ context: responseContext!,
412
+ }));
413
+ try {
414
+ const response = createResponseWithMergedHeaders(body, {
415
+ ...init,
416
+ ...flightControl?.init,
417
+ });
418
+ emitRscStageEvent(sink, () => ({
419
+ type: "stage:complete",
420
+ context: responseContext!,
421
+ durationMs: performance.now() - phaseStartedAt,
422
+ }));
423
+ return response;
424
+ } catch (error) {
425
+ emitRscStageEvent(sink, () => ({
426
+ type: "stage:error",
427
+ context: responseContext!,
428
+ durationMs: performance.now() - phaseStartedAt,
429
+ error,
430
+ }));
431
+ throw error;
432
+ }
433
+ }
434
+
435
+ export async function runRscRenderStages(
436
+ stages: AsyncGenerator<
437
+ RscRenderStage,
438
+ Response,
439
+ RscRenderStageControl | undefined
440
+ >,
441
+ ): Promise<Response> {
442
+ for (;;) {
443
+ const step = await stages.next();
444
+ if (step.done) return step.value;
445
+ }
446
+ }
447
+
448
+ export async function readRscFlightStage(
449
+ stages: AsyncGenerator<
450
+ RscRenderStage,
451
+ Response,
452
+ RscRenderStageControl | undefined
453
+ >,
454
+ ): Promise<RscFlightStage> {
455
+ const payloadStage = await stages.next();
456
+ if (payloadStage.done || payloadStage.value.type !== "payload") {
457
+ throw new Error("[RSC] render stage pipeline skipped payload stage");
458
+ }
459
+
460
+ const flightStage = await stages.next();
461
+ if (flightStage.done || flightStage.value.type !== "flight") {
462
+ throw new Error("[RSC] render stage pipeline skipped Flight stream");
463
+ }
464
+
465
+ return flightStage.value;
466
+ }
467
+
468
+ export async function finishRscRenderStages(
469
+ stages: AsyncGenerator<
470
+ RscRenderStage,
471
+ Response,
472
+ RscRenderStageControl | undefined
473
+ >,
474
+ control?: RscRenderStageControl,
475
+ ): Promise<Response> {
476
+ const responseStage = await stages.next(control);
477
+ if (!responseStage.done) {
478
+ throw new Error("[RSC] render stage pipeline did not finish response");
479
+ }
480
+
481
+ return responseStage.value;
482
+ }
22
483
 
23
484
  /**
24
485
  * DEVELOPMENT/TEST ONLY. When the debug cache signal gate is on,