@ereo/server 0.2.3 → 0.2.6

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.
@@ -0,0 +1,25 @@
1
+ /**
2
+ * @ereo/server - Auth Config Enforcement
3
+ *
4
+ * Runtime enforcement of route-level AuthConfig.
5
+ * Evaluates static checks (required, roles) and custom check functions
6
+ * before loaders/actions execute.
7
+ */
8
+ import type { AuthConfig, AuthCheckResult, AppContext } from '@ereo/core';
9
+ /**
10
+ * Resolve an auth denial using the static AuthConfig fallbacks.
11
+ * Checks redirect → unauthorized → default 403.
12
+ */
13
+ export declare function resolveAuthDenial(auth: AuthConfig, request: Request): Response;
14
+ /**
15
+ * Resolve a rich AuthCheckResult denial into a Response.
16
+ */
17
+ export declare function resolveCheckResult(result: AuthCheckResult & {
18
+ allowed: false;
19
+ }): Response;
20
+ /**
21
+ * Enforce route-level auth config before running loaders/actions.
22
+ * Returns a Response if access is denied, or null if access is allowed.
23
+ */
24
+ export declare function enforceAuthConfig(authConfig: AuthConfig, request: Request, context: AppContext, params: Record<string, string | string[] | undefined>): Promise<Response | null>;
25
+ //# sourceMappingURL=auth-enforcement.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth-enforcement.d.ts","sourceRoot":"","sources":["../src/auth-enforcement.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE1E;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,UAAU,EAAE,OAAO,EAAE,OAAO,GAAG,QAAQ,CAgB9E;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,eAAe,GAAG;IAAE,OAAO,EAAE,KAAK,CAAA;CAAE,GAAG,QAAQ,CAezF;AAED;;;GAGG;AACH,wBAAsB,iBAAiB,CACrC,UAAU,EAAE,UAAU,EACtB,OAAO,EAAE,OAAO,EAChB,OAAO,EAAE,UAAU,EACnB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAC,GACpD,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CA4B1B"}
@@ -1 +1 @@
1
- {"version":3,"file":"bun-server.d.ts","sourceRoot":"","sources":["../src/bun-server.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"bun-server.d.ts","sourceRoot":"","sources":["../src/bun-server.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -8,8 +8,9 @@ export type { ServerOptions, ServerRenderMode } from './bun-server';
8
8
  export { MiddlewareChain, createMiddlewareChain, logger, cors, securityHeaders, compress, rateLimit, } from './middleware';
9
9
  export type { MiddlewareDefinition, CorsOptions, SecurityHeadersOptions, RateLimitOptions, } from './middleware';
10
10
  export type { MiddlewareHandler, NextFunction, Middleware, AppContext, } from '@ereo/core';
11
+ export { enforceAuthConfig, resolveAuthDenial, resolveCheckResult, } from './auth-enforcement';
11
12
  export { serveStatic, staticMiddleware, getMimeType, } from './static';
12
13
  export type { StaticOptions } from './static';
13
- export { createShell, renderToStream, renderToString, createResponse, createSuspenseStream, } from './streaming';
14
+ export { createShell, renderToStream, renderToString, createResponse, } from './streaming';
14
15
  export type { RenderOptions, ShellTemplate, RenderResult, } from './streaming';
15
16
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,EACL,SAAS,EACT,YAAY,EACZ,KAAK,GACN,MAAM,cAAc,CAAC;AAEtB,YAAY,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAGpE,OAAO,EACL,eAAe,EACf,qBAAqB,EACrB,MAAM,EACN,IAAI,EACJ,eAAe,EACf,QAAQ,EACR,SAAS,GACV,MAAM,cAAc,CAAC;AAEtB,YAAY,EACV,oBAAoB,EACpB,WAAW,EACX,sBAAsB,EACtB,gBAAgB,GACjB,MAAM,cAAc,CAAC;AAItB,YAAY,EACV,iBAAiB,EACjB,YAAY,EACZ,UAAU,EACV,UAAU,GACX,MAAM,YAAY,CAAC;AAGpB,OAAO,EACL,WAAW,EACX,gBAAgB,EAChB,WAAW,GACZ,MAAM,UAAU,CAAC;AAElB,YAAY,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAG9C,OAAO,EACL,WAAW,EACX,cAAc,EACd,cAAc,EACd,cAAc,EACd,oBAAoB,GACrB,MAAM,aAAa,CAAC;AAErB,YAAY,EACV,aAAa,EACb,aAAa,EACb,YAAY,GACb,MAAM,aAAa,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,EACL,SAAS,EACT,YAAY,EACZ,KAAK,GACN,MAAM,cAAc,CAAC;AAEtB,YAAY,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAGpE,OAAO,EACL,eAAe,EACf,qBAAqB,EACrB,MAAM,EACN,IAAI,EACJ,eAAe,EACf,QAAQ,EACR,SAAS,GACV,MAAM,cAAc,CAAC;AAEtB,YAAY,EACV,oBAAoB,EACpB,WAAW,EACX,sBAAsB,EACtB,gBAAgB,GACjB,MAAM,cAAc,CAAC;AAItB,YAAY,EACV,iBAAiB,EACjB,YAAY,EACZ,UAAU,EACV,UAAU,GACX,MAAM,YAAY,CAAC;AAGpB,OAAO,EACL,iBAAiB,EACjB,iBAAiB,EACjB,kBAAkB,GACnB,MAAM,oBAAoB,CAAC;AAG5B,OAAO,EACL,WAAW,EACX,gBAAgB,EAChB,WAAW,GACZ,MAAM,UAAU,CAAC;AAElB,YAAY,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAG9C,OAAO,EACL,WAAW,EACX,cAAc,EACd,cAAc,EACd,cAAc,GACf,MAAM,aAAa,CAAC;AAErB,YAAY,EACV,aAAa,EACb,aAAa,EACb,YAAY,GACb,MAAM,aAAa,CAAC"}
package/dist/index.js CHANGED
@@ -484,40 +484,85 @@ async function renderToStream(element, options) {
484
484
  context: options.context
485
485
  });
486
486
  }
