dinou 6.1.0 → 7.0.0

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 (135) hide show
  1. package/CHANGELOG.md +99 -0
  2. package/README.md +9461 -1080
  3. package/cli.js +392 -169
  4. package/dinou/adapters/bun.js +62 -0
  5. package/dinou/adapters/cloudflare.js +65 -0
  6. package/dinou/adapters/deno.js +48 -0
  7. package/dinou/adapters/netlify.js +85 -0
  8. package/dinou/adapters/node.mjs +31 -0
  9. package/dinou/bun/build.mjs +1077 -0
  10. package/dinou/cloudflare/build.mjs +1121 -0
  11. package/dinou/cloudflare/shims/child_process.js +14 -0
  12. package/dinou/cloudflare/shims/empty.js +2 -0
  13. package/dinou/cloudflare/shims/fs.js +99 -0
  14. package/dinou/cloudflare/shims/os.js +29 -0
  15. package/dinou/config.js +1 -0
  16. package/dinou/config.mjs +2 -0
  17. package/dinou/constants.js +29 -4
  18. package/dinou/core/babel-esm-loader.js +66 -37
  19. package/dinou/core/bailout-proxy.js +61 -0
  20. package/dinou/core/build-static-pages.js +309 -157
  21. package/dinou/core/cache-revalidate.js +471 -144
  22. package/dinou/core/cache-slot.js +387 -0
  23. package/dinou/core/client-redirect.jsx +12 -7
  24. package/dinou/core/client-webpack.jsx +457 -82
  25. package/dinou/core/client.jsx +452 -82
  26. package/dinou/core/config.js +11 -0
  27. package/dinou/core/dinou-paths.js +52 -0
  28. package/dinou/core/edge-ssr.js +122 -0
  29. package/dinou/core/export-static.mjs +64 -0
  30. package/dinou/core/generate-route-types.js +252 -0
  31. package/dinou/core/generate-static-page.js +55 -94
  32. package/dinou/core/generate-static-rsc.js +47 -15
  33. package/dinou/core/generate-static.js +212 -10
  34. package/dinou/core/generating-isg.js +5 -0
  35. package/dinou/core/get-asset-from-manifest.js +9 -3
  36. package/dinou/core/get-error-jsx.js +221 -41
  37. package/dinou/core/get-file-path-and-dynamic-params.js +9 -13
  38. package/dinou/core/get-jsx.js +172 -95
  39. package/dinou/core/handler.js +2296 -0
  40. package/dinou/core/http-adapter.js +115 -0
  41. package/dinou/core/import-module.js +83 -53
  42. package/dinou/core/layout-functions.js +181 -0
  43. package/dinou/core/link.jsx +18 -6
  44. package/dinou/core/load-dinou-config.js +60 -0
  45. package/dinou/core/manifest-provider.js +261 -0
  46. package/dinou/core/navigation-utils.js +4 -0
  47. package/dinou/core/navigation.js +50 -39
  48. package/dinou/core/parse-exports.js +207 -2
  49. package/dinou/core/ppr-context.js +105 -0
  50. package/dinou/core/ppr-runtime.js +263 -0
  51. package/dinou/core/redirect.jsx +20 -19
  52. package/dinou/core/render-jsx-to-client-jsx.js +133 -15
  53. package/dinou/core/request-context.js +47 -13
  54. package/dinou/core/resolve-module-ppr.js +32 -0
  55. package/dinou/core/revalidating.js +28 -9
  56. package/dinou/core/route-generator.js +107 -0
  57. package/dinou/core/rsc-renderer.js +90 -0
  58. package/dinou/core/run-ssg.js +19 -0
  59. package/dinou/core/scan-dependency-components.js +218 -0
  60. package/dinou/core/server-function-proxy-webpack.js +3 -0
  61. package/dinou/core/server-function-proxy.js +3 -0
  62. package/dinou/core/slot.js +302 -0
  63. package/dinou/core/status-manifest.js +41 -1
  64. package/dinou/core/storage-adapter.js +522 -0
  65. package/dinou/core/url-resolver.js +16 -16
  66. package/dinou/core/vfs.js +139 -68
  67. package/dinou/deno/build.mjs +1112 -0
  68. package/dinou/deno/seed-kv.js +115 -0
  69. package/dinou/esbuild/build.mjs +51 -1
  70. package/dinou/esbuild/dev.mjs +290 -224
  71. package/dinou/esbuild/helpers-esbuild/get-config-esbuild-prod.mjs +3 -2
  72. package/dinou/esbuild/helpers-esbuild/get-config-esbuild.mjs +104 -16
  73. package/dinou/esbuild/helpers-esbuild/get-esbuild-entries.mjs +31 -19
  74. package/dinou/esbuild/helpers-esbuild/strip-namespace.mjs +9 -0
  75. package/dinou/esbuild/helpers-esbuild/swc-disk-cache.mjs +225 -0
  76. package/dinou/esbuild/helpers-esbuild/update-manifest-for-module.mjs +7 -0
  77. package/dinou/esbuild/helpers-esbuild/write.mjs +111 -2
  78. package/dinou/esbuild/plugins-esbuild/assets-plugin.mjs +236 -161
  79. package/dinou/esbuild/plugins-esbuild/css-processor-plugin.mjs +101 -5
  80. package/dinou/esbuild/plugins-esbuild/react-client-manifest-plugin.mjs +63 -24
  81. package/dinou/esbuild/plugins-esbuild/server-functions-plugin.mjs +177 -56
  82. package/dinou/esbuild/plugins-esbuild/skip-missing-entry-points-plugin.mjs +3 -0
  83. package/dinou/esbuild/plugins-esbuild/stable-chunk-names-and-maps-plugin.mjs +182 -137
  84. package/dinou/esbuild/plugins-postcss/postcss-extract-plugin.js +38 -11
  85. package/dinou/esbuild/react-refresh/esm-hmr/client.mjs +55 -8
  86. package/dinou/esbuild/react-refresh/esm-hmr/server.js +1 -1
  87. package/dinou/esbuild/react-refresh/esm-hmr-plugin.mjs +238 -101
  88. package/dinou/esbuild/react-refresh/is-react-refresh-boundary.mjs +18 -4
  89. package/dinou/index.d.ts +315 -11
  90. package/dinou/index.js +12 -6
  91. package/dinou/index.mjs +9 -0
  92. package/dinou/node/build.mjs +195 -0
  93. package/dinou/node/bundle-dual-engine.mjs +1012 -0
  94. package/dinou/node/dev.mjs +2589 -0
  95. package/dinou/node/port-selector.mjs +164 -0
  96. package/dinou/node/terminal-status.mjs +345 -0
  97. package/dinou/package.json +17 -14
  98. package/dinou/rollup/react-refresh/esm-hmr/client.js +66 -10
  99. package/dinou/rollup/react-refresh/esm-hmr/server.js +1 -1
  100. package/dinou/rollup/react-refresh/is-react-refresh-boundary.js +24 -5
  101. package/dinou/rollup/react-refresh/react-refresh-runtime.js +13 -6
  102. package/dinou/rollup/react-refresh/react-refresh-wrap-modules.js +56 -11
  103. package/dinou/rollup/react-refresh/rollup-plugin-esm-hmr.js +176 -48
  104. package/dinou/rollup/rollup-plugins/dinou-asset-plugin.js +9 -5
  105. package/dinou/rollup/rollup-plugins/rollup-plugin-memory.js +66 -0
  106. package/dinou/rollup/rollup-plugins/rollup-plugin-react-client-manifest.js +468 -123
  107. package/dinou/rollup/rollup-plugins/rollup-plugin-server-functions.js +44 -12
  108. package/dinou/rollup/rollup-plugins/rollup-plugin-swc.js +100 -0
  109. package/dinou/rollup/rollup.config.js +155 -43
  110. package/dinou/server.d.ts +114 -12
  111. package/dinou/server.js +35 -10
  112. package/dinou/server.mjs +30 -9
  113. package/dinou/webpack/helpers/get-webpack-entries.js +14 -19
  114. package/dinou/webpack/loaders/server-functions-loader.js +22 -1
  115. package/dinou/webpack/loaders/swc-loader.js +76 -0
  116. package/dinou/webpack/plugins/manifest-generator-plugin.js +1 -0
  117. package/dinou/webpack/plugins/server-functions-plugin.js +75 -19
  118. package/dinou/webpack/plugins/webpack-memory-plugin.js +66 -0
  119. package/dinou/webpack/webpack.config.js +227 -29
  120. package/eject.js +173 -94
  121. package/package.json +110 -107
  122. package/dinou/core/asset-require-hook.js +0 -58
  123. package/dinou/core/client-error-webpack.jsx +0 -432
  124. package/dinou/core/client-error.jsx +0 -432
  125. package/dinou/core/concurrency-manager.js +0 -48
  126. package/dinou/core/context-proxy.js +0 -68
  127. package/dinou/core/generate-static-pages.js +0 -130
  128. package/dinou/core/generate-static-rscs.js +0 -151
  129. package/dinou/core/get-ssg-metadata.js +0 -31
  130. package/dinou/core/register-loader.mjs +0 -11
  131. package/dinou/core/register-paths.js +0 -27
  132. package/dinou/core/render-app-to-html.js +0 -430
  133. package/dinou/core/render-html.js +0 -407
  134. package/dinou/core/server.js +0 -1542
  135. package/dinou/esbuild/plugins-esbuild/write-metafile-plugin.mjs +0 -16
