@lazarv/react-server 0.0.0-experimental-43e79e6-20230928

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +5 -0
  3. package/bin/cli.mjs +93 -0
  4. package/bin/commands/build.mjs +19 -0
  5. package/bin/commands/dev.mjs +23 -0
  6. package/bin/commands/start.mjs +16 -0
  7. package/bin/loader.mjs +38 -0
  8. package/client/ActionState.mjs +16 -0
  9. package/client/ClientOnly.jsx +14 -0
  10. package/client/ClientProvider.jsx +243 -0
  11. package/client/ErrorBoundary.jsx +45 -0
  12. package/client/FlightContext.mjs +3 -0
  13. package/client/Link.jsx +59 -0
  14. package/client/Params.mjs +15 -0
  15. package/client/ReactServerComponent.jsx +77 -0
  16. package/client/Refresh.jsx +52 -0
  17. package/client/components.mjs +28 -0
  18. package/client/context.mjs +6 -0
  19. package/client/entry.client.jsx +146 -0
  20. package/client/index.jsx +6 -0
  21. package/client/navigation.jsx +4 -0
  22. package/config/context.mjs +37 -0
  23. package/config/index.mjs +114 -0
  24. package/lib/build/action.mjs +57 -0
  25. package/lib/build/banner.mjs +13 -0
  26. package/lib/build/chunks.mjs +26 -0
  27. package/lib/build/client.mjs +114 -0
  28. package/lib/build/custom-logger.mjs +13 -0
  29. package/lib/build/dependencies.mjs +54 -0
  30. package/lib/build/resolve.mjs +101 -0
  31. package/lib/build/server.mjs +142 -0
  32. package/lib/build/static.mjs +89 -0
  33. package/lib/dev/action.mjs +63 -0
  34. package/lib/dev/create-logger.mjs +52 -0
  35. package/lib/dev/create-server.mjs +208 -0
  36. package/lib/dev/modules.mjs +20 -0
  37. package/lib/dev/ssr-handler.mjs +135 -0
  38. package/lib/handlers/error.mjs +153 -0
  39. package/lib/handlers/not-found.mjs +5 -0
  40. package/lib/handlers/redirect.mjs +1 -0
  41. package/lib/handlers/rewrite.mjs +1 -0
  42. package/lib/handlers/static.mjs +120 -0
  43. package/lib/handlers/trailing-slash.mjs +12 -0
  44. package/lib/plugins/react-server.mjs +73 -0
  45. package/lib/plugins/use-client.mjs +135 -0
  46. package/lib/plugins/use-server.mjs +175 -0
  47. package/lib/start/action.mjs +110 -0
  48. package/lib/start/create-server.mjs +111 -0
  49. package/lib/start/manifest.mjs +104 -0
  50. package/lib/start/ssr-handler.mjs +134 -0
  51. package/lib/sys.mjs +49 -0
  52. package/lib/utils/merge.mjs +31 -0
  53. package/lib/utils/server-address.mjs +14 -0
  54. package/memory-cache/index.mjs +125 -0
  55. package/package.json +81 -0
  56. package/react-server.d.ts +209 -0
  57. package/server/ErrorBoundary.jsx +14 -0
  58. package/server/RemoteComponent.jsx +210 -0
  59. package/server/Route.jsx +108 -0
  60. package/server/actions.mjs +72 -0
  61. package/server/cache.mjs +19 -0
  62. package/server/client-component.mjs +62 -0
  63. package/server/context.mjs +32 -0
  64. package/server/cookies.mjs +14 -0
  65. package/server/entry.server.jsx +972 -0
  66. package/server/error-boundary.jsx +2 -0
  67. package/server/http-headers.mjs +8 -0
  68. package/server/http-status.mjs +6 -0
  69. package/server/index.mjs +14 -0
  70. package/server/logger.mjs +15 -0
  71. package/server/module-loader.mjs +20 -0
  72. package/server/redirects.mjs +45 -0
  73. package/server/remote-component.jsx +2 -0
  74. package/server/request.mjs +37 -0
  75. package/server/revalidate.mjs +22 -0
  76. package/server/rewrites.mjs +0 -0
  77. package/server/router.jsx +6 -0
  78. package/server/runtime.mjs +32 -0
  79. package/server/symbols.mjs +24 -0