487
- if (hasDeferredData(loaderData)) {
488
- loaderData = await resolveAllDeferred(loaderData);
489
- }
490
- const { head, tail } = createShell({ shell, scripts, styles, loaderData });
487
+ const hasDeferred = hasDeferredData(loaderData);
488
+ const { head, tail } = createShell({
489
+ shell,
490
+ scripts: [],
491
+ styles,
492
+ loaderData: hasDeferred ? null : loaderData
493
+ });
491
494
  return new Promise((resolve2, reject) => {
492
495
  const { PassThrough } = __require("stream");
496
+ const DEFERRED_TIMEOUT_MS = 1e4;
497
+ const RENDER_TIMEOUT_MS = 1e4;
498
+ const timeoutId = setTimeout(() => {
499
+ abort();
500
+ }, RENDER_TIMEOUT_MS);
493
501
  const { pipe, abort } = renderToPipeableStream(element, {
494
- bootstrapScripts: scripts,
502
+ bootstrapModules: scripts,
495
503
  onShellReady() {
496
504
  const passThrough = new PassThrough;
497
- const chunks = [];
498
- chunks.push(Buffer.from(head));
499
- passThrough.on("data", (chunk) => {
500
- chunks.push(chunk);
501
- });
502
- passThrough.on("end", () => {
503
- chunks.push(Buffer.from(tail));
504
- const fullHtml = Buffer.concat(chunks).toString("utf-8");
505
- resolve2({
506
- body: fullHtml,
507
- headers: new Headers({
508
- "Content-Type": "text/html; charset=utf-8",
509
- "Content-Length": Buffer.byteLength(fullHtml).toString()
510
- }),
511
- status: 200
512
- });
505
+ const encoder = new TextEncoder;
506
+ const stream = new ReadableStream({
507
+ start(controller) {
508
+ controller.enqueue(encoder.encode(head));
509
+ passThrough.on("data", (chunk) => {
510
+ controller.enqueue(new Uint8Array(chunk));
511
+ });
512
+ passThrough.on("end", () => {
513
+ (async () => {
514
+ if (hasDeferred) {
515
+ let resolvedData;
516
+ try {
517
+ resolvedData = await Promise.race([
518
+ resolveAllDeferred(loaderData),
519
+ new Promise((_, rejectTimeout) => setTimeout(() => rejectTimeout(new Error("Deferred data resolution timed out")), DEFERRED_TIMEOUT_MS))
520
+ ]);
521
+ } catch (error) {
522
+ console.error("Deferred data resolution failed:", error);
523
+ resolvedData = null;
524
+ }
525
+ const loaderScript = `<script>window.__EREO_DATA__=${serializeLoaderData(resolvedData)}</script>`;
526
+ const resolvedTail = `</div>
527
+ ${loaderScript}
528
+ </body>
529
+ </html>`;
530
+ controller.enqueue(encoder.encode(resolvedTail));
531
+ } else {
532
+ controller.enqueue(encoder.encode(tail));
533
+ }
534
+ controller.close();
535
+ })().catch((error) => {
536
+ console.error("Stream finalization error:", error);
537
+ try {
538
+ controller.error(error);
539
+ } catch {}
540
+ }).finally(() => {
541
+ clearTimeout(timeoutId);
542
+ });
543
+ });
544
+ passThrough.on("error", (error) => {
545
+ clearTimeout(timeoutId);
546
+ console.error("Stream error:", error);
547
+ controller.error(error);
548
+ });
549
+ },
550
+ cancel() {
551
+ clearTimeout(timeoutId);
552
+ passThrough.destroy();
553
+ }
513
554
  });
514
- passThrough.on("error", (error) => {
515
- console.error("Stream error:", error);
516
- reject(error);
555
+ resolve2({
556
+ body: stream,
557
+ headers: new Headers({
558
+ "Content-Type": "text/html; charset=utf-8"
559
+ }),
560
+ status: 200
517
561
  });
518
562
  pipe(passThrough);
519
563
  },
520
564
  onShellError(error) {
565
+ clearTimeout(timeoutId);
521
566
  console.error("Shell render error:", error);
522
567
  reject(error);
523
568
  },
@@ -525,9 +570,6 @@ async function renderToStream(element, options) {
525
570
  console.error("Render error:", error);
526
571
  }
527
572
  });
528
- setTimeout(() => {
529
- abort();
530
- }, 1e4);
531
573
  });
532
574
  }
533
575
  async function renderToString(element, options) {
@@ -562,29 +604,67 @@ function createResponse(result) {
562
604
  headers: result.headers
563
605
  });
564
606
  }
565
- function createSuspenseStream() {
566
- const encoder = new TextEncoder;
567
- let controller;
568
- const stream = new ReadableStream({
569
- start(c) {
570
- controller = c;
571
- }
572
- });
573
- return {
574
- stream,
575
- push: (chunk) => {
576
- controller.enqueue(encoder.encode(chunk));
577
- },
578
- close: () => {
579
- controller.close();
580
- }
581
- };
582
- }
583
607
 
584
608
  // src/bun-server.ts
585
609
  import { serializeLoaderData as serializeLoaderData2, hasDeferredData as hasDeferredData2, resolveAllDeferred as resolveAllDeferred2 } from "@ereo/data";
586
610
  import { createElement } from "react";
587
611
  import { OutletProvider } from "@ereo/client";
612
+
613
+ // src/auth-enforcement.ts
614
+ function resolveAuthDenial(auth, request) {
615
+ if (auth.redirect) {
616
+ const pathname = new URL(request.url).pathname;
617
+ const url = auth.redirect.replace("{pathname}", encodeURIComponent(pathname));
618
+ return new Response(null, {
619
+ status: 302,
620
+ headers: { Location: url }
621
+ });
622
+ }
623
+ if (auth.unauthorized) {
624
+ return new Response(JSON.stringify(auth.unauthorized.body), {
625
+ status: auth.unauthorized.status,
626
+ headers: { "Content-Type": "application/json" }
627
+ });
628
+ }
629
+ return new Response("Forbidden", { status: 403 });
630
+ }
631
+ function resolveCheckResult(result) {
632
+ if ("response" in result)
633
+ return result.response;
634
+ if ("redirect" in result) {
635
+ return new Response(null, {
636
+ status: 302,
637
+ headers: { Location: result.redirect }
638
+ });
639
+ }
640
+ return new Response(result.body !== undefined ? JSON.stringify(result.body) : "Forbidden", {
641
+ status: result.status,
642
+ headers: result.body !== undefined ? { "Content-Type": "application/json" } : {}
643
+ });
644
+ }
645
+ async function enforceAuthConfig(authConfig, request, context, params) {
646
+ const authCtx = context.get("auth");
647
+ if (authConfig.required && !authCtx?.isAuthenticated()) {
648
+ return resolveAuthDenial(authConfig, request);
649
+ }
650
+ if (authConfig.roles?.length) {
651
+ if (!authCtx?.isAuthenticated() || !authCtx.hasAnyRole(authConfig.roles)) {
652
+ return resolveAuthDenial(authConfig, request);
653
+ }
654
+ }
655
+ if (authConfig.check) {
656
+ const result = await authConfig.check({ request, context, params });
657
+ if (result === false) {
658
+ return resolveAuthDenial(authConfig, request);
659
+ }
660
+ if (result !== true && typeof result === "object" && "allowed" in result && !result.allowed) {
661
+ return resolveCheckResult(result);
662
+ }
663
+ }
664
+ return null;
665
+ }
666
+
667
+ // src/bun-server.ts
588
668
  async function getStreamingRenderer() {
589
669
  try {
590
670
  const browserServer = await import("react-dom/server.browser");
@@ -766,6 +846,12 @@ class BunServer {
766
846
  }
767
847
  async handleRouteInner(request, match, context) {
768
848
  const module = match.route.module;
849
+ const routeAuthConfig = match.route.config?.auth || module.config?.auth;
850
+ if (routeAuthConfig) {
851
+ const denied = await enforceAuthConfig(routeAuthConfig, request, context, match.params);
852
+ if (denied)
853
+ return denied;
854
+ }
769
855
  const httpMethod = request.method.toUpperCase();
770
856
  const HTTP_METHODS = ["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS", "HEAD"];
771
857
  if (HTTP_METHODS.includes(httpMethod)) {
@@ -938,27 +1024,31 @@ class BunServer {
938
1024
  }
939
1025
  }
940
1026
  async renderStreamingPage(element, shell, loaderData) {
1027
+ let timeoutId;
941
1028
  try {
942
1029
  const { renderToReadableStream } = await getStreamingRenderer();
943
1030
  if (!renderToReadableStream) {
944
1031
  return this.renderStringPage(element, shell, loaderData);
945
1032
  }
946
1033
  const hasDeferred = hasDeferredData2(loaderData);
947
- const scripts = [this.options.clientEntry];
1034
+ const clientEntry = this.options.clientEntry;
948
1035
  const { head, tail } = createShell({
949
1036
  shell,
950
- scripts,
1037
+ scripts: [],
951
1038
  loaderData: hasDeferred ? null : loaderData
952
1039
  });
953
1040
  const encoder = new TextEncoder;
954
1041
  const headBytes = encoder.encode(head);
955
1042
  const tailBytes = hasDeferred ? null : encoder.encode(tail);
1043
+ const abortController = new AbortController;
1044
+ timeoutId = setTimeout(() => abortController.abort(), 1e4);
956
1045
  const reactStream = await renderToReadableStream(element, {
1046
+ bootstrapModules: [clientEntry],
1047
+ signal: abortController.signal,
957
1048
  onError(error) {
958
1049
  console.error("Streaming render error:", error);
959
1050
  }
960
1051
  });
961
- const clientEntry = this.options.clientEntry;
962
1052
  const reader = reactStream.getReader();
963
1053
  let phase = "head";
964
1054
  const stream = new ReadableStream({
@@ -972,18 +1062,26 @@ class BunServer {
972
1062
  const { done, value } = await reader.read();
973
1063
  if (done) {
974
1064
  if (hasDeferred) {
975
- const resolved = await resolveAllDeferred2(loaderData);
976
- const loaderScript = `<script>window.__EREO_DATA__=${serializeLoaderData2(resolved)}</script>`;
977
- const scriptTag = `<script type="module" src="${clientEntry}"></script>`;
1065
+ let resolvedData;
1066
+ try {
1067
+ resolvedData = await Promise.race([
1068
+ resolveAllDeferred2(loaderData),
1069
+ new Promise((_, reject) => setTimeout(() => reject(new Error("Deferred data resolution timed out")), 1e4))
1070
+ ]);
1071
+ } catch (error) {
1072
+ console.error("Deferred data resolution failed:", error);
1073
+ resolvedData = null;
1074
+ }
1075
+ const loaderScript = `<script>window.__EREO_DATA__=${serializeLoaderData2(resolvedData)}</script>`;
978
1076
  const resolvedTail = `</div>
979
1077
  ${loaderScript}
980
- ${scriptTag}
981
1078
  </body>
982
1079
  </html>`;
983
1080
  controller.enqueue(encoder.encode(resolvedTail));
984
1081
  } else {
985
1082
  controller.enqueue(tailBytes);
986
1083
  }
1084
+ clearTimeout(timeoutId);
987
1085
  controller.close();
988
1086
  phase = "done";
989
1087
  } else {
@@ -995,6 +1093,7 @@ class BunServer {
995
1093
  },
996
1094
  cancel() {
997
1095
  reader.cancel();
1096
+ clearTimeout(timeoutId);
998
1097
  }
999
1098
  });
1000
1099
  return new Response(stream, {
@@ -1004,6 +1103,7 @@ class BunServer {
1004
1103
  }
1005
1104
  });
1006
1105
  } catch (error) {
1106
+ clearTimeout(timeoutId);
1007
1107
  console.error("Streaming render failed:", error);
1008
1108
  return this.renderStringPage(element, shell, loaderData);
1009
1109
  }
@@ -1031,20 +1131,25 @@ class BunServer {
1031
1131
  }
1032
1132
  }
1033
1133
  async renderStreamingPageDirect(element, loaderData) {
1134
+ let timeoutId;
1034
1135
  try {
1035
1136
  const { renderToReadableStream } = await getStreamingRenderer();
1036
1137
  if (!renderToReadableStream) {
1037
1138
  return this.renderStringPageDirect(element, loaderData);
1038
1139
  }
1140
+ const hasDeferred = hasDeferredData2(loaderData);
1141
+ const clientEntry = this.options.clientEntry;
1142
+ const encoder = new TextEncoder;
1143
+ const abortController = new AbortController;
1144
+ timeoutId = setTimeout(() => abortController.abort(), 1e4);
1039
1145
  const reactStream = await renderToReadableStream(element, {
1146
+ bootstrapModules: [clientEntry],
1147
+ signal: abortController.signal,
1040
1148
  onError(error) {
1041
1149
  console.error("Streaming render error:", error);
1042
1150
  }
1043
1151
  });
1044
- const encoder = new TextEncoder;
1045
- const hasDeferred = hasDeferredData2(loaderData);
1046
- const clientEntry = this.options.clientEntry;
1047
- const injectedScripts = hasDeferred ? null : encoder.encode((loaderData ? `<script>window.__EREO_DATA__=${serializeLoaderData2(loaderData)}</script>` : "") + `<script type="module" src="${clientEntry}"></script>`);
1152
+ const loaderScript = !hasDeferred && loaderData ? encoder.encode(`<script>window.__EREO_DATA__=${serializeLoaderData2(loaderData)}</script>`) : null;
1048
1153
  const reader = reactStream.getReader();
1049
1154
  let done = false;
1050
1155
  const stream = new ReadableStream({
@@ -1054,13 +1159,21 @@ class BunServer {
1054
1159
  const result = await reader.read();
1055
1160
  if (result.done) {
1056
1161
  if (hasDeferred) {
1057
- const resolved = await resolveAllDeferred2(loaderData);
1058
- const loaderScript = resolved ? `<script>window.__EREO_DATA__=${serializeLoaderData2(resolved)}</script>` : "";
1059
- const clientScript = `<script type="module" src="${clientEntry}"></script>`;
1060
- controller.enqueue(encoder.encode(loaderScript + clientScript));
1061
- } else {
1062
- controller.enqueue(injectedScripts);
1162
+ let resolvedData;
1163
+ try {
1164
+ resolvedData = await Promise.race([
1165
+ resolveAllDeferred2(loaderData),
1166
+ new Promise((_, reject) => setTimeout(() => reject(new Error("Deferred data resolution timed out")), 1e4))
1167
+ ]);
1168
+ } catch (error) {
1169
+ console.error("Deferred data resolution failed:", error);
1170
+ resolvedData = null;
1171
+ }
1172
+ controller.enqueue(encoder.encode(`<script>window.__EREO_DATA__=${serializeLoaderData2(resolvedData)}</script>`));
1173
+ } else if (loaderScript) {
1174
+ controller.enqueue(loaderScript);
1063
1175
  }
1176
+ clearTimeout(timeoutId);
1064
1177
  controller.close();
1065
1178
  done = true;
1066
1179
  } else {
@@ -1069,6 +1182,7 @@ class BunServer {
1069
1182
  },
1070
1183
  cancel() {
1071
1184
  reader.cancel();
1185
+ clearTimeout(timeoutId);
1072
1186
  }
1073
1187
  });
1074
1188
  return new Response(stream, {
@@ -1078,6 +1192,7 @@ class BunServer {
1078
1192
  }
1079
1193
  });
1080
1194
  } catch (error) {
1195
+ clearTimeout(timeoutId);
1081
1196
  console.error("Streaming render failed:", error);
1082
1197
  return this.renderStringPageDirect(element, loaderData);
1083
1198
  }
@@ -1373,12 +1488,14 @@ export {
1373
1488
  serveStatic,
1374
1489
  serve,
1375
1490
  securityHeaders,
1491
+ resolveCheckResult,
1492
+ resolveAuthDenial,
1376
1493
  renderToString,
1377
1494
  renderToStream,
1378
1495
  rateLimit,
1379
1496
  logger,
1380
1497
  getMimeType,
1381
- createSuspenseStream,
1498
+ enforceAuthConfig,
1382
1499
  createShell,
1383
1500
  createServer,
1384
1501
  createResponse,
@@ -69,6 +69,13 @@ export declare function createShell(options: {
69
69
  /**
70
70
  * Render a route to a streaming response.
71
71
  * Uses renderToPipeableStream for Node.js/Bun environments.
72
+ *
73
+ * Returns a ReadableStream body that progressively sends:
74
+ * shell head → React content (with $RC scripts for Suspense) → tail
75
+ *
76
+ * Deferred data is NOT resolved upfront — React streams Suspense fallbacks
77
+ * and resolves them out-of-order via inline $RC scripts. Loader data is
78
+ * serialized into the tail only after all Suspense boundaries resolve.
72
79
  */
73
80
  export declare function renderToStream(element: ReactElement, options: RenderOptions): Promise<RenderResult>;
74
81
  /**
@@ -79,12 +86,4 @@ export declare function renderToString(element: ReactElement, options: RenderOpt
79
86
  * Create a Response from render result.
80
87
  */
81
88
  export declare function createResponse(result: RenderResult): Response;
82
- /**
83
- * Stream helper for sending chunks with delays (Suspense boundaries).
84
- */
85
- export declare function createSuspenseStream(): {
86
- stream: ReadableStream<Uint8Array>;
87
- push: (chunk: string) => void;
88
- close: () => void;
89
- };
90
89
  //# sourceMappingURL=streaming.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"streaming.d.ts","sourceRoot":"","sources":["../src/streaming.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,OAAO,CAAC;AAC1C,OAAO,KAAK,EAAS,UAAU,EAAE,UAAU,EAAkB,cAAc,EAAE,MAAM,YAAY,CAAC;AAGhG;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,kBAAkB;IAClB,KAAK,EAAE,UAAU,CAAC;IAClB,sBAAsB;IACtB,OAAO,EAAE,UAAU,CAAC;IACpB,qBAAqB;IACrB,KAAK,CAAC,EAAE,aAAa,CAAC;IACtB,uBAAuB;IACvB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,wBAAwB;IACxB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,kBAAkB;IAClB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,qBAAqB;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,gBAAgB;IAChB,IAAI,CAAC,EAAE,KAAK,CAAC;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACpE,uDAAuD;IACvD,KAAK,CAAC,EAAE,cAAc,EAAE,CAAC;IACzB,mBAAmB;IACnB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,sBAAsB;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACnC,sBAAsB;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACpC;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,6BAA6B;IAC7B,IAAI,EAAE,MAAM,GAAG,cAAc,CAAC,UAAU,CAAC,CAAC;IAC1C,uBAAuB;IACvB,OAAO,EAAE,OAAO,CAAC;IACjB,kBAAkB;IAClB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE;IACnC,KAAK,CAAC,EAAE,aAAa,CAAC;IACtB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CA8DjC;AAED;;;GAGG;AACH,wBAAsB,cAAc,CAClC,OAAO,EAAE,YAAY,EACrB,OAAO,EAAE,aAAa,GACrB,OAAO,CAAC,YAAY,CAAC,CAwEvB;AAED;;GAEG;AACH,wBAAsB,cAAc,CAClC,OAAO,EAAE,YAAY,EACrB,OAAO,EAAE,aAAa,GACrB,OAAO,CAAC,YAAY,CAAC,CAgCvB;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,YAAY,GAAG,QAAQ,CAK7D;AAED;;GAEG;AACH,wBAAgB,oBAAoB,IAAI;IACtC,MAAM,EAAE,cAAc,CAAC,UAAU,CAAC,CAAC;IACnC,IAAI,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAC9B,KAAK,EAAE,MAAM,IAAI,CAAC;CACnB,CAmBA"}
1
+ {"version":3,"file":"streaming.d.ts","sourceRoot":"","sources":["../src/streaming.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,OAAO,CAAC;AAC1C,OAAO,KAAK,EAAS,UAAU,EAAE,UAAU,EAAkB,cAAc,EAAE,MAAM,YAAY,CAAC;AAGhG;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,kBAAkB;IAClB,KAAK,EAAE,UAAU,CAAC;IAClB,sBAAsB;IACtB,OAAO,EAAE,UAAU,CAAC;IACpB,qBAAqB;IACrB,KAAK,CAAC,EAAE,aAAa,CAAC;IACtB,uBAAuB;IACvB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,wBAAwB;IACxB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,kBAAkB;IAClB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,qBAAqB;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,gBAAgB;IAChB,IAAI,CAAC,EAAE,KAAK,CAAC;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACpE,uDAAuD;IACvD,KAAK,CAAC,EAAE,cAAc,EAAE,CAAC;IACzB,mBAAmB;IACnB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,sBAAsB;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACnC,sBAAsB;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACpC;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,6BAA6B;IAC7B,IAAI,EAAE,MAAM,GAAG,cAAc,CAAC,UAAU,CAAC,CAAC;IAC1C,uBAAuB;IACvB,OAAO,EAAE,OAAO,CAAC;IACjB,kBAAkB;IAClB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE;IACnC,KAAK,CAAC,EAAE,aAAa,CAAC;IACtB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CA8DjC;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,cAAc,CAClC,OAAO,EAAE,YAAY,EACrB,OAAO,EAAE,aAAa,GACrB,OAAO,CAAC,YAAY,CAAC,CAuHvB;AAED;;GAEG;AACH,wBAAsB,cAAc,CAClC,OAAO,EAAE,YAAY,EACrB,OAAO,EAAE,aAAa,GACrB,OAAO,CAAC,YAAY,CAAC,CAgCvB;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,YAAY,GAAG,QAAQ,CAK7D"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ereo/server",
3
- "version": "0.2.3",
3
+ "version": "0.2.6",
4
4
  "license": "MIT",
5
5
  "author": "Ereo Team",
6
6
  "homepage": "https://ereojs.github.io/ereoJS",
@@ -32,10 +32,10 @@
32
32
  "typecheck": "tsc --noEmit"
33
33
  },
34
34
  "dependencies": {
35
- "@ereo/core": "^0.2.3",
36
- "@ereo/client": "^0.2.3",
37
- "@ereo/router": "^0.2.3",
38
- "@ereo/data": "^0.2.3"
35
+ "@ereo/core": "^0.2.6",
36
+ "@ereo/client": "^0.2.6",
37
+ "@ereo/router": "^0.2.6",
38
+ "@ereo/data": "^0.2.6"
39
39
  },
40
40
  "devDependencies": {
41
41
  "@types/bun": "^1.1.0",