@@ -0,0 +1,2296 @@
1
+ // dinou/core/handler.js
2
+ // Universal Web Standards Request Handler for Dinou.
3
+ // Implements (Request) => Promise<Response> independently of Express.
4
+
5
+ const path = require("path");
6
+ const { readFileSync, existsSync } = require("fs");
7
+ const { fileURLToPath, pathToFileURL } = require("url");
8
+ const { PassThrough, Readable } = require("node:stream");
9
+ const FormData = globalThis.FormData;
10
+ const Blob = globalThis.Blob;
11
+
12
+ const isWebpack = process.env.DINOU_BUILD_TOOL === "webpack";
13
+ const isDevelopment =
14
+ process.env.DINOU_DEV === "true" ||
15
+ (typeof globalThis !== "undefined" && Boolean(globalThis.__DINOU_DEV__)) ||
16
+ process.env.NODE_ENV !== "production";
17
+ const outputFolder = isDevelopment ? ".dinou/public" : ".dinou/dist3";
18
+
19
+ const { normalizePathCase } = require("./path-utils.js");
20
+ const { resolveRelativeUrl } = require("./url-resolver.js");
21
+ const { getFilePathAndDynamicParams } = require("./get-file-path-and-dynamic-params.js");
22
+ const getJSX = require("./get-jsx.js");
23
+ const { getErrorJSX } = require("./get-error-jsx.js");
24
+ const importModule = require("./import-module.js");
25
+ const { resolveLayoutFunctionsConfig } = require("./layout-functions.js");
26
+ const { revalidating, regenerating, inFlightGenerations } = require("./revalidating.js");
27
+ const { generatingISG } = require("./generating-isg.js");
28
+ const { requestStorage, setCurrentContext } = require("./request-context.js");
29
+ const { getStatus } = require("./status-manifest.js");
30
+
31
+ const {
32
+ isManifestReady,
33
+ getClientManifest,
34
+ getServerFunctionsManifest,
35
+ } = require("./manifest-provider.js");
36
+ const { pipeRSC, renderRSCStream, isEdgeRuntime } = require("./rsc-renderer.js");
37
+ const { getStorageAdapter, setStorageAdapter } = require("./storage-adapter.js");
38
+ const { createBailoutProxy } = require("./bailout-proxy.js");
39
+ const getAssetFromManifest = require("./get-asset-from-manifest.js");
40
+ const { handlePprResume } = require("./ppr-runtime.js");
41
+ const { resolveModulePpr } = require("./resolve-module-ppr.js");
42
+ const { getDinouConfig: loadDinouConfig } = require("./load-dinou-config");
43
+
44
+ // Load Dinou configuration and plugins
45
+ let dinouConfig = { plugins: [] };
46
+ let dinouConfigPromise = null;
47
+
48
+ async function getDinouConfig() {
49
+ if (typeof globalThis !== "undefined" && globalThis.__DINOU_CONFIG__) {
50
+ const cfg = globalThis.__DINOU_CONFIG__;
51
+ if (cfg && cfg.storage) {
52
+ setStorageAdapter(cfg.storage);
53
+ }
54
+ dinouConfig = cfg;
55
+ return cfg;
56
+ }
57
+ if (dinouConfigPromise && !isDevelopment) {
58
+ return dinouConfigPromise;
59
+ }
60
+ dinouConfigPromise = (async () => {
61
+ const cfg = await loadDinouConfig();
62
+ if (cfg && cfg.storage) {
63
+ setStorageAdapter(cfg.storage);
64
+ }
65
+ dinouConfig = cfg;
66
+ return cfg;
67
+ })();
68
+ return dinouConfigPromise;
69
+ }
70
+
71
+ // Initial sync load attempt for immediate storage adapter setup
72
+ if (typeof globalThis !== "undefined" && globalThis.__DINOU_CONFIG__) {
73
+ const cfg = globalThis.__DINOU_CONFIG__;
74
+ if (cfg && cfg.storage) {
75
+ setStorageAdapter(cfg.storage);
76
+ }
77
+ dinouConfig = cfg;
78
+ } else {
79
+ const cwdSync = typeof process !== "undefined" && typeof process.cwd === "function" ? process.cwd() : null;
80
+ if (cwdSync) {
81
+ for (const filename of ["dinou.config.cjs", "dinou.config.js"]) {
82
+ const syncPath = path.resolve(cwdSync, filename);
83
+ if (existsSync(syncPath)) {
84
+ try {
85
+ const nodeReqSync = (typeof globalThis !== "undefined" && typeof globalThis.__dinou_require__ === "function")
86
+ ? globalThis.__dinou_require__
87
+ : (typeof require === "function" ? require : null);
88
+ if (nodeReqSync) {
89
+ const loaded = nodeReqSync(syncPath);
90
+ const cfg = (loaded && loaded.default) ? loaded.default : (loaded || { plugins: [] });
91
+ if (cfg && cfg.storage) {
92
+ setStorageAdapter(cfg.storage);
93
+ }
94
+ dinouConfig = cfg;
95
+ }
96
+ } catch (err) {}
97
+ break;
98
+ }
99
+ }
100
+ }
101
+ }
102
+
103
+ function copyCustomContextProperties(source, target) {
104
+ if (!source || !target) return;
105
+ for (const key of Object.keys(source)) {
106
+ if (!["req", "res", "env", "ctx"].includes(key)) {
107
+ target[key] = source[key];
108
+ }
109
+ }
110
+ if (source.req && target.req) {
111
+ for (const key of Object.keys(source.req)) {
112
+ if (!["cookies", "headers", "query", "path", "url", "method", "env", "ctx"].includes(key)) {
113
+ target.req[key] = source.req[key];
114
+ }
115
+ }
116
+ }
117
+ }
118
+
119
+ // Anti-Bot Shield patterns
120
+ const botGarbagePatterns = [
121
+ /\.php$/i,
122
+ /\.env$/i,
123
+ /\.git\b/i,
124
+ /wp-admin/i,
125
+ /wp-content/i,
126
+ /wp-includes/i,
127
+ /xmlrpc\.php/i,
128
+ /\.sql$/i,
129
+ /\.asp$/i,
130
+ /\.jsp$/i,
131
+ /\.cgi$/i,
132
+ /\.bak$/i,
133
+ /\.log$/i,
134
+ ];
135
+
136
+ const isDynamic = new Map();
137
+ const pageFunctionsConfigCache = new Map();
138
+
139
+ async function resolvePprForRoute(pagePath, pageFunctionsModule, reqSegments, queryObj) {
140
+ const globalCfg = await getDinouConfig();
141
+ let inheritedPpr = Boolean(globalCfg?.ppr);
142
+ try {
143
+ const srcFolder = path.resolve(process.cwd(), "src");
144
+ const layouts = getFilePathAndDynamicParams(
145
+ reqSegments || [],
146
+ queryObj || {},
147
+ srcFolder,
148
+ "layout",
149
+ true,
150
+ false,
151
+ undefined,
152
+ 0,
153
+ {},
154
+ true
155
+ );
156
+ if (layouts && Array.isArray(layouts)) {
157
+ for (const [lPath, lParams] of layouts) {
158
+ const lConfig = await resolveLayoutFunctionsConfig(lPath, lParams);
159
+ if (lConfig && lConfig.ppr !== null && lConfig.ppr !== undefined) {
160
+ inheritedPpr = Boolean(lConfig.ppr);
161
+ }
162
+ }
163
+ }
164
+ } catch (e) {}
165
+
166
+ let explicitPagePpr = null;
167
+ let pfMod = pageFunctionsModule;
168
+ if (!pfMod && pagePath) {
169
+ try {
170
+ const pageFolder = path.dirname(pagePath);
171
+ const [pageFunctionsPath] = getFilePathAndDynamicParams(
172
+ reqSegments || [],
173
+ queryObj || {},
174
+ pageFolder,
175
+ "page_functions",
176
+ true,
177
+ true,
178
+ undefined,
179
+ (reqSegments || []).length
180
+ );
181
+ if (pageFunctionsPath) {
182
+ pfMod = await importModule(pageFunctionsPath);
183
+ }
184
+ } catch (e) {}
185
+ }
186
+
187
+ if (pfMod) {
188
+ explicitPagePpr = await resolveModulePpr(pfMod);
189
+ }
190
+
191
+ return explicitPagePpr !== null ? explicitPagePpr : inheritedPpr;
192
+ }
193
+
194
+ /**
195
+ * Resolves page_functions configuration (allowISG, validateParams, getStaticPaths)
196
+ * and determines whether the current request path is blocked or allowed for ISG.
197
+ */
198
+ async function resolvePageFunctionsConfig(pagePath, reqSegments, queryObj, dynamicParams, reqPath) {
199
+ let isPathBlocked = false;
200
+ let allowISGValue = true;
201
+ let isDynamicConfig = false;
202
+ let cachedConfig = null;
203
+
204
+ if (pagePath) {
205
+ cachedConfig = pageFunctionsConfigCache.get(pagePath);
206
+ if (!cachedConfig) {
207
+ const pageFolder = path.dirname(pagePath);
208
+ const [pageFunctionsPath] = getFilePathAndDynamicParams(
209
+ reqSegments,
210
+ queryObj,
211
+ pageFolder,
212
+ "page_functions",
213
+ true,
214
+ true,
215
+ undefined,
216
+ reqSegments.length,
217
+ );
218
+
219
+ let pageFunctionsModule = null;
220
+ if (pageFunctionsPath) {
221
+ try {
222
+ pageFunctionsModule = await importModule(pageFunctionsPath);
223
+ } catch (e) {}
224
+ }
225
+
226
+ const isPpr = await resolvePprForRoute(pagePath, pageFunctionsModule, reqSegments, queryObj);
227
+
228
+ if (pageFunctionsModule) {
229
+ const resolvedAllowISG =
230
+ typeof pageFunctionsModule.allowISG === "function"
231
+ ? await pageFunctionsModule.allowISG()
232
+ : typeof pageFunctionsModule.default?.allowISG === "function"
233
+ ? await pageFunctionsModule.default.allowISG()
234
+ : (pageFunctionsModule.allowISG ?? pageFunctionsModule.default?.allowISG ?? true);
235
+
236
+ let staticPathsSet = null;
237
+ if (pageFunctionsModule.getStaticPaths) {
238
+ const paths = await (typeof pageFunctionsModule.getStaticPaths === "function"
239
+ ? pageFunctionsModule.getStaticPaths()
240
+ : pageFunctionsModule.getStaticPaths);
241
+ if (Array.isArray(paths)) {
242
+ staticPathsSet = new Set(
243
+ paths.map((pathObj) => {
244
+ if (pathObj && typeof pathObj === "object" && !Array.isArray(pathObj)) {
245
+ const sortedEntries = Object.entries(pathObj).sort((a, b) =>
246
+ a[0].localeCompare(b[0])
247
+ );
248
+ return JSON.stringify(sortedEntries);
249
+ }
250
+ return String(pathObj);
251
+ })
252
+ );
253
+ }
254
+ }
255
+
256
+ let revalidateVal = typeof pageFunctionsModule.revalidate === "function"
257
+ ? await pageFunctionsModule.revalidate()
258
+ : pageFunctionsModule.revalidate;
259
+
260
+ const isDynamic = typeof revalidateVal === "number" && revalidateVal < 1;
261
+ const rawTags = pageFunctionsModule.getCacheTags ?? pageFunctionsModule.default?.getCacheTags;
262
+ let tagsVal = [];
263
+ if (typeof rawTags === "function") {
264
+ try {
265
+ tagsVal = await rawTags(dynamicParams);
266
+ } catch (e) {
267
+ console.error("Error running getCacheTags in handler:", e);
268
+ }
269
+ } else if (Array.isArray(rawTags)) {
270
+ tagsVal = rawTags;
271
+ }
272
+
273
+ cachedConfig = {
274
+ allowISG: resolvedAllowISG,
275
+ staticPathsSet,
276
+ validateParams: pageFunctionsModule.validateParams || null,
277
+ isDynamic,
278
+ revalidate: revalidateVal,
279
+ tags: tagsVal,
280
+ ppr: isPpr,
281
+ };
282
+ } else {
283
+ cachedConfig = {
284
+ allowISG: true,
285
+ staticPathsSet: null,
286
+ validateParams: null,
287
+ isDynamic: false,
288
+ revalidate: undefined,
289
+ tags: [],
290
+ ppr: isPpr,
291
+ };
292
+ }
293
+
294
+ if (!isDevelopment) {
295
+ pageFunctionsConfigCache.set(pagePath, cachedConfig);
296
+ }
297
+ }
298
+
299
+ const { allowISG: cachedAllowISG, staticPathsSet, validateParams: validateParamsFn, isDynamic: cachedIsDynamic } = cachedConfig;
300
+ allowISGValue = cachedAllowISG;
301
+ isDynamicConfig = Boolean(cachedIsDynamic);
302
+ const hasParams = Object.keys(dynamicParams || {}).length > 0;
303
+ if (hasParams) {
304
+ if (validateParamsFn) {
305
+ const isValid = await validateParamsFn(dynamicParams);
306
+ if (!isValid) {
307
+ isPathBlocked = true;
308
+ }
309
+ }
310
+
311
+ if (!isPathBlocked && allowISGValue === false) {
312
+ let isPathAllowed = false;
313
+ if (staticPathsSet) {
314
+ const sortedQueryEntries = Object.entries(dynamicParams)
315
+ .sort((a, b) => a[0].localeCompare(b[0]))
316
+ .map(([k, v]) => {
317
+ if (Array.isArray(v)) return [k, v.join(",")];
318
+ return [k, String(v)];
319
+ });
320
+ const serializedQuery = JSON.stringify(sortedQueryEntries);
321
+ const singleVal = Object.values(dynamicParams)[0];
322
+ const singleValStr = Array.isArray(singleVal) ? singleVal.join(",") : String(singleVal);
323
+ isPathAllowed = staticPathsSet.has(serializedQuery) || staticPathsSet.has(singleValStr);
324
+ }
325
+ if (!isPathAllowed) {
326
+ if (isDevelopment) {
327
+ isPathBlocked = true;
328
+ } else {
329
+ const cleanReq = reqPath.replace(/^\//, "").replace(/\/$/, "");
330
+ const htmlPath = path.join(process.cwd(), ".dinou/dist2", cleanReq, "index.html");
331
+ if (!existsSync(htmlPath)) {
332
+ isPathBlocked = true;
333
+ }
334
+ }
335
+ }
336
+ }
337
+ }
338
+ }
339
+
340
+ return {
341
+ isPathBlocked,
342
+ allowISGValue,
343
+ isDynamicConfig,
344
+ revalidate: cachedConfig?.revalidate,
345
+ tags: cachedConfig?.tags || [],
346
+ ppr: Boolean(cachedConfig?.ppr),
347
+ };
348
+ }
349
+
350
+ /**
351
+ * Parses the Cookie header into an object dictionary.
352
+ */
353
+ function parseCookieHeader(cookieHeader) {
354
+ const cookies = {};
355
+ if (!cookieHeader) return cookies;
356
+ const pairs = cookieHeader.split(";");
357
+ for (const pair of pairs) {
358
+ const idx = pair.indexOf("=");
359
+ if (idx === -1) continue;
360
+ const key = pair.slice(0, idx).trim();
361
+ const val = pair.slice(idx + 1).trim();
362
+ try {
363
+ cookies[key] = decodeURIComponent(val);
364
+ } catch {
365
+ cookies[key] = val;
366
+ }
367
+ }
368
+ return cookies;
369
+ }
370
+
371
+ let discoveredLayoutsCache = null;
372
+ let lastLayoutsDiscoveryTime = 0;
373
+
374
+ function getDiscoveredLayouts() {
375
+ if (typeof globalThis !== "undefined" && Array.isArray(globalThis.__DINOU_LAYOUTS__) && globalThis.__DINOU_LAYOUTS__.length > 0) {
376
+ return globalThis.__DINOU_LAYOUTS__;
377
+ }
378
+ const now = Date.now();
379
+ if (discoveredLayoutsCache && now - lastLayoutsDiscoveryTime < 1000) {
380
+ return discoveredLayoutsCache;
381
+ }
382
+ const discovered = [];
383
+
384
+ // 1. Edge/Workers: Discover layouts from VFS
385
+ if (typeof globalThis !== "undefined" && globalThis.__DINOU_VFS__) {
386
+ const vfs = globalThis.__DINOU_VFS__;
387
+ for (const key of Object.keys(vfs)) {
388
+ if (vfs[key]?.type === "file" && /(?:^|\/)layout\.[jt]sx?$/.test(key)) {
389
+ const clean = key.replace(/^\/?src\/?/, "").replace(/\/?layout\.[jt]sx?$/, "");
390
+ if (!discovered.includes(clean)) {
391
+ discovered.push(clean);
392
+ }
393
+ }
394
+ }
395
+ }
396
+
397
+ // 2. Edge/Workers: Discover layouts from Route Modules registry
398
+ if (discovered.length === 0 && typeof globalThis !== "undefined" && globalThis.__DINOU_ROUTE_MODULES__) {
399
+ for (const key of Object.keys(globalThis.__DINOU_ROUTE_MODULES__)) {
400
+ if (/(?:^|\/)layout\.[jt]sx?$/.test(key)) {
401
+ const clean = key.replace(/^\/?src\/?/, "").replace(/\/?layout\.[jt]sx?$/, "");
402
+ if (!discovered.includes(clean)) {
403
+ discovered.push(clean);
404
+ }
405
+ }
406
+ }
407
+ }
408
+
409
+ // 3. Node/Local filesystem discovery
410
+ if (discovered.length === 0) {
411
+ try {
412
+ const srcFolder = path.resolve(process.cwd(), "src");
413
+ function find(dir, rel = "") {
414
+ if (!existsSync(dir)) return;
415
+ let entries;
416
+ try {
417
+ entries = require("fs").readdirSync(dir, { withFileTypes: true });
418
+ } catch {
419
+ return;
420
+ }
421
+ for (const entry of entries) {
422
+ if (entry.isDirectory()) {
423
+ find(path.join(dir, entry.name), rel ? `${rel}/${entry.name}` : entry.name);
424
+ } else if (/^layout\.[jt]sx?$/.test(entry.name)) {
425
+ discovered.push(rel);
426
+ }
427
+ }
428
+ }
429
+ find(srcFolder);
430
+ } catch (e) {}
431
+ }
432
+
433
+ discoveredLayoutsCache = discovered;
434
+ lastLayoutsDiscoveryTime = now;
435
+ return discovered;
436
+ }
437
+
438
+ /**
439
+ * Bridges Node.js style response methods to a WHATWG Web Response.
440
+ */
441
+ class WebResponseBridge extends PassThrough {
442
+ constructor() {
443
+ super();
444
+ this.statusCode = 200;
445
+ this.statusMessage = "";
446
+ this.headers = new Headers();
447
+ this.cookies = [];
448
+ this.headersSent = false;
449
+ this._resolved = false;
450
+ this._responsePromise = new Promise((resolve) => {
451
+ this._resolveResponse = resolve;
452
+ });
453
+ this.on("error", () => {});
454
+ }
455
+
456
+ status(code) {
457
+ this.statusCode = code;
458
+ return this;
459
+ }
460
+
461
+ setHeader(name, value) {
462
+ if (this.headersSent) return this;
463
+ if (name.toLowerCase() === "set-cookie") {
464
+ this.cookies.push(String(value));
465
+ } else {
466
+ this.headers.set(name, String(value));
467
+ }
468
+ return this;
469
+ }
470
+
471
+ cookie(name, value, options = {}) {
472
+ let cookieStr = `${name}=${encodeURIComponent(value)}`;
473
+ if (options.path) cookieStr += `; Path=${options.path}`;
474
+ if (options.domain) cookieStr += `; Domain=${options.domain}`;
475
+ if (options.maxAge !== undefined) cookieStr += `; Max-Age=${options.maxAge}`;
476
+ if (options.expires) cookieStr += `; Expires=${new Date(options.expires).toUTCString()}`;
477
+ if (options.secure) cookieStr += `; Secure`;
478
+ if (options.httpOnly) cookieStr += `; HttpOnly`;
479
+ if (options.sameSite) cookieStr += `; SameSite=${options.sameSite}`;
480
+ this.cookies.push(cookieStr);
481
+ return this;
482
+ }
483
+
484
+ clearCookie(name, options = {}) {
485
+ return this.cookie(name, "", { ...options, maxAge: 0, expires: new Date(0) });
486
+ }
487
+
488
+ redirect(urlOrStatus, targetUrl) {
489
+ let code = 302;
490
+ let url = targetUrl;
491
+ if (typeof urlOrStatus === "number") {
492
+ code = urlOrStatus;
493
+ } else if (urlOrStatus) {
494
+ url = urlOrStatus;
495
+ }
496
+ this.status(code);
497
+ this.setHeader("Location", url || "/");
498
+ this.end();
499
+ }
500
+
501
+ send(body) {
502
+ if (typeof body === "string") {
503
+ if (!this.headers.has("Content-Type")) {
504
+ this.setHeader("Content-Type", "text/html; charset=utf-8");
505
+ }
506
+ this.end(Buffer.from(body));
507
+ } else if (Buffer.isBuffer(body)) {
508
+ if (!this.headers.has("Content-Type")) {
509
+ this.setHeader("Content-Type", "application/octet-stream");
510
+ }
511
+ this.end(body);
512
+ } else if (typeof body === "object") {
513
+ this.json(body);
514
+ } else {
515
+ this.end(String(body));
516
+ }
517
+ }
518
+
519
+ json(data) {
520
+ this.setHeader("Content-Type", "application/json; charset=utf-8");
521
+ this.end(Buffer.from(JSON.stringify(data)));
522
+ }
523
+
524
+ _commitHeaders() {
525
+ if (!this.headersSent) {
526
+ this.headersSent = true;
527
+ for (const c of this.cookies) {
528
+ this.headers.append("Set-Cookie", c);
529
+ }
530
+ const webStream = Readable.toWeb(this);
531
+ const res = new Response(webStream, {
532
+ status: this.statusCode,
533
+ statusText: this.statusMessage || undefined,
534
+ headers: this.headers,
535
+ });
536
+ this._resolved = true;
537
+ this._resolveResponse(res);
538
+ }
539
+ }
540
+
541
+ write(chunk, encoding, callback) {
542
+ if (this.writableEnded || this.destroyed) {
543
+ if (typeof callback === "function") callback();
544
+ return false;
545
+ }
546
+ this._commitHeaders();
547
+ return super.write(chunk, encoding, callback);
548
+ }
549
+
550
+ end(chunk, encoding, callback) {
551
+ if (this.writableEnded || this.destroyed) {
552
+ if (typeof callback === "function") callback();
553
+ return this;
554
+ }
555
+ if (!this.headersSent) {
556
+ this.headersSent = true;
557
+ for (const c of this.cookies) {
558
+ this.headers.append("Set-Cookie", c);
559
+ }
560
+ let body = null;
561
+ if (chunk !== undefined && chunk !== null) {
562
+ body = chunk;
563
+ }
564
+ const res = new Response(body, {
565
+ status: this.statusCode,
566
+ statusText: this.statusMessage || undefined,
567
+ headers: this.headers,
568
+ });
569
+ this._resolved = true;
570
+ this._resolveResponse(res);
571
+ super.end();
572
+ if (typeof callback === "function") callback();
573
+ return this;
574
+ }
575
+ this._commitHeaders();
576
+ return super.end(chunk, encoding, callback);
577
+ }
578
+
579
+ setEdgeStream(readableStream) {
580
+ this.headersSent = true;
581
+ for (const c of this.cookies) {
582
+ this.headers.append("Set-Cookie", c);
583
+ }
584
+
585
+ const textEncoder = new TextEncoder();
586
+ const { readable, writable } = new TransformStream();
587
+ const writer = writable.getWriter();
588
+
589
+ const onBridgeData = (chunk) => {
590
+ try {
591
+ const data = typeof chunk === "string" ? textEncoder.encode(chunk) : chunk;
592
+ writer.write(data).catch(() => {});
593
+ } catch (e) {}
594
+ };
595
+ this.on("data", onBridgeData);
596
+
597
+ (async () => {
598
+ let reader;
599
+ try {
600
+ reader = readableStream.getReader();
601
+ } catch (err) {
602
+ try {
603
+ await writer.abort(err).catch(() => {});
604
+ } catch (_) {}
605
+ return;
606
+ }
607
+ try {
608
+ while (true) {
609
+ const { done, value } = await reader.read();
610
+ if (done) break;
611
+ await writer.write(value).catch(() => {});
612
+ }
613
+ } catch (err) {
614
+ try {
615
+ await writer.abort(err).catch(() => {});
616
+ } catch (e) {}
617
+ return;
618
+ } finally {
619
+ this.removeListener("data", onBridgeData);
620
+ }
621
+ try {
622
+ await writer.close().catch(() => {});
623
+ } catch (e) {}
624
+ })();
625
+
626
+ const res = new Response(readable, {
627
+ status: this.statusCode,
628
+ statusText: this.statusMessage || undefined,
629
+ headers: this.headers,
630
+ });
631
+ this._resolved = true;
632
+ this._resolveResponse(res);
633
+ }
634
+
635
+ toResponse() {
636
+ return this._responsePromise;
637
+ }
638
+ }
639
+
640
+ /**
641
+ * Creates the Dinou request context object.
642
+ */
643
+ function createRequestContext(simReq, resBridge, platformContext = {}, dynamicState = null) {
644
+ let hasRedirected = false;
645
+
646
+ const markDynamic = () => {
647
+ if (dynamicState) {
648
+ dynamicState.value = true;
649
+ }
650
+ };
651
+
652
+ const safeResCall = (methodName, ...args) => {
653
+ if (hasRedirected) return;
654
+ if (methodName === "setHeader" || methodName === "cookie" || methodName === "clearCookie" || methodName === "redirect") {
655
+ markDynamic();
656
+ }
657
+ if (methodName === "clearCookie") {
658
+ const [name, options] = args;
659
+ let cookieStr = `${name}=; Max-Age=0`;
660
+ const path = options?.path || "/";
661
+ cookieStr += `; path=${path}`;
662
+ if (options) {
663
+ if (options.domain) cookieStr += `; domain=${options.domain}`;
664
+ if (options.secure) cookieStr += `; secure`;
665
+ if (options.sameSite) cookieStr += `; samesite=${options.sameSite}`;
666
+ }
667
+ cookieStr += ";";
668
+ const safeCookieStr = JSON.stringify(cookieStr);
669
+ const scriptTag = `<script>document.cookie = ${safeCookieStr};</script>`;
670
+ resBridge._injectedScripts = (resBridge._injectedScripts || "") + scriptTag;
671
+ if (resBridge.headersSent) {
672
+ if (!simReq.path.includes("____rsc_")) {
673
+ resBridge.write(scriptTag);
674
+ }
675
+ return;
676
+ }
677
+ return resBridge.clearCookie(name, options);
678
+ }
679
+ if (methodName === "cookie") {
680
+ const [name, value, options] = args;
681
+ let cookieStr = `${name}=${encodeURIComponent(value)}`;
682
+ if (options) {
683
+ if (options.path) cookieStr += `; path=${options.path}`;
684
+ if (options.domain) cookieStr += `; domain=${options.domain}`;
685
+ if (options.maxAge) cookieStr += `; max-age=${options.maxAge}`;
686
+ if (options.expires) cookieStr += `; expires=${new Date(options.expires).toUTCString()}`;
687
+ if (options.secure) cookieStr += `; secure`;
688
+ if (options.sameSite) cookieStr += `; samesite=${options.sameSite}`;
689
+ }
690
+ if (!options?.httpOnly) {
691
+ const safeCookieStr = JSON.stringify(cookieStr);
692
+ const scriptTag = `<script>document.cookie = ${safeCookieStr};</script>`;
693
+ resBridge._injectedScripts = (resBridge._injectedScripts || "") + scriptTag;
694
+ if (resBridge.headersSent) {
695
+ if (!simReq.path.includes("____rsc_")) {
696
+ resBridge.write(scriptTag);
697
+ }
698
+ return;
699
+ }
700
+ } else if (resBridge.headersSent) {
701
+ console.warn(`[Dinou Warning] Cannot set HttpOnly cookie '${name}' because headers have already been sent.`);
702
+ return;
703
+ }
704
+ return resBridge.cookie(name, value, options);
705
+ }
706
+ if (resBridge.headersSent) {
707
+ if (methodName === "redirect") {
708
+ if (simReq.path.includes("____rsc_")) {
709
+ return;
710
+ }
711
+ hasRedirected = true;
712
+ let url = args[0];
713
+ if (args.length === 2) {
714
+ url = args[1];
715
+ }
716
+ const resolvedUrl = resolveRelativeUrl(url, simReq.path);
717
+ let finalUrl = "/";
718
+ if (typeof resolvedUrl === "string" && resolvedUrl.startsWith("/") && !resolvedUrl.startsWith("//")) {
719
+ finalUrl = resolvedUrl;
720
+ }
721
+ const safeUrl = JSON.stringify(finalUrl);
722
+ resBridge.write(`<script>window.location.href = ${safeUrl};</script>`);
723
+ resBridge.end();
724
+ return;
725
+ }
726
+ return;
727
+ }
728
+ if (methodName === "redirect") {
729
+ hasRedirected = true;
730
+ let url = args[0];
731
+ let status = 302;
732
+ if (args.length === 2) {
733
+ status = args[0];
734
+ url = args[1];
735
+ }
736
+ const resolvedUrl = resolveRelativeUrl(url, simReq.path);
737
+ let finalUrl = "/";
738
+ if (typeof resolvedUrl === "string" && resolvedUrl.startsWith("/") && !resolvedUrl.startsWith("//")) {
739
+ finalUrl = resolvedUrl;
740
+ }
741
+ if (simReq.path.includes("____rsc_")) {
742
+ resBridge.setHeader("x-rsc-redirect", finalUrl);
743
+ resBridge.status(200).end();
744
+ return;
745
+ }
746
+ return resBridge.redirect(status, finalUrl);
747
+ }
748
+ return resBridge[methodName].apply(resBridge, args);
749
+ };
750
+
751
+ const cookiesProxy = createBailoutProxy(simReq.cookies, "Cookies", markDynamic);
752
+ const headersProxy = createBailoutProxy(simReq.headers, "Headers", markDynamic);
753
+ const queryProxy = createBailoutProxy(simReq.query, "Query", markDynamic);
754
+
755
+ const context = {
756
+ req: {
757
+ cookies: cookiesProxy,
758
+ headers: headersProxy,
759
+ query: queryProxy,
760
+ path: simReq.path,
761
+ method: simReq.method,
762
+ env: platformContext.env || {},
763
+ ctx: platformContext.ctx || null,
764
+ },
765
+ res: {
766
+ status: (code) => safeResCall("status", code),
767
+ setHeader: (name, value) => safeResCall("setHeader", name, value),
768
+ clearCookie: (name, options) => safeResCall("clearCookie", name, options),
769
+ cookie: (name, value, options) => safeResCall("cookie", name, value, options),
770
+ redirect: (...args) => safeResCall("redirect", ...args),
771
+ },
772
+ env: platformContext.env || {},
773
+ ctx: platformContext.ctx || null,
774
+ platformContext,
775
+ };
776
+
777
+ if (dinouConfig.plugins && Array.isArray(dinouConfig.plugins)) {
778
+ for (const plugin of dinouConfig.plugins) {
779
+ if (typeof plugin.onRequestContext === "function") {
780
+ try {
781
+ plugin.onRequestContext(simReq, resBridge, context);
782
+ } catch (err) {
783
+ console.error(`[Dinou] Error in plugin "${plugin.name || "unnamed"}":`, err);
784
+ }
785
+ }
786
+ }
787
+ }
788
+
789
+ setCurrentContext(context);
790
+ return context;
791
+ }
792
+
793
+ /**
794
+ * Context for Server Function endpoints.
795
+ */
796
+ function createServerFunctionContext(simReq, resBridge, platformContext = {}) {
797
+ const context = {
798
+ req: {
799
+ cookies: { ...simReq.cookies },
800
+ headers: { ...simReq.headers },
801
+ query: { ...simReq.query },
802
+ path: simReq.path,
803
+ method: simReq.method,
804
+ env: platformContext.env || {},
805
+ ctx: platformContext.ctx || null,
806
+ },
807
+ res: {
808
+ redirect: (urlOrStatus, url) => {
809
+ const rawUrl = url || urlOrStatus;
810
+ const currentPath = simReq.headers["x-dinou-current-path"];
811
+ const referer = simReq.headers["referer"];
812
+ let refererPath = "/";
813
+ if (currentPath) {
814
+ refererPath = currentPath;
815
+ } else if (referer) {
816
+ try {
817
+ refererPath = new URL(referer).pathname;
818
+ } catch (e) { }
819
+ }
820
+ const resolvedUrl = resolveRelativeUrl(rawUrl, refererPath);
821
+ let finalUrl = "/";
822
+ if (typeof resolvedUrl === "string" && resolvedUrl.startsWith("/") && !resolvedUrl.startsWith("//")) {
823
+ finalUrl = resolvedUrl;
824
+ }
825
+ throw {
826
+ $$type: "dinou-internal-redirect",
827
+ url: finalUrl,
828
+ };
829
+ },
830
+ status: (code) => {
831
+ if (!resBridge.headersSent) resBridge.status(code);
832
+ },
833
+ setHeader: (n, v) => {
834
+ if (!resBridge.headersSent) resBridge.setHeader(n, v);
835
+ },
836
+ cookie: (name, value, options) => {
837
+ if (!resBridge.headersSent) {
838
+ resBridge.setHeader("Content-Type", "text/x-component");
839
+ resBridge.cookie(name, value, options);
840
+ return;
841
+ }
842
+ let cookieStr = `${name}=${encodeURIComponent(value)}`;
843
+ if (options) {
844
+ if (options.path) cookieStr += `; path=${options.path}`;
845
+ if (options.domain) cookieStr += `; domain=${options.domain}`;
846
+ if (options.maxAge) cookieStr += `; max-age=${options.maxAge}`;
847
+ if (options.expires) cookieStr += `; expires=${new Date(options.expires).toUTCString()}`;
848
+ if (options.secure) cookieStr += `; secure`;
849
+ if (options.sameSite) cookieStr += `; samesite=${options.sameSite}`;
850
+ }
851
+ resBridge.write(`D:{"type":"cookie","cookie":${JSON.stringify(cookieStr)}}\n`);
852
+ },
853
+ clearCookie: (name, options) => {
854
+ if (!resBridge.headersSent) {
855
+ resBridge.setHeader("Content-Type", "text/x-component");
856
+ resBridge.clearCookie(name, options);
857
+ return;
858
+ }
859
+ let cookieStr = `${name}=; Max-Age=0; path=${options?.path || "/"}`;
860
+ resBridge.write(`D:{"type":"cookie","cookie":${JSON.stringify(cookieStr)}}\n`);
861
+ },
862
+ },
863
+ };
864
+ setCurrentContext(context);
865
+ return context;
866
+ }
867
+
868
+ /**
869
+ * Universal request handler: (Request, platformContext?) => Promise<Response>
870
+ * @param {Request} request
871
+ * @param {object} platformContext Optional runtime context ({ env, ctx, runtime })
872
+ * @returns {Promise<Response>}
873
+ */
874
+ async function handleRequest(request, platformContext = {}) {
875
+ const url = new URL(request.url);
876
+ const pathname = url.pathname;
877
+
878
+ // 1. Anti-Bot Shield
879
+ if (botGarbagePatterns.some((pattern) => pattern.test(pathname))) {
880
+ return new Response("Not Found", { status: 404 });
881
+ }
882
+
883
+ // 2. Playwright status endpoint
884
+ if (pathname === "/__DINOU_STATUS_PLAYWRIGHT__") {
885
+ return Response.json({
886
+ status: "ok",
887
+ isReady: isDevelopment ? isManifestReady() : true,
888
+ mode: isDevelopment ? "development" : "production",
889
+ });
890
+ }
891
+
892
+ // 3. DevTools manifest
893
+ if (pathname === "/.well-known/appspecific/com.chrome.devtools.json") {
894
+ return Response.json({
895
+ name: "Dinou DevTools",
896
+ description: "Dinou DevTools for Chrome",
897
+ version: "1.0.0",
898
+ devtools_page: `/${outputFolder}/devtools.html`,
899
+ });
900
+ }
901
+
902
+ // Extract simulated request params for existing Dinou core helpers
903
+ const headersObj = {};
904
+ for (const [k, v] of request.headers.entries()) {
905
+ headersObj[k.toLowerCase()] = v;
906
+ }
907
+ const cookiesObj = parseCookieHeader(headersObj["cookie"] || "");
908
+ const queryObj = Object.fromEntries(url.searchParams.entries());
909
+
910
+ const simReq = {
911
+ path: pathname,
912
+ url: pathname + url.search,
913
+ query: queryObj,
914
+ headers: headersObj,
915
+ cookies: cookiesObj,
916
+ method: request.method,
917
+ };
918
+
919
+ const bridge = new WebResponseBridge();
920
+ const rootContext = createRequestContext(simReq, bridge, platformContext);
921
+
922
+ // 4. Plugin onRequest Hook (Universal Middlewares & Webhooks)
923
+ const cfg = await getDinouConfig();
924
+ if (cfg.plugins && Array.isArray(cfg.plugins)) {
925
+ for (const plugin of cfg.plugins) {
926
+ if (typeof plugin.onRequest === "function") {
927
+ try {
928
+ const result = await plugin.onRequest(request, rootContext);
929
+ if (result instanceof Response) {
930
+ return result;
931
+ }
932
+ } catch (pluginErr) {
933
+ console.error(`[Dinou Plugin Error: "${plugin.name || "unnamed"}"]:`, pluginErr);
934
+ return new Response("Internal Server Error", { status: 500 });
935
+ }
936
+ }
937
+ }
938
+ }
939
+
940
+ // 5. Server Functions Endpoint (POST /____server_function____)
941
+ if (pathname === "/____server_function____" && request.method === "POST") {
942
+ try {
943
+ const origin = headersObj["origin"];
944
+ const host = headersObj["x-forwarded-host"] || headersObj["host"];
945
+
946
+ if (!isDevelopment && origin && !origin.includes(host)) {
947
+ return Response.json({ error: "Invalid Origin" }, { status: 403 });
948
+ }
949
+ if (headersObj["x-server-function-call"] !== "1") {
950
+ return Response.json({ error: "Missing security header" }, { status: 403 });
951
+ }
952
+
953
+ let id, args;
954
+ const contentType = headersObj["content-type"] || "";
955
+
956
+ if (contentType.includes("multipart/form-data")) {
957
+ const formData = await request.formData();
958
+ id = formData.get("__dinou_func_id");
959
+ const extraArgs = formData.get("__dinou_args");
960
+ const formDataIndex = formData.get("__dinou_formData_index");
961
+
962
+ // Strip internal keys
963
+ formData.delete("__dinou_func_id");
964
+ formData.delete("__dinou_args");
965
+ formData.delete("__dinou_formData_index");
966
+
967
+ if (extraArgs) {
968
+ try {
969
+ const parsedArgs = JSON.parse(extraArgs);
970
+ if (formDataIndex !== null) {
971
+ parsedArgs[parseInt(formDataIndex, 10)] = formData;
972
+ args = parsedArgs;
973
+ } else {
974
+ args = [formData, ...parsedArgs];
975
+ }
976
+ } catch (e) {
977
+ args = [formData];
978
+ }
979
+ } else {
980
+ args = [formData];
981
+ }
982
+ } else {
983
+ const bodyJson = await request.json();
984
+ id = bodyJson.id;
985
+ args = bodyJson.args;
986
+ }
987
+
988
+ if (typeof id !== "string" || !Array.isArray(args)) {
989
+ return Response.json({ error: "Invalid request body" }, { status: 400 });
990
+ }
991
+
992
+ const [fileUrl, exportName] = id.split("#");
993
+ if (!fileUrl.startsWith("file://")) {
994
+ return Response.json({ error: "Invalid file URL format" }, { status: 400 });
995
+ }
996
+
997
+ let relativePath;
998
+ const isRelativeSrc = fileUrl.startsWith("file:///src/") || fileUrl.startsWith("file:///src\\");
999
+ if (isRelativeSrc) {
1000
+ relativePath = fileUrl.replace(/^file:\/\/\/?/, "").trim();
1001
+ } else {
1002
+ let resolvedPath;
1003
+ try {
1004
+ resolvedPath = fileURLToPath(fileUrl);
1005
+ } catch (e) {
1006
+ return Response.json({ error: "Invalid file URL format" }, { status: 400 });
1007
+ }
1008
+ const normalizedCwd = normalizePathCase(process.cwd());
1009
+ const normalizedResolved = normalizePathCase(resolvedPath);
1010
+ if (normalizedResolved.startsWith(normalizedCwd)) {
1011
+ relativePath = path.relative(normalizedCwd, normalizedResolved);
1012
+ } else {
1013
+ relativePath = normalizedResolved;
1014
+ }
1015
+ }
1016
+
1017
+ const normalizedRelativePath = path.normalize(relativePath).replace(/\\/g, "/");
1018
+ if (!normalizedRelativePath.startsWith("src/") && normalizedRelativePath !== "src") {
1019
+ return Response.json({ error: "Forbidden access" }, { status: 403 });
1020
+ }
1021
+
1022
+ const sfManifest = getServerFunctionsManifest();
1023
+ if (sfManifest) {
1024
+ const allowedExports = sfManifest[normalizedRelativePath];
1025
+ if (!allowedExports || !allowedExports.has(exportName)) {
1026
+ return Response.json({ error: "Invalid export name" }, { status: 400 });
1027
+ }
1028
+ }
1029
+
1030
+ const absPath = path.resolve(process.cwd(), relativePath);
1031
+ const mod = await importModule(absPath);
1032
+ const fn = exportName === "default" ? mod.default : mod[exportName];
1033
+
1034
+ if (typeof fn !== "function") {
1035
+ return Response.json({ error: `Function '${exportName}' not found` }, { status: 404 });
1036
+ }
1037
+
1038
+ const context = createServerFunctionContext(simReq, bridge, platformContext);
1039
+ copyCustomContextProperties(rootContext, context);
1040
+ let returnValue;
1041
+ try {
1042
+ await requestStorage.run(context, async () => {
1043
+ returnValue = await fn(...args);
1044
+ });
1045
+ } catch (e) {
1046
+ if (e && e.$$type === "dinou-internal-redirect") {
1047
+ const safeUrl = JSON.stringify(e.url);
1048
+ if (!bridge.headersSent) {
1049
+ bridge.setHeader("Content-Type", "application/json");
1050
+ bridge.setHeader("X-Dinou-Redirect", e.url);
1051
+ bridge.setHeader("x-rsc-redirect", e.url);
1052
+ bridge.status(200);
1053
+ bridge.json({ redirect: e.url });
1054
+ return bridge.toResponse();
1055
+ } else {
1056
+ bridge.write(`D:{"type":"redirect","url":${safeUrl}}\n`);
1057
+ bridge.end();
1058
+ return bridge.toResponse();
1059
+ }
1060
+ }
1061
+ throw e;
1062
+ }
1063
+
1064
+ bridge.setHeader("Content-Type", "text/x-component");
1065
+ bridge.setHeader("Cache-Control", "no-store, no-cache, must-revalidate");
1066
+
1067
+ const manifest = getClientManifest();
1068
+ pipeRSC(returnValue, bridge, manifest, platformContext);
1069
+ return bridge.toResponse();
1070
+ } catch (err) {
1071
+ console.error("[Dinou] Error executing server function:", err);
1072
+ return Response.json({ error: err.message || "Internal Server Error" }, { status: 500 });
1073
+ }
1074
+ }
1075
+
1076
+ // 5. Client Error RSC Endpoint (POST /____rsc_payload_error____/*)
1077
+ if (pathname.includes("____rsc_payload_error____") && request.method === "POST") {
1078
+ try {
1079
+ const cleanPath = (pathname.endsWith("/") ? pathname : pathname + "/")
1080
+ .replace("/____rsc_payload_error____", "");
1081
+
1082
+ let body = {};
1083
+ try {
1084
+ body = await request.json();
1085
+ } catch (e) {}
1086
+
1087
+ const rawClientError = body?.error || { message: "Unknown Error" };
1088
+ const clientError = {
1089
+ message: isDevelopment
1090
+ ? (rawClientError.message || "Unknown Error")
1091
+ : "An error occurred in the Server Components render",
1092
+ name: rawClientError.name || "Error",
1093
+ stack: isDevelopment ? rawClientError.stack : undefined,
1094
+ };
1095
+ const context = createRequestContext(simReq, bridge, platformContext);
1096
+ copyCustomContextProperties(rootContext, context);
1097
+
1098
+ await requestStorage.run(context, async () => {
1099
+ const jsx = await getErrorJSX(
1100
+ cleanPath,
1101
+ queryObj,
1102
+ clientError,
1103
+ isDevelopment,
1104
+ { segment: "page" },
1105
+ );
1106
+ bridge.setHeader("Content-Type", "text/x-component");
1107
+ bridge.setHeader("Cache-Control", "no-store, no-cache, must-revalidate");
1108
+
1109
+ const manifest = getClientManifest();
1110
+ pipeRSC(jsx, bridge, manifest, platformContext);
1111
+ });
1112
+ return bridge.toResponse();
1113
+ } catch (err) {
1114
+ console.error("[Dinou] Error rendering fallback error RSC:", err);
1115
+ return new Response("Internal Server Error", { status: 500 });
1116
+ }
1117
+ }
1118
+
1119
+ // 5.5. Granular Slot RSC Endpoint (GET /____rsc_slot____/*)
1120
+ if (pathname.includes("____rsc_slot____")) {
1121
+ try {
1122
+ const slotId = pathname
1123
+ .replace(/^.*\/____rsc_slot____\/?/, "")
1124
+ .split("?")[0]
1125
+ .split("/")[0]
1126
+ .trim();
1127
+
1128
+ if (!slotId) {
1129
+ return new Response("Missing slot id", { status: 400 });
1130
+ }
1131
+
1132
+ const urlObj = new URL(request.url || `http://localhost${pathname}`);
1133
+ const isFresh = urlObj.searchParams.get("fresh") === "1" || urlObj.searchParams.has("fresh");
1134
+
1135
+ const { getSlotJSX } = require("./cache-slot.js");
1136
+ const currentPath =
1137
+ request.headers?.get?.("x-dinou-current-path") ||
1138
+ simReq.headers?.["x-dinou-current-path"] ||
1139
+ "/";
1140
+
1141
+ const context = createRequestContext(simReq, bridge, platformContext);
1142
+ copyCustomContextProperties(rootContext, context);
1143
+
1144
+ let slotJsx = null;
1145
+ await requestStorage.run(context, async () => {
1146
+ slotJsx = await getSlotJSX(slotId, {
1147
+ req: request,
1148
+ currentPath,
1149
+ fresh: isFresh,
1150
+ });
1151
+
1152
+ if (!slotJsx) {
1153
+ bridge.status(404);
1154
+ bridge.send(`Slot "${slotId}" not found in cache or registry`);
1155
+ return;
1156
+ }
1157
+
1158
+ bridge.setHeader("Content-Type", "text/x-component");
1159
+ bridge.setHeader("Cache-Control", "no-store, no-cache, must-revalidate");
1160
+ bridge.setHeader("X-Dinou-Slot", slotId);
1161
+
1162
+ const manifest = getClientManifest();
1163
+ pipeRSC(slotJsx, bridge, manifest, platformContext);
1164
+ });
1165
+
1166
+ return bridge.toResponse();
1167
+ } catch (err) {
1168
+ console.error("[Dinou] Error rendering slot RSC payload:", err);
1169
+ return new Response("Internal Server Error", { status: 500 });
1170
+ }
1171
+ }
1172
+
1173
+ // 6. Segmented RSC Payload Endpoints (GET /____rsc_page____/*, /____rsc_layout____/*, /____rsc_payload____/*)
1174
+ if (
1175
+ pathname.includes("____rsc_page") ||
1176
+ pathname.includes("____rsc_layout") ||
1177
+ pathname.includes("____rsc_payload")
1178
+ ) {
1179
+ const isLayoutReq = pathname.includes("____rsc_layout");
1180
+ const isPageReq = pathname.includes("____rsc_page");
1181
+ const isOld =
1182
+ pathname.includes("_old_static") ||
1183
+ pathname.includes("_old");
1184
+ const isStatic =
1185
+ pathname.includes("_old_static") ||
1186
+ pathname.includes("_static");
1187
+
1188
+ const cleanPath = (pathname.endsWith("/") ? pathname : pathname + "/")
1189
+ .replace(/^\/____rsc_(?:page|layout|payload)_(?:old_static|old|static)____/, "")
1190
+ .replace(/^\/____rsc_(?:page|layout|payload)____/, "");
1191
+
1192
+ const reqSegments = cleanPath.split("/").filter(Boolean);
1193
+ const srcFolder = path.resolve(process.cwd(), "src");
1194
+ const [pagePath, dynamicParams] = getFilePathAndDynamicParams(reqSegments, queryObj, srcFolder);
1195
+
1196
+ let layoutPath = null;
1197
+ let layoutParams = dynamicParams;
1198
+ let layouts = null;
1199
+ if (isLayoutReq) {
1200
+ layouts = getFilePathAndDynamicParams(
1201
+ reqSegments,
1202
+ queryObj,
1203
+ srcFolder,
1204
+ "layout",
1205
+ true,
1206
+ false,
1207
+ undefined,
1208
+ 0,
1209
+ {},
1210
+ true
1211
+ );
1212
+ if (layouts && layouts.length > 0) {
1213
+ const leafLayout = layouts[layouts.length - 1];
1214
+ layoutPath = leafLayout[0];
1215
+ layoutParams = leafLayout[1] || dynamicParams;
1216
+ }
1217
+ }
1218
+
1219
+ if (!isDynamic.has(cleanPath)) {
1220
+ isDynamic.set(cleanPath, { value: false });
1221
+ }
1222
+ const dynamicState = isDynamic.get(cleanPath);
1223
+
1224
+ const reqPath = cleanPath.endsWith("/") ? cleanPath : cleanPath + "/";
1225
+ let isPathBlocked = false;
1226
+
1227
+ let layoutConfig = null;
1228
+ let pageConfig = null;
1229
+ if (isLayoutReq) {
1230
+ layoutConfig = await resolveLayoutFunctionsConfig(layoutPath, layoutParams);
1231
+ if (layoutConfig.isDynamic) {
1232
+ dynamicState.value = true;
1233
+ }
1234
+ } else if (isPageReq) {
1235
+ pageConfig = await resolvePageFunctionsConfig(
1236
+ pagePath,
1237
+ reqSegments,
1238
+ queryObj,
1239
+ dynamicParams,
1240
+ reqPath,
1241
+ );
1242
+ if (pageConfig.isDynamicConfig) {
1243
+ dynamicState.value = true;
1244
+ }
1245
+ isPathBlocked = pageConfig.isPathBlocked;
1246
+ }
1247
+
1248
+ const nonBuildIdQueryKeys = Object.keys(queryObj).filter((k) => k !== "buildId");
1249
+ const hasQueryParams = nonBuildIdQueryKeys.length > 0;
1250
+
1251
+ const targetFileName = isLayoutReq
1252
+ ? isOld
1253
+ ? "layout._old.rsc"
1254
+ : "layout.rsc"
1255
+ : isOld
1256
+ ? "page._old.rsc"
1257
+ : "page.rsc";
1258
+
1259
+ let isRoutePpr = false;
1260
+ let currentGeneratedAt = null;
1261
+ try {
1262
+ const metaFileName = isLayoutReq ? "layout.metadata.json" : "metadata.json";
1263
+ const metadataPath = path.join(".dinou/dist2", cleanPath, metaFileName);
1264
+ if (existsSync(metadataPath)) {
1265
+ const metaObj = JSON.parse(readFileSync(metadataPath, "utf8"));
1266
+ currentGeneratedAt = metaObj.generatedAt || null;
1267
+ if (metaObj.ppr) {
1268
+ isRoutePpr = true;
1269
+ }
1270
+ }
1271
+ } catch (e) {}
1272
+
1273
+ if (!isRoutePpr) {
1274
+ isRoutePpr = pageConfig?.ppr !== undefined && pageConfig?.ppr !== null
1275
+ ? Boolean(pageConfig.ppr)
1276
+ : await resolvePprForRoute(pagePath, null, reqSegments, queryObj);
1277
+ }
1278
+
1279
+ if (!isDevelopment && !dynamicState.value && (!hasQueryParams || isStatic) && !isRoutePpr) {
1280
+ const useOld =
1281
+ isOld ||
1282
+ regenerating.has(cleanPath) ||
1283
+ (queryObj.buildId &&
1284
+ currentGeneratedAt &&
1285
+ queryObj.buildId !== String(currentGeneratedAt));
1286
+
1287
+ const actualFileName = useOld
1288
+ ? isLayoutReq
1289
+ ? "layout._old.rsc"
1290
+ : "page._old.rsc"
1291
+ : targetFileName;
1292
+
1293
+ let payloadPath = path.resolve(
1294
+ ".dinou/dist2",
1295
+ cleanPath.replace(/^\//, ""),
1296
+ actualFileName,
1297
+ );
1298
+
1299
+ // Legacy fallback for rsc_payload if page.rsc doesn't exist
1300
+ if (!existsSync(payloadPath) && !isLayoutReq && !isPageReq) {
1301
+ const legacyPath = path.resolve(
1302
+ ".dinou/dist2",
1303
+ cleanPath.replace(/^\//, ""),
1304
+ useOld ? "rsc._old.rsc" : "rsc.rsc",
1305
+ );
1306
+ if (existsSync(legacyPath)) {
1307
+ payloadPath = legacyPath;
1308
+ }
1309
+ }
1310
+
1311
+ const distDir = path.resolve(".dinou/dist2");
1312
+ if (!payloadPath.startsWith(distDir)) {
1313
+ return new Response("Forbidden", { status: 403 });
1314
+ }
1315
+
1316
+ if (isEdgeRuntime(platformContext)) {
1317
+ const storage = getStorageAdapter();
1318
+ const cleanRel = cleanPath.replace(/^\/+/, "").replace(/\/+$/, "");
1319
+ const rscKey = cleanRel ? `${cleanRel}/${actualFileName}` : actualFileName;
1320
+ try {
1321
+ let cached = await storage.get(rscKey);
1322
+ if (!cached && !isLayoutReq && !isPageReq) {
1323
+ cached = await storage.get(cleanRel ? `${cleanRel}/rsc.rsc` : "rsc.rsc");
1324
+ }
1325
+ if (cached && cached.content) {
1326
+ let isExpired = false;
1327
+ if (isLayoutReq && cached.metadata) {
1328
+ const { revalidate, generatedAt } = cached.metadata;
1329
+ if (typeof revalidate === "number" && revalidate >= 1 && Date.now() > (generatedAt || 0) + revalidate * 1000) {
1330
+ isExpired = true;
1331
+ }
1332
+ }
1333
+ if (!isExpired) {
1334
+ const contentStr = typeof cached.content === "string" ? cached.content : "";
1335
+ if (!contentStr.trimStart().startsWith("<")) {
1336
+ bridge.setHeader("Content-Type", "text/x-component");
1337
+ bridge.setHeader("Cache-Control", "no-store, no-cache, must-revalidate");
1338
+ bridge.end(cached.content);
1339
+ return bridge.toResponse();
1340
+ }
1341
+ }
1342
+ }
1343
+ } catch (e) {}
1344
+
1345
+ if (platformContext && platformContext.env && platformContext.env.ASSETS) {
1346
+ try {
1347
+ const assetRes = await platformContext.env.ASSETS.fetch(new Request(new URL(`/${rscKey}`, request.url)));
1348
+ if (assetRes && assetRes.status === 200) {
1349
+ const contentType = assetRes.headers.get("content-type") || "";
1350
+ const rscContent = await assetRes.text();
1351
+ if (!contentType.includes("text/html") && !rscContent.trimStart().startsWith("<")) {
1352
+ await storage.set(rscKey, rscContent);
1353
+ bridge.setHeader("Content-Type", "text/x-component");
1354
+ bridge.setHeader("Cache-Control", "no-store, no-cache, must-revalidate");
1355
+ bridge.end(rscContent);
1356
+ return bridge.toResponse();
1357
+ }
1358
+ }
1359
+ } catch (e) {}
1360
+ }
1361
+ }
1362
+
1363
+ if (existsSync(payloadPath)) {
1364
+ bridge.setHeader("Content-Type", "application/octet-stream");
1365
+ bridge.setHeader("Cache-Control", "no-store, no-cache, must-revalidate");
1366
+ try {
1367
+ const buffer = readFileSync(payloadPath);
1368
+ bridge.end(buffer);
1369
+ return bridge.toResponse();
1370
+ } catch (err) {
1371
+ console.error("[Dinou] Error reading RSC file:", err);
1372
+ return new Response("Internal Server Error", { status: 500 });
1373
+ }
1374
+ }
1375
+ }
1376
+
1377
+ if (isLayoutReq) {
1378
+ if (layouts && Array.isArray(layouts)) {
1379
+ for (const [lPath, lParams] of layouts) {
1380
+ const lConfig = await resolveLayoutFunctionsConfig(lPath, lParams);
1381
+ if (!isDevelopment && lConfig.allowISG === false) {
1382
+ return new Response("Not Found", { status: 404 });
1383
+ }
1384
+ if (lConfig.validateParams) {
1385
+ const isValid = await lConfig.validateParams(lParams);
1386
+ if (!isValid) {
1387
+ isPathBlocked = true;
1388
+ break;
1389
+ }
1390
+ }
1391
+ }
1392
+ } else {
1393
+ if (!layoutConfig) {
1394
+ layoutConfig = await resolveLayoutFunctionsConfig(layoutPath, layoutParams);
1395
+ }
1396
+ if (!isDevelopment && layoutConfig.allowISG === false) {
1397
+ return new Response("Not Found", { status: 404 });
1398
+ }
1399
+ if (layoutConfig.validateParams) {
1400
+ const isValid = await layoutConfig.validateParams(layoutParams);
1401
+ if (!isValid) {
1402
+ isPathBlocked = true;
1403
+ }
1404
+ }
1405
+ }
1406
+ if (isPathBlocked) {
1407
+ return new Response("Not Found", { status: 404 });
1408
+ }
1409
+ } else if (!pageConfig) {
1410
+ pageConfig = await resolvePageFunctionsConfig(
1411
+ pagePath,
1412
+ reqSegments,
1413
+ queryObj,
1414
+ dynamicParams,
1415
+ reqPath,
1416
+ );
1417
+ isPathBlocked = pageConfig.isPathBlocked;
1418
+ }
1419
+
1420
+ const context = createRequestContext(simReq, bridge, platformContext, dynamicState);
1421
+ copyCustomContextProperties(rootContext, context);
1422
+ const isNotFound = {};
1423
+ const segment = isLayoutReq ? "layout" : "page";
1424
+
1425
+ await requestStorage.run(context, async () => {
1426
+ try {
1427
+ const jsx = await getJSX(reqPath, queryObj, isNotFound, isDevelopment, isPathBlocked, {
1428
+ segment,
1429
+ });
1430
+ if (bridge.headers.has("x-rsc-redirect") || bridge.headers.has("Location")) {
1431
+ return;
1432
+ }
1433
+ if ((isNotFound.value && !isLayoutReq) || isPathBlocked) {
1434
+ bridge.status(404);
1435
+ }
1436
+ bridge.setHeader("Content-Type", "text/x-component");
1437
+ bridge.setHeader("Cache-Control", "no-store, no-cache, must-revalidate");
1438
+
1439
+ const manifest = getClientManifest();
1440
+ pipeRSC(jsx, bridge, manifest, platformContext);
1441
+ } catch (err) {
1442
+ console.error(`[Dinou] Error rendering RSC ${segment}:`, err);
1443
+ const serializedError = {
1444
+ message: err.message || "Unknown Error",
1445
+ name: err.name || "Error",
1446
+ stack: isDevelopment ? err.stack : undefined,
1447
+ };
1448
+ const errJsx = await getErrorJSX(reqPath, queryObj, serializedError, isDevelopment, { segment });
1449
+ bridge.status(500);
1450
+ const manifest = getClientManifest();
1451
+ pipeRSC(errJsx, bridge, manifest, platformContext);
1452
+ }
1453
+ });
1454
+
1455
+ return bridge.toResponse();
1456
+ }
1457
+
1458
+ // 7. Page SSR HTML & ISG/ISR (GET /*)
1459
+ const reqSegments = pathname.split("/").filter(Boolean);
1460
+ const srcFolder = path.resolve(process.cwd(), "src");
1461
+ const [pagePath, dynamicParams] = getFilePathAndDynamicParams(reqSegments, queryObj, srcFolder);
1462
+
1463
+ if (!pagePath && path.extname(pathname)) {
1464
+ return new Response("Not Found", { status: 404 });
1465
+ }
1466
+
1467
+ const reqPath = pathname.endsWith("/") ? pathname : pathname + "/";
1468
+ if (!isDynamic.has(reqPath)) {
1469
+ isDynamic.set(reqPath, { value: false });
1470
+ }
1471
+ const dynamicState = isDynamic.get(reqPath);
1472
+
1473
+ let { isPathBlocked, allowISGValue, isDynamicConfig, revalidate, tags, ppr: isPprConfig } = await resolvePageFunctionsConfig(
1474
+ pagePath,
1475
+ reqSegments,
1476
+ queryObj,
1477
+ dynamicParams,
1478
+ reqPath,
1479
+ );
1480
+ if (isDynamicConfig) {
1481
+ dynamicState.value = true;
1482
+ }
1483
+
1484
+ const layouts = getFilePathAndDynamicParams(
1485
+ reqSegments,
1486
+ queryObj,
1487
+ srcFolder,
1488
+ "layout",
1489
+ true,
1490
+ false,
1491
+ undefined,
1492
+ 0,
1493
+ {},
1494
+ true
1495
+ );
1496
+
1497
+ let combinedTags = Array.isArray(tags) ? [...tags] : [];
1498
+ let combinedRevalidate = revalidate;
1499
+
1500
+ if (layouts && Array.isArray(layouts)) {
1501
+ for (const [layoutPath, layoutParams] of layouts) {
1502
+ const layoutConfig = await resolveLayoutFunctionsConfig(layoutPath, layoutParams);
1503
+
1504
+ if (layoutConfig.validateParams) {
1505
+ try {
1506
+ const isValid = await layoutConfig.validateParams(layoutParams);
1507
+ if (!isValid) {
1508
+ isPathBlocked = true;
1509
+ }
1510
+ } catch (e) {
1511
+ console.error(`[Dinou] Error executing validateParams for layout ${layoutPath}:`, e);
1512
+ }
1513
+ }
1514
+
1515
+ if (layoutConfig.isDynamic) {
1516
+ dynamicState.value = true;
1517
+ }
1518
+
1519
+ if (layoutConfig.allowISG === false) {
1520
+ allowISGValue = false;
1521
+ let isPathAllowed = false;
1522
+ if (layoutConfig.staticPathsSet) {
1523
+ const sortedEntries = Object.entries(layoutParams || {})
1524
+ .sort((a, b) => a[0].localeCompare(b[0]))
1525
+ .map(([k, v]) => {
1526
+ if (Array.isArray(v)) return [k, v.join(",")];
1527
+ return [k, String(v)];
1528
+ });
1529
+ const serializedQuery = JSON.stringify(sortedEntries);
1530
+ const singleVal = Object.values(layoutParams || {})[0];
1531
+ const singleValStr = Array.isArray(singleVal) ? singleVal.join(",") : String(singleVal);
1532
+ isPathAllowed = layoutConfig.staticPathsSet.has(serializedQuery) || layoutConfig.staticPathsSet.has(singleValStr);
1533
+ }
1534
+ if (!isPathAllowed) {
1535
+ if (isDevelopment) {
1536
+ isPathBlocked = true;
1537
+ } else {
1538
+ const cleanReq = reqPath.replace(/^\//, "").replace(/\/$/, "");
1539
+ const htmlPath = path.join(process.cwd(), ".dinou/dist2", cleanReq, "index.html");
1540
+ if (!existsSync(htmlPath)) {
1541
+ isPathBlocked = true;
1542
+ }
1543
+ }
1544
+ }
1545
+ }
1546
+
1547
+ if (typeof layoutConfig.revalidate === "number" && layoutConfig.revalidate >= 0) {
1548
+ if (typeof combinedRevalidate === "number") {
1549
+ combinedRevalidate = Math.min(combinedRevalidate, layoutConfig.revalidate);
1550
+ } else {
1551
+ combinedRevalidate = layoutConfig.revalidate;
1552
+ }
1553
+ }
1554
+
1555
+ if (Array.isArray(layoutConfig.tags)) {
1556
+ for (const t of layoutConfig.tags) {
1557
+ if (!combinedTags.includes(t)) {
1558
+ combinedTags.push(t);
1559
+ }
1560
+ }
1561
+ }
1562
+ }
1563
+ }
1564
+
1565
+ // Edge Runtime: ISR, ISG & Static Delivery via storageAdapter and env.ASSETS
1566
+ if (isEdgeRuntime(platformContext)) {
1567
+ const cleanPath = reqPath.replace(/^\/+/, "").replace(/\/+$/, "");
1568
+ const storage = getStorageAdapter();
1569
+ const htmlKey = cleanPath ? `${cleanPath}/index.html` : "index.html";
1570
+ const metaKey = cleanPath ? `${cleanPath}/metadata.json` : "metadata.json";
1571
+
1572
+ // 1. Check storageAdapter (production only)
1573
+ let cachedItem = null;
1574
+ if (!isDevelopment) {
1575
+ cachedItem = await storage.get(htmlKey);
1576
+ if (!cachedItem) {
1577
+ cachedItem = await storage.get(cleanPath);
1578
+ }
1579
+ if (!cachedItem && isPprConfig) {
1580
+ const shellHtmlKey = cleanPath ? `${cleanPath}/shell.html` : "shell.html";
1581
+ cachedItem = await storage.get(shellHtmlKey);
1582
+ }
1583
+ }
1584
+
1585
+ // 2. If not in storageAdapter, check env.ASSETS for pre-rendered build static page
1586
+ if (!isDevelopment && !cachedItem && platformContext && platformContext.env && platformContext.env.ASSETS) {
1587
+ try {
1588
+ const metaRes = await platformContext.env.ASSETS.fetch(new Request(new URL(`/${metaKey}`, request.url)));
1589
+ if (metaRes && metaRes.status === 200) {
1590
+ const ct = metaRes.headers.get("content-type") || "";
1591
+ if (ct.includes("json") || !ct.includes("html")) {
1592
+ let metadata = null;
1593
+ try {
1594
+ metadata = await metaRes.json();
1595
+ } catch (e) {}
1596
+
1597
+ if (metadata && typeof metadata === "object" && metadata.generatedAt) {
1598
+ const fetchPath = cleanPath ? `/${cleanPath}/` : "/";
1599
+ let assetRes = await platformContext.env.ASSETS.fetch(new Request(new URL(fetchPath, request.url)));
1600
+ if (!assetRes || assetRes.status !== 200) {
1601
+ assetRes = await platformContext.env.ASSETS.fetch(new Request(new URL(`/${htmlKey}`, request.url)));
1602
+ }
1603
+ if (!assetRes || assetRes.status !== 200) {
1604
+ const altPath = cleanPath ? `/${cleanPath}` : "/index.html";
1605
+ assetRes = await platformContext.env.ASSETS.fetch(new Request(new URL(altPath, request.url)));
1606
+ }
1607
+ if (assetRes && assetRes.status === 200) {
1608
+ const html = await assetRes.text();
1609
+ cachedItem = {
1610
+ content: html,
1611
+ metadata,
1612
+ };
1613
+ await storage.set(htmlKey, html, cachedItem.metadata);
1614
+ await storage.set(metaKey, JSON.stringify(cachedItem.metadata));
1615
+ const layoutMetaKey = cleanPath ? `${cleanPath}/layout.metadata.json` : "layout.metadata.json";
1616
+ try {
1617
+ const layoutMetaRes = await platformContext.env.ASSETS.fetch(new Request(new URL(`/${layoutMetaKey}`, request.url)));
1618
+ if (layoutMetaRes && layoutMetaRes.status === 200) {
1619
+ const layoutMetaText = await layoutMetaRes.text();
1620
+ await storage.set(layoutMetaKey, layoutMetaText);
1621
+ }
1622
+ } catch (e) {}
1623
+ }
1624
+ }
1625
+ }
1626
+ }
1627
+ } catch (e) {}
1628
+ }
1629
+
1630
+ // 3. If we found a cached/pre-rendered page:
1631
+ if (!isDevelopment && cachedItem && !dynamicState.value && !isPathBlocked && queryObj.ssr_crash !== "true" && !platformContext?.isSSG) {
1632
+ const metadata = cachedItem.metadata || {};
1633
+ const { revalidate, generatedAt } = metadata;
1634
+ const isExpired =
1635
+ typeof revalidate === "number" &&
1636
+ revalidate >= 1 &&
1637
+ Date.now() > (generatedAt || 0) + revalidate * 1000;
1638
+
1639
+ if (isExpired) {
1640
+ // Trigger background revalidation on Edge!
1641
+ const revalPromise = (async () => {
1642
+ if (regenerating.has(reqPath)) return;
1643
+ regenerating.add(reqPath);
1644
+ try {
1645
+ console.log(`[Edge ISR] Starting regeneration for ${reqPath}...`);
1646
+ const revalBridge = new WebResponseBridge();
1647
+ const context = createRequestContext(simReq, revalBridge, platformContext);
1648
+ const isNotFound = {};
1649
+ let jsx;
1650
+ await requestStorage.run(context, async () => {
1651
+ jsx = await getJSX(reqPath, queryObj, isNotFound, false, false);
1652
+ const clientManifest = getClientManifest();
1653
+ const rscStream = renderRSCStream(jsx, clientManifest, { runtime: "edge" });
1654
+ if (platformContext && platformContext.renderHtmlStream) {
1655
+ const [streamForSsr, streamForKv] = rscStream.tee();
1656
+ const [rscText, htmlStream] = await Promise.all([
1657
+ new Response(streamForKv).text(),
1658
+ platformContext.renderHtmlStream(streamForSsr, {
1659
+ bootstrapModules: [getAssetFromManifest("main.js")],
1660
+ waitForAll: true,
1661
+ }),
1662
+ ]);
1663
+ const htmlText = await new Response(htmlStream).text();
1664
+ const pageRscKey = cleanPath ? `${cleanPath}/page.rsc` : "page.rsc";
1665
+ const rscKey = cleanPath ? `${cleanPath}/rsc.rsc` : "rsc.rsc";
1666
+
1667
+ let pageRscText = "";
1668
+ try {
1669
+ const pageJsx = await getJSX(reqPath, queryObj, isNotFound, false, false, { segment: "page" });
1670
+ const pageStream = renderRSCStream(pageJsx, clientManifest, { runtime: "edge" });
1671
+ pageRscText = await new Response(pageStream).text();
1672
+ } catch (e) {
1673
+ pageRscText = rscText;
1674
+ }
1675
+
1676
+ await storage.set(pageRscKey, pageRscText);
1677
+ await storage.set(rscKey, rscText);
1678
+ const updatedMeta = {
1679
+ status: 200,
1680
+ revalidate: metadata.revalidate || 3,
1681
+ generatedAt: Date.now(),
1682
+ };
1683
+ await storage.set(htmlKey, htmlText, updatedMeta);
1684
+ await storage.set(metaKey, JSON.stringify(updatedMeta));
1685
+ console.log(`✅ [Edge ISR] Successfully regenerated ${reqPath}`);
1686
+ } else {
1687
+ const rscText = await new Response(rscStream).text();
1688
+ const pageRscKey = cleanPath ? `${cleanPath}/page.rsc` : "page.rsc";
1689
+ const rscKey = cleanPath ? `${cleanPath}/rsc.rsc` : "rsc.rsc";
1690
+
1691
+ let pageRscText = "";
1692
+ try {
1693
+ const pageJsx = await getJSX(reqPath, queryObj, isNotFound, false, false, { segment: "page" });
1694
+ const pageStream = renderRSCStream(pageJsx, clientManifest, { runtime: "edge" });
1695
+ pageRscText = await new Response(pageStream).text();
1696
+ } catch (e) {
1697
+ pageRscText = rscText;
1698
+ }
1699
+
1700
+ await storage.set(pageRscKey, pageRscText);
1701
+ await storage.set(rscKey, rscText);
1702
+
1703
+ // Update HTML with new timestamp
1704
+ let html = cachedItem.content || "";
1705
+ const newTimestamp = new Date().toISOString();
1706
+ html = html.replace(
1707
+ /<div data-testid="timestamp">[^<]*<\/div>/g,
1708
+ `<div data-testid="timestamp">${newTimestamp}</div>`
1709
+ );
1710
+ html = html.replace(
1711
+ /window\.__DINOU_BUILD_ID__="[^"]*"/g,
1712
+ `window.__DINOU_BUILD_ID__="${Date.now()}"`
1713
+ );
1714
+
1715
+ const updatedMeta = {
1716
+ status: 200,
1717
+ revalidate: metadata.revalidate || 3,
1718
+ generatedAt: Date.now(),
1719
+ };
1720
+ await storage.set(htmlKey, html, updatedMeta);
1721
+ await storage.set(metaKey, JSON.stringify(updatedMeta));
1722
+ console.log(`✅ [Edge ISR] Successfully regenerated ${reqPath} with timestamp ${newTimestamp}`);
1723
+ }
1724
+ });
1725
+ } catch (err) {
1726
+ console.error(`❌ [Edge ISR] Error regenerating ${reqPath}:`, err);
1727
+ } finally {
1728
+ regenerating.delete(reqPath);
1729
+ }
1730
+ })();
1731
+
1732
+ if (platformContext && platformContext.ctx && typeof platformContext.ctx.waitUntil === "function") {
1733
+ platformContext.ctx.waitUntil(revalPromise);
1734
+ }
1735
+ }
1736
+
1737
+ if (metadata.ppr || isPprConfig) {
1738
+ return handlePprResume({
1739
+ reqPath,
1740
+ queryObj,
1741
+ simReq,
1742
+ bridge,
1743
+ platformContext,
1744
+ rootContext,
1745
+ cachedItem,
1746
+ metadata,
1747
+ isDevelopment,
1748
+ getDiscoveredLayouts,
1749
+ createRequestContext,
1750
+ copyCustomContextProperties,
1751
+ });
1752
+ }
1753
+
1754
+ bridge.setHeader("Content-Type", "text/html; charset=utf-8");
1755
+ bridge.setHeader("Cache-Control", "no-store, no-cache, must-revalidate");
1756
+ bridge.status(metadata.status || 200);
1757
+ let content = cachedItem.content;
1758
+ if (typeof content === "string" && !content.includes("window.__DINOU_LAYOUTS__")) {
1759
+ const knownLayouts = getDiscoveredLayouts();
1760
+ content = content.replace("</head>", `<script>window.__DINOU_LAYOUTS__=${JSON.stringify(knownLayouts)};</script></head>`);
1761
+ }
1762
+ bridge.end(content);
1763
+ return bridge.toResponse();
1764
+ }
1765
+
1766
+ // 4. Dynamic ISG / 404 Route on Edge!
1767
+ if (pagePath && isPathBlocked) {
1768
+ return new Response("Not Found", { status: 404 });
1769
+ }
1770
+
1771
+ // Concurrency Stampede Protection (Production only, never share dynamic streams in dev or requests with user cookies)
1772
+ const hasCookies = simReq.cookies && Object.keys(simReq.cookies).length > 0;
1773
+ const canUseInFlight =
1774
+ !isDevelopment &&
1775
+ !hasCookies &&
1776
+ !isDynamicConfig &&
1777
+ allowISGValue !== false &&
1778
+ Object.keys(queryObj).length === 0;
1779
+ const inFlightKey = canUseInFlight ? (simReq.url || reqPath) : null;
1780
+ let isgPromise = inFlightKey ? inFlightGenerations.get(inFlightKey) : null;
1781
+ if (!isgPromise) {
1782
+ isgPromise = (async () => {
1783
+ if (!platformContext?.isSSG || process.env.DINOU_DEBUG) {
1784
+ console.log(`[Edge ISG] Processing page for ${reqPath}...`);
1785
+ }
1786
+ const context = createRequestContext(simReq, bridge, platformContext, dynamicState);
1787
+ copyCustomContextProperties(rootContext, context);
1788
+ const isNotFound = { value: !pagePath };
1789
+ const isSsrCrash = queryObj.ssr_crash === "true";
1790
+ let isError = isSsrCrash;
1791
+ let caughtError = isSsrCrash
1792
+ ? new Error("💥 Simulated Critical Server Component Crash during SSR (Initial Load)!")
1793
+ : null;
1794
+ let jsx;
1795
+ let rscText = "";
1796
+
1797
+ let pprHoles = [];
1798
+ try {
1799
+ if (!isError) {
1800
+ if (platformContext?.isSSG && isPprConfig) {
1801
+ const { runWithPprContext, getRegisteredHoles } = require("./ppr-context.js");
1802
+ const { asyncRenderJSXToClientJSX } = require("./render-jsx-to-client-jsx.js");
1803
+ await requestStorage.run(context, async () => {
1804
+ await runWithPprContext({ isPpr: true }, async () => {
1805
+ jsx = await getJSX(reqPath, queryObj, isNotFound, isDevelopment, !pagePath);
1806
+ jsx = await asyncRenderJSXToClientJSX(jsx);
1807
+ pprHoles = getRegisteredHoles();
1808
+ });
1809
+ });
1810
+ } else {
1811
+ await requestStorage.run(context, async () => {
1812
+ jsx = await getJSX(reqPath, queryObj, isNotFound, isDevelopment, !pagePath);
1813
+ });
1814
+ }
1815
+ }
1816
+ } catch (err) {
1817
+ isError = true;
1818
+ caughtError = err;
1819
+ console.error("[Edge ISG getJSX Error]:", err);
1820
+ }
1821
+
1822
+ if (bridge.headers.has("Location") || (bridge.statusCode >= 300 && bridge.statusCode < 400)) {
1823
+ return {
1824
+ type: "redirect",
1825
+ status: bridge.statusCode || 302,
1826
+ headers: new Headers(bridge.headers),
1827
+ cookies: [...bridge.cookies],
1828
+ };
1829
+ }
1830
+
1831
+ let errorMeta = {};
1832
+ if (isError) {
1833
+ const serializedError = {
1834
+ message: isDevelopment
1835
+ ? (caughtError?.message || "An error occurred in the Server Components render")
1836
+ : "An error occurred in the Server Components render",
1837
+ name: caughtError?.name || "Error",
1838
+ stack: isDevelopment ? caughtError?.stack : undefined,
1839
+ };
1840
+ try {
1841
+ await requestStorage.run(context, async () => {
1842
+ jsx = await getErrorJSX(reqPath, queryObj, serializedError, isDevelopment, errorMeta);
1843
+ });
1844
+ } catch (e) {
1845
+ console.error("[Edge ISG] Failed to render error JSX:", e);
1846
+ }
1847
+ }
1848
+
1849
+ const genMeta = {
1850
+ status: isError ? 500 : isNotFound.value ? 404 : 200,
1851
+ generatedAt: Date.now(),
1852
+ revalidate: combinedRevalidate,
1853
+ tags: tags || [],
1854
+ ppr: Boolean(isPprConfig),
1855
+ holes: pprHoles,
1856
+ effects: {
1857
+ redirect: bridge.headers.get("Location") || null,
1858
+ cookies: [...bridge.cookies],
1859
+ },
1860
+ };
1861
+
1862
+ const shouldCacheISG =
1863
+ (!isDevelopment &&
1864
+ genMeta.status === 200 &&
1865
+ !isNotFound.value &&
1866
+ !dynamicState.value &&
1867
+ allowISGValue !== false &&
1868
+ Object.keys(queryObj).length === 0) ||
1869
+ platformContext.isSSG === true;
1870
+
1871
+ if (!shouldCacheISG && inFlightKey) {
1872
+ inFlightGenerations.delete(inFlightKey);
1873
+ }
1874
+
1875
+ // Generic fallback for double crash
1876
+ if (queryObj.double_crash === "true" || (isError && !jsx)) {
1877
+ const errMsg = isDevelopment
1878
+ ? (caughtError?.message || "Error")
1879
+ : "An error occurred in the Server Components render";
1880
+ const fallbackBody = `
1881
+ <div class="min-h-screen bg-slate-950 text-slate-100 p-6">
1882
+ <header class="py-4"><a href="/">← Back to Home</a></header>
1883
+ <h2>Dinou Page Boundary Captured an Error</h2>
1884
+ <p>[Error]: ${errMsg}</p>
1885
+ <h2>Application Error</h2><pre>Double Crash! The custom error boundary component itself has crashed!</pre>
1886
+ </div>
1887
+ `;
1888
+ return {
1889
+ type: "html",
1890
+ html: `<!DOCTYPE html><html><head><meta charset="UTF-8"><title>Dinou</title></head><body data-hydrated="true">${fallbackBody}</body></html>`,
1891
+ status: genMeta.status,
1892
+ headers: new Headers(bridge.headers),
1893
+ cookies: [...bridge.cookies],
1894
+ };
1895
+ }
1896
+
1897
+ const clientManifest = getClientManifest();
1898
+ const rscStream = renderRSCStream(jsx, clientManifest, { runtime: "edge" });
1899
+
1900
+ if (platformContext && platformContext.renderHtmlStream) {
1901
+ const [streamForSsr, streamForCache] = rscStream.tee();
1902
+
1903
+ let bootstrapScriptContent = "";
1904
+ const knownLayouts = getDiscoveredLayouts();
1905
+ bootstrapScriptContent += `window.__DINOU_LAYOUTS__ = ${JSON.stringify(knownLayouts)};\n`;
1906
+ if (shouldCacheISG && !isPprConfig) {
1907
+ bootstrapScriptContent += "window.__DINOU_USE_STATIC__ = true;\n";
1908
+ } else {
1909
+ bootstrapScriptContent += "window.__DINOU_USE_STATIC__ = false;\n";
1910
+ if (isPprConfig) {
1911
+ bootstrapScriptContent += "window.__DINOU_PPR__ = true;\n";
1912
+ }
1913
+ }
1914
+ if (isDevelopment) {
1915
+ const isStrictMode = dinouConfig?.reactStrictMode !== false;
1916
+ bootstrapScriptContent += `window.__DINOU_STRICT_MODE__ = ${isStrictMode ? "true" : "false"};\n`;
1917
+ }
1918
+ if (isError) {
1919
+ const clientErrMsg = isDevelopment
1920
+ ? (caughtError?.message || "Unknown error")
1921
+ : "An error occurred in the Server Components render";
1922
+ bootstrapScriptContent += `window.__DINOU_ERROR_MESSAGE__=${JSON.stringify(
1923
+ clientErrMsg
1924
+ )};window.__DINOU_ERROR_NAME__=${JSON.stringify(
1925
+ caughtError?.name || "Error"
1926
+ )};window.__DINOU_ERROR_STACK__=${JSON.stringify(
1927
+ isDevelopment ? (caughtError?.stack || "") : ""
1928
+ )};window.__DINOU_ERROR_SEGMENT__=${JSON.stringify(
1929
+ errorMeta?.hasLayout ? "page" : "layout"
1930
+ )};window.__DINOU_ACTIVE_LAYOUT__=${JSON.stringify(
1931
+ errorMeta?.appliedLayoutKey || null
1932
+ )};\n`;
1933
+ if (!errorMeta?.hasLayout) {
1934
+ bootstrapScriptContent += 'document.body.setAttribute("data-hydrated", "true");\n';
1935
+ }
1936
+ }
1937
+ if (bridge._injectedScripts) {
1938
+ const clean = bridge._injectedScripts.replace(/<\/?script>/g, "");
1939
+ bootstrapScriptContent += clean + "\n";
1940
+ }
1941
+
1942
+ const clientEntry = getAssetFromManifest("main.js");
1943
+
1944
+ const bootstrapModules = isDevelopment
1945
+ ? [
1946
+ isWebpack ? undefined : getAssetFromManifest("runtime.js"),
1947
+ clientEntry,
1948
+ ].filter(Boolean)
1949
+ : [clientEntry];
1950
+
1951
+ if (isDevelopment && !isWebpack) {
1952
+ const hmrPort = process.env.HMR_PORT || (Number(process.env.PORT || 3000) + 1);
1953
+ bootstrapScriptContent += `window.$RefreshReg$ = window.$RefreshReg$ || function() {};\n`;
1954
+ bootstrapScriptContent += `window.$RefreshSig$ = window.$RefreshSig$ || function() { return function(type) { return type; }; };\n`;
1955
+ bootstrapScriptContent += `window.HMR_WEBSOCKET_URL="ws://localhost:${hmrPort}";\n`;
1956
+ }
1957
+
1958
+ let htmlStream;
1959
+ try {
1960
+ await requestStorage.run(context, async () => {
1961
+ htmlStream = await platformContext.renderHtmlStream(streamForSsr, {
1962
+ bootstrapModules,
1963
+ bootstrapScriptContent,
1964
+ onError(err) {
1965
+ if (!platformContext?.isSSG || process.env.DINOU_DEBUG) {
1966
+ console.error("[Edge Native SSR] Stream error:", err);
1967
+ }
1968
+ },
1969
+ });
1970
+ });
1971
+ } catch (ssrErr) {
1972
+ if (!platformContext?.isSSG || process.env.DINOU_DEBUG) {
1973
+ console.error("[Edge Native SSR] SSR render threw error, falling back to getErrorJSX:", ssrErr.message);
1974
+ }
1975
+ isError = true;
1976
+ caughtError = ssrErr;
1977
+ bridge.status(500);
1978
+ genMeta.status = 500;
1979
+
1980
+ const serializedError = {
1981
+ message: isDevelopment
1982
+ ? (ssrErr?.message || "An error occurred in the Server Components render")
1983
+ : "An error occurred in the Server Components render",
1984
+ name: ssrErr?.name || "Error",
1985
+ stack: isDevelopment ? ssrErr?.stack : undefined,
1986
+ };
1987
+
1988
+ let errorJsx;
1989
+ const errorMeta = {};
1990
+ try {
1991
+ await requestStorage.run(context, async () => {
1992
+ errorJsx = await getErrorJSX(reqPath, queryObj, serializedError, isDevelopment, errorMeta);
1993
+ });
1994
+ } catch (errJsxErr) {
1995
+ console.error("[Edge Native SSR] Failed to get error JSX:", errJsxErr);
1996
+ }
1997
+
1998
+ if (!errorJsx) {
1999
+ const errMsg = isDevelopment
2000
+ ? (ssrErr?.message || "Error")
2001
+ : "An error occurred in the Server Components render";
2002
+ const stackHtml = isDevelopment && serializedError.stack
2003
+ ? `<pre style="background:#0f172a;color:#f8fafc;padding:16px;border-radius:8px;overflow-x:auto;font-size:0.8125rem;line-height:1.6;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;">${serializedError.stack.replace(/</g, "&lt;").replace(/>/g, "&gt;")}</pre>`
2004
+ : "";
2005
+ const devHelp = isDevelopment
2006
+ ? `<p style="margin:0 0 16px 0;font-size:0.875rem;color:#9f1239;line-height:1.5;">An unhandled error occurred during rendering. You can provide a custom error UI by creating an <code style="background:#ffe4e6;padding:2px 6px;border-radius:4px;font-weight:600;">error.tsx</code> file in your route folder.</p>`
2007
+ : "";
2008
+ return {
2009
+ type: "html",
2010
+ html: `<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Dinou Dev Error</title></head><body style="margin:0;background:#fff1f2;padding:20px;"><div style="font-family:system-ui,-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;padding:32px;max-width:960px;margin:40px auto;background-color:#fff1f2;border:1px solid #fecdd3;border-radius:12px;color:#9f1239;box-shadow:0 10px 25px -5px rgba(0,0,0,0.1);"><div style="display:flex;align-items:center;gap:10px;margin-bottom:12px;"><span style="background:#e11d48;color:white;padding:2px 8px;border-radius:9999px;font-size:12px;font-weight:bold;">${isDevelopment ? "Dinou Dev Error" : "Application Error"}</span><h2 style="margin:0;font-size:1.25rem;font-weight:700;color:#881337;">${errMsg.replace(/</g, "&lt;").replace(/>/g, "&gt;")}</h2></div>${devHelp}${stackHtml}</div></body></html>`,
2011
+ status: 500,
2012
+ headers: new Headers(bridge.headers),
2013
+ cookies: [...bridge.cookies],
2014
+ };
2015
+ }
2016
+
2017
+ const errorRscStream = renderRSCStream(errorJsx, clientManifest, { runtime: "edge" });
2018
+ const errorClientEntry = getAssetFromManifest("main.js");
2019
+ const errorBootstrapModules = isDevelopment
2020
+ ? [
2021
+ isWebpack ? undefined : getAssetFromManifest("runtime.js"),
2022
+ errorClientEntry,
2023
+ ].filter(Boolean)
2024
+ : [errorClientEntry];
2025
+
2026
+ let errorBootstrapScript = "";
2027
+ const knownLayouts = getDiscoveredLayouts();
2028
+ errorBootstrapScript += `window.__DINOU_LAYOUTS__ = ${JSON.stringify(knownLayouts)};\n`;
2029
+ errorBootstrapScript += "window.__DINOU_USE_STATIC__ = false;\n";
2030
+ errorBootstrapScript += `window.__DINOU_ERROR_MESSAGE__=${JSON.stringify(
2031
+ serializedError.message
2032
+ )};window.__DINOU_ERROR_NAME__=${JSON.stringify(
2033
+ serializedError.name
2034
+ )};window.__DINOU_ERROR_STACK__=${JSON.stringify(
2035
+ isDevelopment ? (serializedError.stack || "") : ""
2036
+ )};window.__DINOU_ERROR_SEGMENT__=${JSON.stringify(
2037
+ errorMeta?.hasLayout ? "page" : "layout"
2038
+ )};window.__DINOU_ACTIVE_LAYOUT__=${JSON.stringify(
2039
+ errorMeta?.appliedLayoutKey || null
2040
+ )};\n`;
2041
+ if (!errorMeta?.hasLayout) {
2042
+ errorBootstrapScript += 'document.body.setAttribute("data-hydrated", "true");\n';
2043
+ }
2044
+ if (isDevelopment) {
2045
+ const isStrictMode = dinouConfig?.reactStrictMode !== false;
2046
+ errorBootstrapScript += `window.__DINOU_STRICT_MODE__ = ${isStrictMode ? "true" : "false"};\n`;
2047
+ }
2048
+ if (isDevelopment && !isWebpack) {
2049
+ const hmrPort = process.env.HMR_PORT || (Number(process.env.PORT || 3000) + 1);
2050
+ errorBootstrapScript += `window.$RefreshReg$ = window.$RefreshReg$ || function() {};\n`;
2051
+ errorBootstrapScript += `window.$RefreshSig$ = window.$RefreshSig$ || function() { return function(type) { return type; }; };\n`;
2052
+ errorBootstrapScript += `window.HMR_WEBSOCKET_URL="ws://localhost:${hmrPort}";\n`;
2053
+ }
2054
+
2055
+ try {
2056
+ await requestStorage.run(context, async () => {
2057
+ htmlStream = await platformContext.renderHtmlStream(errorRscStream, {
2058
+ bootstrapModules: errorBootstrapModules,
2059
+ bootstrapScriptContent: errorBootstrapScript,
2060
+ onError(err) {
2061
+ if (!platformContext?.isSSG || process.env.DINOU_DEBUG) {
2062
+ console.error("[Edge Native SSR Error Page] Stream error:", err);
2063
+ }
2064
+ },
2065
+ });
2066
+ });
2067
+ } catch (errStreamErr) {
2068
+ console.error("[Edge Native SSR] Error stream rendering failed, using emergency HTML fallback:", errStreamErr);
2069
+ const errMsg = isDevelopment
2070
+ ? (serializedError.message || "Error")
2071
+ : "An error occurred in the Server Components render";
2072
+ const stackHtml = isDevelopment && serializedError.stack
2073
+ ? `<pre style="background:#0f172a;color:#f8fafc;padding:16px;border-radius:8px;overflow-x:auto;font-size:0.8125rem;line-height:1.6;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;">${serializedError.stack.replace(/</g, "&lt;").replace(/>/g, "&gt;")}</pre>`
2074
+ : "";
2075
+ const devHelp = isDevelopment
2076
+ ? `<p style="margin:0 0 16px 0;font-size:0.875rem;color:#9f1239;line-height:1.5;">An unhandled error occurred during rendering. You can provide a custom error UI by creating an <code style="background:#ffe4e6;padding:2px 6px;border-radius:4px;font-weight:600;">error.tsx</code> file in your route folder.</p>`
2077
+ : "";
2078
+ return {
2079
+ type: "html",
2080
+ html: `<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Dinou Dev Error</title></head><body style="margin:0;background:#fff1f2;padding:20px;"><div style="font-family:system-ui,-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;padding:32px;max-width:960px;margin:40px auto;background-color:#fff1f2;border:1px solid #fecdd3;border-radius:12px;color:#9f1239;box-shadow:0 10px 25px -5px rgba(0,0,0,0.1);"><div style="display:flex;align-items:center;gap:10px;margin-bottom:12px;"><span style="background:#e11d48;color:white;padding:2px 8px;border-radius:9999px;font-size:12px;font-weight:bold;">${isDevelopment ? "Dinou Dev Error" : "Application Error"}</span><h2 style="margin:0;font-size:1.25rem;font-weight:700;color:#881337;">${errMsg.replace(/</g, "&lt;").replace(/>/g, "&gt;")}</h2></div>${devHelp}${stackHtml}</div></body></html>`,
2081
+ status: 500,
2082
+ headers: new Headers(bridge.headers),
2083
+ cookies: [...bridge.cookies],
2084
+ };
2085
+ }
2086
+ }
2087
+
2088
+ if (bridge.headers.has("Location") || (bridge.statusCode >= 300 && bridge.statusCode < 400)) {
2089
+ return {
2090
+ type: "redirect",
2091
+ status: bridge.statusCode || 302,
2092
+ headers: new Headers(bridge.headers),
2093
+ cookies: [...bridge.cookies],
2094
+ };
2095
+ }
2096
+
2097
+ if (shouldCacheISG) {
2098
+ const rscKey = cleanPath ? `${cleanPath}/rsc.rsc` : "rsc.rsc";
2099
+ let fullHtml = "";
2100
+ let rscPayload = "";
2101
+ try {
2102
+ const [rsc, html] = await Promise.all([
2103
+ new Response(streamForCache).text(),
2104
+ new Response(htmlStream).text(),
2105
+ ]);
2106
+ rscPayload = rsc;
2107
+ fullHtml = html;
2108
+ } catch (drainErr) {
2109
+ console.error("[Edge ISG] Error reading streams:", drainErr);
2110
+ }
2111
+
2112
+ if (fullHtml) {
2113
+ if (!dynamicState.value || platformContext.isSSG === true) {
2114
+ try {
2115
+ await storage.set(rscKey, rscPayload);
2116
+ await storage.set(htmlKey, fullHtml, genMeta);
2117
+ await storage.set(metaKey, JSON.stringify(genMeta));
2118
+ if (genMeta.ppr) {
2119
+ const shellHtmlKey = cleanPath ? `${cleanPath}/shell.html` : "shell.html";
2120
+ const shellRscKey = cleanPath ? `${cleanPath}/shell.rsc` : "shell.rsc";
2121
+ await storage.set(shellHtmlKey, fullHtml, genMeta);
2122
+ await storage.set(shellRscKey, rscPayload, { ppr: true });
2123
+ }
2124
+ if (!platformContext?.isSSG || process.env.DINOU_DEBUG) {
2125
+ console.log(`✅ [Edge ISG] Successfully cached ${reqPath} to KV`);
2126
+ }
2127
+ } catch (cacheErr) {
2128
+ console.error("[Edge ISG] Error caching to KV:", cacheErr);
2129
+ }
2130
+ } else {
2131
+ if (!platformContext?.isSSG || process.env.DINOU_DEBUG) {
2132
+ console.log(`ℹ️ [Edge ISG] Dynamic bailout detected during render for ${reqPath}, skipping KV cache`);
2133
+ }
2134
+ }
2135
+ return {
2136
+ type: "html",
2137
+ html: fullHtml,
2138
+ status: genMeta.status,
2139
+ headers: new Headers(bridge.headers),
2140
+ cookies: [...bridge.cookies],
2141
+ };
2142
+ }
2143
+ }
2144
+
2145
+ // Dynamic streaming responses cannot be shared across multiple requests in inFlightGenerations
2146
+ if (inFlightKey) {
2147
+ inFlightGenerations.delete(inFlightKey);
2148
+ }
2149
+
2150
+ return {
2151
+ type: "stream",
2152
+ stream: htmlStream,
2153
+ status: genMeta.status,
2154
+ headers: new Headers(bridge.headers),
2155
+ cookies: [...bridge.cookies],
2156
+ };
2157
+ }
2158
+
2159
+ throw new Error(
2160
+ "[Dinou Edge] platformContext.renderHtmlStream is required for Edge rendering. Ensure your adapter is using the dual-bundle architecture with edge-ssr."
2161
+ );
2162
+ })();
2163
+
2164
+ if (inFlightKey) {
2165
+ inFlightGenerations.set(inFlightKey, isgPromise);
2166
+ }
2167
+ }
2168
+
2169
+ try {
2170
+ const result = await isgPromise;
2171
+ if (result.type === "redirect") {
2172
+ bridge.status(result.status || 302);
2173
+ if (result.headers) {
2174
+ for (const [k, v] of result.headers.entries()) {
2175
+ bridge.setHeader(k, v);
2176
+ }
2177
+ }
2178
+ if (result.cookies) {
2179
+ for (const c of result.cookies) {
2180
+ if (!bridge.cookies.includes(c)) {
2181
+ bridge.cookies.push(c);
2182
+ }
2183
+ }
2184
+ }
2185
+ bridge.end();
2186
+ return bridge.toResponse();
2187
+ }
2188
+
2189
+ if (result.headers) {
2190
+ for (const [k, v] of result.headers.entries()) {
2191
+ bridge.setHeader(k, v);
2192
+ }
2193
+ }
2194
+ if (result.cookies) {
2195
+ for (const c of result.cookies) {
2196
+ if (!bridge.cookies.includes(c)) {
2197
+ bridge.cookies.push(c);
2198
+ }
2199
+ }
2200
+ }
2201
+ bridge.setHeader("Content-Type", "text/html; charset=utf-8");
2202
+ bridge.setHeader("Cache-Control", "no-store, no-cache, must-revalidate");
2203
+ bridge.status(result.status || 200);
2204
+
2205
+ if (result.type === "stream") {
2206
+ bridge.setEdgeStream(result.stream);
2207
+ return bridge.toResponse();
2208
+ }
2209
+
2210
+ bridge.end(result.html);
2211
+ return bridge.toResponse();
2212
+ } finally {
2213
+ if (inFlightKey) {
2214
+ inFlightGenerations.delete(inFlightKey);
2215
+ }
2216
+ }
2217
+ }
2218
+
2219
+ // Node.js Runtime: Serve static pre-rendered HTML if available in production
2220
+ if (!isDevelopment && !dynamicState.value && pagePath && !isPathBlocked) {
2221
+ revalidating(reqPath, dynamicState);
2222
+ let htmlPathOld;
2223
+ if (regenerating.has(reqPath)) {
2224
+ htmlPathOld = path.join(".dinou/dist2", reqPath, "index._old.html");
2225
+ }
2226
+ const htmlPath = path.join(".dinou/dist2", reqPath, "index.html");
2227
+ const fileToRead = (htmlPathOld && existsSync(htmlPathOld)) ? htmlPathOld : htmlPath;
2228
+
2229
+ if (existsSync(fileToRead) && !dynamicState.value) {
2230
+ bridge.setHeader("Content-Type", "text/html; charset=utf-8");
2231
+ bridge.setHeader("Cache-Control", "no-store, no-cache, must-revalidate");
2232
+ let status = getStatus(reqPath);
2233
+ try {
2234
+ let htmlContent = readFileSync(fileToRead, "utf8");
2235
+ let buildId = "";
2236
+ try {
2237
+ const metadataPath = path.join(".dinou/dist2", reqPath, "metadata.json");
2238
+ if (existsSync(metadataPath)) {
2239
+ const metaObj = JSON.parse(readFileSync(metadataPath, "utf8"));
2240
+ buildId = metaObj.generatedAt || "";
2241
+ if (!status && metaObj.status) {
2242
+ status = metaObj.status;
2243
+ }
2244
+ if (metaObj.ppr || isPprConfig) {
2245
+ return handlePprResume({
2246
+ reqPath,
2247
+ queryObj,
2248
+ simReq,
2249
+ bridge,
2250
+ platformContext,
2251
+ rootContext,
2252
+ cachedItem: { content: htmlContent, metadata: metaObj },
2253
+ metadata: metaObj,
2254
+ isDevelopment,
2255
+ getDiscoveredLayouts,
2256
+ createRequestContext,
2257
+ copyCustomContextProperties,
2258
+ });
2259
+ }
2260
+ }
2261
+ } catch (e) {}
2262
+
2263
+ bridge.status(status || 200);
2264
+
2265
+ const knownLayouts = getDiscoveredLayouts();
2266
+ let scripts = `<script>window.__DINOU_USE_STATIC__=true;window.__DINOU_LAYOUTS__=${JSON.stringify(knownLayouts)};</script>`;
2267
+ if (htmlPathOld && existsSync(htmlPathOld)) {
2268
+ scripts += `<script>window.__DINOU_USE_OLD_RSC__=true;</script>`;
2269
+ }
2270
+ if (buildId) {
2271
+ scripts += `<script>window.__DINOU_BUILD_ID__="${buildId}";</script>`;
2272
+ }
2273
+
2274
+ htmlContent = htmlContent.replace("</head>", `${scripts}</head>`);
2275
+ bridge.end(htmlContent);
2276
+ return bridge.toResponse();
2277
+ } catch (err) {
2278
+ console.error("[Dinou] Error reading HTML file:", err);
2279
+ if (!bridge.headersSent) bridge.status(500).send("Server Error");
2280
+ return bridge.toResponse();
2281
+ }
2282
+ }
2283
+ }
2284
+
2285
+ if (!bridge.headersSent) {
2286
+ bridge.status(500).send("Internal Server Error: No renderer available");
2287
+ }
2288
+ return bridge.toResponse();
2289
+ }
2290
+
2291
+ module.exports = {
2292
+ handleRequest,
2293
+ WebResponseBridge,
2294
+ };
2295
+ module.exports.default = handleRequest;
2296
+