@@ -0,0 +1,972 @@
1
+ import { createHash } from "node:crypto";
2
+ import { ReadableStream } from "node:stream/web";
3
+
4
+ import { ActionStateContext } from "@lazarv/react-server/client/ActionState.mjs";
5
+ import {
6
+ concat,
7
+ copyBytesFrom,
8
+ immediate,
9
+ } from "@lazarv/react-server/lib/sys.mjs";
10
+ import {
11
+ asyncLocalStorage,
12
+ callServerReference,
13
+ serverReferenceMap,
14
+ } from "@lazarv/react-server/server/actions.mjs";
15
+ import { clientReferenceMap } from "@lazarv/react-server/server/client-component.mjs";
16
+ import { context$, getContext } from "@lazarv/react-server/server/context.mjs";
17
+ import { status } from "@lazarv/react-server/server/http-status.mjs";
18
+ import { init$ as revalidate$ } from "@lazarv/react-server/server/revalidate.mjs";
19
+ import {
20
+ CACHE_CONTEXT,
21
+ CLIENT_COMPONENTS,
22
+ ERROR_CONTEXT,
23
+ FLIGHT_CACHE,
24
+ FORM_DATA_PARSER,
25
+ HTML_CACHE,
26
+ HTTP_CONTEXT,
27
+ HTTP_HEADERS,
28
+ HTTP_STATUS,
29
+ LOGGER_CONTEXT,
30
+ MAIN_MODULE,
31
+ OUTLET_CACHE,
32
+ REDIRECT_CONTEXT,
33
+ SERVER_CONTEXT,
34
+ SSR_CONTROLLER,
35
+ STYLES_CONTEXT,
36
+ } from "@lazarv/react-server/server/symbols.mjs";
37
+ import dom from "react-dom/server.edge";
38
+ import edge from "react-server-dom-webpack/client.edge";
39
+ import server from "react-server-dom-webpack/server.edge";
40
+
41
+ globalThis.__webpack_chunk_load__ = async (id) => {
42
+ return Promise.resolve(serverReferenceMap.get(id));
43
+ };
44
+
45
+ const decoder = new TextDecoder();
46
+ const encoder = new TextEncoder();
47
+
48
+ const outletRegExp =
49
+ /__react_server_remote_component_outlet_([a-zA-Z0-9_]+)__/g;
50
+
51
+ function getOutletOffset(outlet) {
52
+ return createHash("shake256", { outputLength: 2 })
53
+ .update(outlet)
54
+ .digest("hex");
55
+ }
56
+ function applyOutletOffset(rsc, offset) {
57
+ return rsc
58
+ .replaceAll(/^([0-9a-f]+):/gm, (match, lineId) => `${offset + lineId}:`)
59
+ .replaceAll(/"\$([0-9a-f]+)"/gm, (match, ref) => `"$${offset + ref}"`)
60
+ .replaceAll(/"\$@([0-9a-f]+)"/gm, (match, ref) => `"$${offset + ref}"`)
61
+ .replaceAll(/"\$L([0-9a-f]+)"/gm, (match, ref) => `"$L${offset + ref}"`)
62
+ .replaceAll(/"\$F([0-9a-f]+)"/gm, (match, ref) => `"$F${offset + ref}"`);
63
+ }
64
+ function applyOutletOffsetToHTML(html, offset) {
65
+ return html
66
+ .replaceAll(
67
+ /<(div hidden|template) id="([SPB]):([0-9a-f]+)">/g,
68
+ (match, element, type, id) => `<${element} id="${type}:${offset + id}">`
69
+ )
70
+ .replaceAll(
71
+ /\$R([A-Z]+)\("([SPB]):([0-9a-f]+)","([SPB]):([0-9a-f]+)"\)/g,
72
+ (match, command, targetType, targetId, sourceType, sourceId) =>
73
+ `$R${command}("${targetType}:${offset + targetId}", "${sourceType}:${
74
+ offset + sourceId
75
+ }")`
76
+ );
77
+ }
78
+ async function processRemote(value, queue) {
79
+ try {
80
+ let chunk = decoder.decode(value);
81
+
82
+ const remotes = chunk.match(outletRegExp);
83
+ if (remotes) {
84
+ for (const remote of remotes) {
85
+ const [key, outlet] = outletRegExp.exec(remote);
86
+ const offset = getOutletOffset(outlet);
87
+ const remotePromise = getContext(key);
88
+ if (remotePromise) {
89
+ const Component = await remotePromise;
90
+ if (queue && Component.stream) {
91
+ queue.push(Component.stream.getReader());
92
+ }
93
+ chunk = chunk.replace(key, `$L${offset}0`);
94
+ }
95
+ }
96
+
97
+ return encoder.encode(chunk);
98
+ }
99
+
100
+ return value;
101
+ } catch (e) {
102
+ console.error(e);
103
+ return value;
104
+ }
105
+ }
106
+
107
+ export async function render(Component) {
108
+ const logger = getContext(LOGGER_CONTEXT);
109
+ try {
110
+ // eslint-disable-next-line no-async-promise-executor
111
+ const streaming = new Promise(async (resolve, reject) => {
112
+ const context = getContext(HTTP_CONTEXT);
113
+ try {
114
+ revalidate$();
115
+
116
+ const accept = context.request.headers.get("accept");
117
+ const standalone = accept.includes(";standalone");
118
+ const isRemote = accept.includes(";remote");
119
+ const outlet = (
120
+ context.request.headers.get("react-server-outlet") ?? "PAGE_ROOT"
121
+ ).replace(/[^a-zA-Z0-9_]/g, "_");
122
+
123
+ const Styles = async () => {
124
+ const styles = getContext(STYLES_CONTEXT);
125
+ return (
126
+ <>
127
+ {styles.map((link) => {
128
+ const href = link?.id || link;
129
+ return (
130
+ <link
131
+ key={href}
132
+ rel="stylesheet"
133
+ href={href}
134
+ // eslint-disable-next-line react/no-unknown-property
135
+ precedence="default"
136
+ />
137
+ );
138
+ })}
139
+ </>
140
+ );
141
+ };
142
+ const ComponentWithStyles = (
143
+ <>
144
+ <Styles />
145
+ <Component />
146
+ </>
147
+ );
148
+ let app = (
149
+ <ActionStateContext.Provider
150
+ value={{
151
+ input: [],
152
+ formData: null,
153
+ data: null,
154
+ error: null,
155
+ actionId: null,
156
+ }}
157
+ >
158
+ {ComponentWithStyles}
159
+ </ActionStateContext.Provider>
160
+ );
161
+
162
+ const isFormData = context.request.headers
163
+ .get("content-type")
164
+ ?.includes("multipart/form-data");
165
+ let actionId =
166
+ context.request.headers.get("react-server-action") ?? null;
167
+ if (
168
+ "POST,PUT,PATCH,DELETE".includes(context.request.method) &&
169
+ (actionId || isFormData)
170
+ ) {
171
+ let input = [];
172
+ let formData = null;
173
+ try {
174
+ if (isFormData) {
175
+ let files = [];
176
+ formData = await getContext(FORM_DATA_PARSER)(context.request, {
177
+ handleFile: async ({ body, ...info }) => {
178
+ const [file, formFile] = body.tee();
179
+ files.push({ info, file });
180
+
181
+ const reader = formFile.getReader();
182
+ for (;;) {
183
+ const { done } = await reader.read();
184
+ if (done) break;
185
+ }
186
+
187
+ return `__react_server_file_index__${files.length - 1}__`;
188
+ },
189
+ });
190
+
191
+ if (actionId) {
192
+ [formData] = await server.decodeReply(formData);
193
+ }
194
+
195
+ const data = {};
196
+ for (const [key, value] of formData.entries()) {
197
+ if (key.startsWith("$ACTION_ID_")) {
198
+ actionId = key.slice(11);
199
+ } else {
200
+ if (value.startsWith("__react_server_file_index__")) {
201
+ const { info, file } =
202
+ files[parseInt(value.replace(/\D/g, ""), 10)];
203
+
204
+ data[key] = new Response(file, {
205
+ headers: {
206
+ "content-type": info.contentType,
207
+ },
208
+ });
209
+ } else {
210
+ data[key] = value;
211
+ }
212
+ }
213
+ }
214
+ input = [data];
215
+ } else {
216
+ input = await server.decodeReply(await context.request.text());
217
+ }
218
+
219
+ if (!serverReferenceMap.has(actionId)) {
220
+ await new Promise((actionResolve) => {
221
+ asyncLocalStorage.run({ actionId }, async () => {
222
+ try {
223
+ const actionFlight = server.renderToReadableStream(
224
+ app,
225
+ clientReferenceMap
226
+ );
227
+ const actionFlightReader = actionFlight.getReader();
228
+ await actionFlightReader.read();
229
+ } catch (e) {
230
+ // expected
231
+ }
232
+ asyncLocalStorage.exit(() => {
233
+ actionResolve();
234
+ });
235
+ });
236
+ });
237
+ }
238
+
239
+ if (actionId) {
240
+ try {
241
+ const data = await callServerReference(
242
+ actionId,
243
+ ...(input ?? [])
244
+ );
245
+
246
+ const redirect = getContext(REDIRECT_CONTEXT);
247
+ if (redirect?.response) {
248
+ return resolve(redirect.response);
249
+ }
250
+
251
+ app = (
252
+ <ActionStateContext.Provider
253
+ value={{ formData, data, error: null, actionId }}
254
+ >
255
+ {ComponentWithStyles}
256
+ </ActionStateContext.Provider>
257
+ );
258
+ } catch (error) {
259
+ const redirect = getContext(REDIRECT_CONTEXT);
260
+ if (redirect?.response) {
261
+ return resolve(redirect.response);
262
+ }
263
+
264
+ logger.error(error);
265
+
266
+ app = (
267
+ <ActionStateContext.Provider
268
+ value={{
269
+ formData,
270
+ data: null,
271
+ error: error instanceof Error ? error.message : error,
272
+ actionId,
273
+ }}
274
+ >
275
+ {ComponentWithStyles}
276
+ </ActionStateContext.Provider>
277
+ );
278
+ }
279
+ }
280
+ } catch (error) {
281
+ const redirect = getContext(REDIRECT_CONTEXT);
282
+ if (redirect?.response) {
283
+ return resolve(redirect.response);
284
+ }
285
+
286
+ logger.error(error);
287
+
288
+ app = (
289
+ <ActionStateContext.Provider
290
+ value={{
291
+ formData,
292
+ data: null,
293
+ error: error instanceof Error ? error.message : error,
294
+ actionId,
295
+ }}
296
+ >
297
+ {ComponentWithStyles}
298
+ </ActionStateContext.Provider>
299
+ );
300
+ }
301
+ }
302
+
303
+ const ifModifiedSince =
304
+ context.request.headers.get("if-modified-since");
305
+ const noCache =
306
+ context.request.headers.get("cache-control") === "no-cache";
307
+ const cacheType = isRemote
308
+ ? OUTLET_CACHE
309
+ : accept.includes("text/x-component")
310
+ ? FLIGHT_CACHE
311
+ : HTML_CACHE;
312
+
313
+ if (ifModifiedSince && !noCache) {
314
+ const hasCache = await getContext(CACHE_CONTEXT)?.get([
315
+ context.url,
316
+ accept,
317
+ cacheType,
318
+ ifModifiedSince,
319
+ ]);
320
+
321
+ if (hasCache) {
322
+ return resolve(
323
+ new Response(null, {
324
+ status: 304,
325
+ statusText: "Not Modified",
326
+ })
327
+ );
328
+ }
329
+ }
330
+
331
+ const lastModified = new Date().toUTCString();
332
+ if (accept.includes("text/x-component")) {
333
+ if (!noCache) {
334
+ const responseFromCache = await getContext(CACHE_CONTEXT)?.get([
335
+ context.url,
336
+ accept,
337
+ FLIGHT_CACHE,
338
+ ]);
339
+ if (responseFromCache) {
340
+ const stream = new ReadableStream({
341
+ type: "bytes",
342
+ async start(controller) {
343
+ controller.enqueue(new Uint8Array(responseFromCache.buffer));
344
+ controller.close();
345
+ },
346
+ });
347
+ return resolve(
348
+ new Response(stream, {
349
+ status: responseFromCache.status,
350
+ statusText: responseFromCache.statusText,
351
+ headers: {
352
+ "content-type": "text/x-component",
353
+ "cache-control":
354
+ context.request.headers.get("cache-control") ===
355
+ "no-cache"
356
+ ? "no-cache"
357
+ : "must-revalidate",
358
+ "last-modified": lastModified,
359
+ ...(getContext(HTTP_HEADERS) ?? {}),
360
+ },
361
+ })
362
+ );
363
+ }
364
+ }
365
+
366
+ const stream = new ReadableStream({
367
+ type: "bytes",
368
+ async start(controller) {
369
+ const outletQueue = [];
370
+ const flight = server.renderToReadableStream(
371
+ app,
372
+ clientReferenceMap,
373
+ {
374
+ onError(err) {
375
+ const viteDevServer = getContext(SERVER_CONTEXT);
376
+ viteDevServer?.ssrFixStacktrace?.(err);
377
+ },
378
+ }
379
+ );
380
+
381
+ const reader = flight.getReader();
382
+ let done = false;
383
+ const payload = [];
384
+ let breakOnNewLine = false;
385
+ while (!done) {
386
+ const { value: _value, done: _done } = await reader.read();
387
+ done = _done;
388
+ if (_value) {
389
+ const redirect = getContext(REDIRECT_CONTEXT);
390
+ if (redirect?.response) {
391
+ controller.close();
392
+ return resolve(redirect.response);
393
+ }
394
+
395
+ const value = await processRemote(_value, outletQueue);
396
+ payload.push(copyBytesFrom(value));
397
+
398
+ const endsWithNewLine = value[value.length - 1] === 0x0a;
399
+ if (breakOnNewLine && endsWithNewLine) {
400
+ break;
401
+ }
402
+
403
+ const lastNewLine = value.lastIndexOf(0x0a);
404
+ if (
405
+ (value[0] === 0x30 && value[1] === 0x3a) ||
406
+ (lastNewLine > 0 &&
407
+ lastNewLine < value.length - 2 &&
408
+ value[lastNewLine + 1] === 0x30 &&
409
+ value[lastNewLine + 2] === 0x3a)
410
+ ) {
411
+ if (endsWithNewLine) {
412
+ break;
413
+ } else {
414
+ breakOnNewLine = true;
415
+ }
416
+ }
417
+ }
418
+ }
419
+
420
+ controller.enqueue(new Uint8Array(concat(payload)));
421
+
422
+ const httpStatus = getContext(HTTP_STATUS) ?? {
423
+ status: 200,
424
+ statusText: "OK",
425
+ };
426
+ resolve(
427
+ new Response(stream, {
428
+ ...httpStatus,
429
+ headers: {
430
+ "content-type": "text/x-component",
431
+ "cache-control":
432
+ context.request.headers.get("cache-control") ===
433
+ "no-cache"
434
+ ? "no-cache"
435
+ : "must-revalidate",
436
+ "last-modified": lastModified,
437
+ ...(getContext(HTTP_HEADERS) ?? {}),
438
+ },
439
+ })
440
+ );
441
+
442
+ while (!done) {
443
+ const { value, done: _done } = await reader.read();
444
+ done = _done;
445
+ if (value) {
446
+ const bytesValue = await processRemote(value, outletQueue);
447
+ payload.push(copyBytesFrom(bytesValue));
448
+ controller.enqueue(bytesValue);
449
+ }
450
+ }
451
+
452
+ await Promise.all(
453
+ outletQueue.map(async (outletReader) => {
454
+ let done = false;
455
+ while (!done) {
456
+ const { value, done: _done } = await outletReader.read();
457
+ done = _done;
458
+
459
+ if (value?.rsc) {
460
+ const bytesValue = encoder.encode(
461
+ applyOutletOffset(value.rsc, getOutletOffset(outlet))
462
+ );
463
+ payload.push(copyBytesFrom(bytesValue));
464
+ controller.enqueue(bytesValue);
465
+ }
466
+ }
467
+ })
468
+ );
469
+
470
+ controller.close();
471
+
472
+ getContext(CACHE_CONTEXT)?.set(
473
+ [context.url, accept, FLIGHT_CACHE, lastModified],
474
+ {
475
+ ...httpStatus,
476
+ buffer: concat(payload),
477
+ }
478
+ );
479
+ },
480
+ });
481
+ } else if (accept.includes("text/html")) {
482
+ if (!noCache) {
483
+ const responseFromCache = await getContext(CACHE_CONTEXT)?.get([
484
+ context.url,
485
+ accept,
486
+ cacheType,
487
+ ]);
488
+ if (responseFromCache) {
489
+ const stream = new ReadableStream({
490
+ type: "bytes",
491
+ async start(controller) {
492
+ controller.enqueue(new Uint8Array(responseFromCache.buffer));
493
+ controller.close();
494
+ },
495
+ });
496
+ return resolve(
497
+ new Response(stream, {
498
+ status: responseFromCache.status,
499
+ statusText: responseFromCache.statusText,
500
+ headers: {
501
+ "content-type": "text/html",
502
+ "cache-control":
503
+ context.request.headers.get("cache-control") ===
504
+ "no-cache"
505
+ ? "no-cache"
506
+ : "must-revalidate",
507
+ "last-modified": lastModified,
508
+ ...(getContext(HTTP_HEADERS) ?? {}),
509
+ },
510
+ })
511
+ );
512
+ }
513
+ }
514
+
515
+ let flightWriter = false;
516
+ const flight = server.renderToReadableStream(
517
+ app,
518
+ clientReferenceMap,
519
+ {
520
+ onError(e) {
521
+ if (!flightWriter) {
522
+ const redirect = getContext(REDIRECT_CONTEXT);
523
+ if (redirect?.response) {
524
+ return resolve(redirect.response);
525
+ }
526
+
527
+ status(
528
+ e.status || 500,
529
+ e.statusText || "Internal Server Error"
530
+ );
531
+
532
+ const viteDevServer = getContext(SERVER_CONTEXT);
533
+ viteDevServer?.ssrFixStacktrace?.(e);
534
+ logger.error(e);
535
+ }
536
+ },
537
+ }
538
+ );
539
+ const stream = new ReadableStream({
540
+ type: "bytes",
541
+ async start(controller) {
542
+ try {
543
+ context$(SSR_CONTROLLER, controller);
544
+
545
+ const payload = [];
546
+ let outletQueue = [];
547
+ const [renderStream, forwardStream] = flight.tee();
548
+
549
+ const decoder = new TextDecoder();
550
+ const encoder = new TextEncoder();
551
+
552
+ const tree = edge.createFromReadableStream(renderStream);
553
+
554
+ const forwardReader = forwardStream.getReader();
555
+
556
+ let hydrated = false;
557
+ let hasClientComponent = false;
558
+ let bootstrapped = false;
559
+ const bootstrapScripts = standalone
560
+ ? []
561
+ : [
562
+ `const moduleCache = new Map();
563
+ self.__webpack_require__ = function (id) {
564
+ id = id.startsWith("http") ? id : "/${
565
+ import.meta.env.DEV ? "@fs" : ""
566
+ }" + id;
567
+ if (!moduleCache.has(id)) {
568
+ const modulePromise = import(id);
569
+ modulePromise.then(
570
+ (module) => {
571
+ modulePromise.value = module;
572
+ modulePromise.status = "fulfilled";
573
+ },
574
+ (reason) => {
575
+ modulePromise.reason = reason;
576
+ modulePromise.status = "rejected";
577
+ }
578
+ );
579
+ moduleCache.set(id, modulePromise);
580
+ }
581
+ return moduleCache.get(id);
582
+ };`.replace(/\n/g, ""),
583
+ ];
584
+ const bootstrapModules = standalone
585
+ ? []
586
+ : getContext(MAIN_MODULE);
587
+
588
+ const html = await dom.renderToReadableStream(tree);
589
+ const htmlReader = html.getReader();
590
+
591
+ const start = () => {
592
+ const httpStatus = getContext(HTTP_STATUS) ?? {
593
+ status: 200,
594
+ statusText: "OK",
595
+ };
596
+ resolve(
597
+ new Response(stream, {
598
+ ...httpStatus,
599
+ headers: {
600
+ "content-type": "text/html",
601
+ "cache-control":
602
+ context.request.headers.get("cache-control") ===
603
+ "no-cache"
604
+ ? "no-cache"
605
+ : "must-revalidate",
606
+ "last-modified": lastModified,
607
+ ...(getContext(HTTP_HEADERS) ?? {}),
608
+ },
609
+ })
610
+ );
611
+ };
612
+
613
+ const redirect = () => {
614
+ const redirect = getContext(REDIRECT_CONTEXT);
615
+ if (redirect?.response) {
616
+ controller.close();
617
+ resolve(redirect.response);
618
+ }
619
+ };
620
+
621
+ let forwardReady = null;
622
+ let htmlReady = null;
623
+ let outletReady = null;
624
+
625
+ let forwardDone = false;
626
+ let forwardNext = null;
627
+ const forwardWorker = async function* () {
628
+ await (outletReady || htmlReady);
629
+
630
+ let done = false;
631
+
632
+ const interrupt = new Promise((resolve) =>
633
+ immediate(() => resolve("interrupt"))
634
+ );
635
+
636
+ let _resolve;
637
+ forwardReady = new Promise((resolve) => {
638
+ _resolve = resolve;
639
+ });
640
+
641
+ let force = false;
642
+ while (!done || force) {
643
+ const read = forwardNext
644
+ ? forwardNext
645
+ : forwardReader.read();
646
+ const res = await Promise.race([read, interrupt]);
647
+
648
+ if (res === "interrupt") {
649
+ forwardNext = read;
650
+ done = true;
651
+ break;
652
+ }
653
+
654
+ forwardNext = null;
655
+
656
+ const { value, done: _done } = res;
657
+ forwardDone = _done;
658
+
659
+ hasClientComponent =
660
+ getContext(CLIENT_COMPONENTS)?.size > 0;
661
+
662
+ if (_done) break;
663
+
664
+ if (value) {
665
+ const lines = decoder.decode(value).split("\n");
666
+ force = value[value.length - 1] !== 0x0a;
667
+
668
+ if (lines.some((l) => l.startsWith("0:"))) {
669
+ if (!bootstrapped) {
670
+ if (!isRemote) {
671
+ bootstrapScripts.unshift(
672
+ `self.__flightStream__${outlet}__=new TransformStream();self.__flightWriter__${outlet}__=self.__flightStream__${outlet}__.writable.getWriter();self.__flightEncoder__${outlet}__=new TextEncoder();`
673
+ );
674
+ }
675
+ bootstrapped = true;
676
+ }
677
+ }
678
+
679
+ const bytesValue = await processRemote(value);
680
+ if (isRemote) {
681
+ if (hydrated) {
682
+ yield bytesValue;
683
+ } else {
684
+ bootstrapScripts.push(bytesValue);
685
+ }
686
+ } else {
687
+ const chunk = `self.__flightWriter__${outlet}__.write(self.__flightEncoder__${outlet}__.encode(${JSON.stringify(
688
+ decoder.decode(bytesValue)
689
+ )}));`;
690
+ if (hydrated) {
691
+ const script = encoder.encode(
692
+ `<script>document.currentScript.parentNode.removeChild(document.currentScript);${chunk}</script>`
693
+ );
694
+ yield script;
695
+ } else {
696
+ bootstrapScripts.push(chunk);
697
+ }
698
+ }
699
+
700
+ if (bootstrapped && !force) {
701
+ break;
702
+ }
703
+ }
704
+ }
705
+
706
+ _resolve();
707
+ };
708
+
709
+ let htmlDone = false;
710
+ let htmlNext = null;
711
+ const htmlWorker = async function* () {
712
+ await forwardReady;
713
+
714
+ let done = false;
715
+
716
+ const interrupt = new Promise((resolve) =>
717
+ immediate(() => resolve("interrupt"))
718
+ );
719
+
720
+ let _resolve;
721
+ htmlReady = new Promise((resolve) => {
722
+ _resolve = resolve;
723
+ });
724
+
725
+ let buffer = "";
726
+ let remoteFlightResponse = "";
727
+
728
+ let force = false;
729
+ let hasNewLine = true;
730
+ while (!done || force) {
731
+ const read = htmlNext ? htmlNext : htmlReader.read();
732
+ const res = await Promise.race([read, interrupt]);
733
+
734
+ if (res === "interrupt") {
735
+ htmlNext = read;
736
+ done = true;
737
+ break;
738
+ }
739
+
740
+ htmlNext = null;
741
+
742
+ const { value, done: _done } = res;
743
+ htmlDone = _done;
744
+
745
+ if (_done) break;
746
+
747
+ if (value) {
748
+ hasNewLine = value[value.length - 1] === 0x0a;
749
+ force = value[value.length - 1] !== 0x3e;
750
+
751
+ let chunk = decoder.decode(value);
752
+ buffer += chunk;
753
+
754
+ if (chunk.endsWith("<!--/$-->")) {
755
+ done = true;
756
+ }
757
+ }
758
+ }
759
+
760
+ if (isRemote && !hasNewLine) {
761
+ buffer += "\n";
762
+ }
763
+
764
+ const remotes = buffer.match(outletRegExp);
765
+ if (remotes) {
766
+ for await (const match of remotes) {
767
+ const [key, remoteOutlet] = outletRegExp.exec(match);
768
+ const outletComponentPromise = getContext(key);
769
+ if (outletComponentPromise) {
770
+ const outletComponent = await outletComponentPromise;
771
+ if (outletComponent.stream) {
772
+ outletQueue.push(outletComponent.stream.getReader());
773
+ }
774
+ const outletOffset = getOutletOffset(remoteOutlet);
775
+ const { html, rsc } = outletComponent;
776
+ const remoteChunk = applyOutletOffset(
777
+ rsc,
778
+ outletOffset
779
+ );
780
+ remoteFlightResponse = `<script>self.__flightWriter__${outlet}__.write(self.__flightEncoder__${outlet}__.encode(${JSON.stringify(
781
+ remoteChunk
782
+ )}));</script>`;
783
+ buffer = buffer.replace(
784
+ match,
785
+ applyOutletOffsetToHTML(html, outletOffset)
786
+ );
787
+ }
788
+ }
789
+ }
790
+
791
+ if (buffer.length > 0) {
792
+ yield encoder.encode(remoteFlightResponse + buffer);
793
+ }
794
+
795
+ if (!hydrated && bootstrapped && hasClientComponent) {
796
+ if (isRemote) {
797
+ yield new Uint8Array(concat(bootstrapScripts));
798
+ } else {
799
+ // TODO: bootstrapScripts should be buffers instead of strings, fix script parts should be pre-encoded buffers then yield copy of those buffers
800
+ const script = encoder.encode(
801
+ `<script>document.currentScript.parentNode.removeChild(document.currentScript);${bootstrapScripts.join(
802
+ ""
803
+ )}</script>${bootstrapModules
804
+ .map(
805
+ (mod) =>
806
+ `<script type="module" src="${mod}" async></script>`
807
+ )
808
+ .join("")}`
809
+ );
810
+ yield script;
811
+ }
812
+
813
+ hydrated = true;
814
+ }
815
+
816
+ _resolve();
817
+ };
818
+
819
+ let outletDone = false;
820
+ let outletNext = null;
821
+ const outletWorker = async function* () {
822
+ await htmlReady;
823
+
824
+ let interrupted = false;
825
+
826
+ const interrupt = new Promise((resolve) =>
827
+ immediate(() => resolve("interrupt"))
828
+ );
829
+
830
+ let _resolve;
831
+ outletReady = new Promise((resolve) => {
832
+ _resolve = resolve;
833
+ });
834
+
835
+ while (outletQueue.length > 0) {
836
+ if (interrupted) break;
837
+
838
+ const outletReader = outletQueue.shift();
839
+ let done = outletReader.done || false;
840
+
841
+ while (!done) {
842
+ const read = outletNext
843
+ ? outletNext
844
+ : outletReader.read();
845
+ const res = await Promise.race([read, interrupt]);
846
+
847
+ if (res === "interrupt") {
848
+ outletNext = read;
849
+ done = true;
850
+ interrupted = true;
851
+ outletQueue.push(outletReader);
852
+ break;
853
+ }
854
+
855
+ outletNext = null;
856
+
857
+ const { value, done: _done } = res;
858
+ outletReader.done = _done;
859
+
860
+ if (_done) break;
861
+
862
+ if (value) {
863
+ const { outlet: remoteOutlet, rsc, html } = value;
864
+
865
+ if (html) {
866
+ const remoteHtml = applyOutletOffsetToHTML(
867
+ html,
868
+ getOutletOffset(remoteOutlet)
869
+ );
870
+ yield encoder.encode(remoteHtml);
871
+ }
872
+
873
+ if (rsc) {
874
+ let remoteChunk = applyOutletOffset(
875
+ rsc,
876
+ getOutletOffset(remoteOutlet)
877
+ );
878
+ if (isRemote) {
879
+ if (hasClientComponent) {
880
+ yield encoder.encode(remoteChunk);
881
+ } else {
882
+ bootstrapScripts.push(remoteChunk);
883
+ }
884
+ } else {
885
+ const chunk = `self.__flightWriter__${outlet}__.write(self.__flightEncoder__${outlet}__.encode(${JSON.stringify(
886
+ remoteChunk
887
+ )}));`;
888
+ const script = encoder.encode(
889
+ `<script>${chunk}</script>`
890
+ );
891
+ yield script;
892
+ }
893
+ }
894
+ }
895
+ }
896
+ }
897
+
898
+ outletDone = outletQueue.length === 0 && !outletNext;
899
+
900
+ _resolve();
901
+ };
902
+
903
+ const worker = async function* () {
904
+ while (!(forwardDone && htmlDone && outletDone)) {
905
+ for await (const value of forwardWorker()) {
906
+ yield value;
907
+ }
908
+
909
+ for await (const value of htmlWorker()) {
910
+ yield value;
911
+ }
912
+
913
+ if (!(await Promise.race([streaming, false]))) {
914
+ if (!redirect()) start();
915
+ }
916
+
917
+ for await (const value of outletWorker()) {
918
+ yield value;
919
+ }
920
+ }
921
+ };
922
+
923
+ const render = async () => {
924
+ for await (const value of worker()) {
925
+ payload.push(copyBytesFrom(value));
926
+ controller.enqueue(value);
927
+ }
928
+
929
+ const httpStatus = getContext(HTTP_STATUS) ?? {
930
+ status: 200,
931
+ statusText: "OK",
932
+ };
933
+ getContext(CACHE_CONTEXT)?.set(
934
+ [context.url, accept, cacheType, lastModified],
935
+ {
936
+ ...httpStatus,
937
+ buffer: concat(payload),
938
+ }
939
+ );
940
+
941
+ controller.close();
942
+ };
943
+
944
+ render();
945
+ } catch (e) {
946
+ logger.error(e);
947
+ return resolve(await getContext(ERROR_CONTEXT)?.(e));
948
+ }
949
+ },
950
+ });
951
+ } else {
952
+ return resolve(
953
+ new Response(null, {
954
+ status: 404,
955
+ statusText: "Not Found",
956
+ })
957
+ );
958
+ }
959
+ } catch (e) {
960
+ logger.error(e);
961
+ getContext(ERROR_CONTEXT)?.(e)?.then(resolve, reject);
962
+ }
963
+ });
964
+ return streaming;
965
+ } catch (e) {
966
+ logger.error(e);
967
+ return new Response(null, {
968
+ status: 500,
969
+ statusText: "Internal Server Error",
970
+ });
971
+ }
972
+ }