dinou 6.1.1 → 7.1.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.
- package/CHANGELOG.md +106 -0
- package/README.md +9352 -1081
- package/cli.js +314 -91
- package/dinou/adapters/bun.js +62 -0
- package/dinou/adapters/cloudflare.js +65 -0
- package/dinou/adapters/deno.js +48 -0
- package/dinou/adapters/netlify.js +85 -0
- package/dinou/adapters/node.mjs +31 -0
- package/dinou/bun/build.mjs +1077 -0
- package/dinou/cloudflare/build.mjs +1121 -0
- package/dinou/cloudflare/shims/child_process.js +14 -0
- package/dinou/cloudflare/shims/empty.js +2 -0
- package/dinou/cloudflare/shims/fs.js +99 -0
- package/dinou/cloudflare/shims/os.js +29 -0
- package/dinou/config.js +1 -0
- package/dinou/config.mjs +2 -0
- package/dinou/constants.js +29 -4
- package/dinou/core/babel-esm-loader.js +66 -37
- package/dinou/core/bailout-proxy.js +61 -0
- package/dinou/core/build-static-pages.js +322 -165
- package/dinou/core/cache-revalidate.js +471 -144
- package/dinou/core/cache-slot.js +387 -0
- package/dinou/core/client-redirect.jsx +12 -7
- package/dinou/core/client-webpack.jsx +457 -82
- package/dinou/core/client.jsx +452 -82
- package/dinou/core/config.js +11 -0
- package/dinou/core/dinou-paths.js +52 -0
- package/dinou/core/edge-ssr.js +122 -0
- package/dinou/core/export-static.mjs +64 -0
- package/dinou/core/generate-route-types.js +252 -0
- package/dinou/core/generate-static-page.js +55 -94
- package/dinou/core/generate-static-rsc.js +47 -15
- package/dinou/core/generate-static.js +212 -10
- package/dinou/core/generating-isg.js +5 -0
- package/dinou/core/get-asset-from-manifest.js +9 -3
- package/dinou/core/get-error-jsx.js +221 -41
- package/dinou/core/get-file-path-and-dynamic-params.js +9 -13
- package/dinou/core/get-jsx.js +175 -96
- package/dinou/core/handler.js +2303 -0
- package/dinou/core/http-adapter.js +115 -0
- package/dinou/core/import-module.js +83 -53
- package/dinou/core/layout-functions.js +183 -0
- package/dinou/core/link.jsx +18 -6
- package/dinou/core/load-dinou-config.js +60 -0
- package/dinou/core/manifest-provider.js +261 -0
- package/dinou/core/navigation-utils.js +4 -0
- package/dinou/core/navigation.js +50 -39
- package/dinou/core/parse-exports.js +252 -2
- package/dinou/core/ppr-context.js +105 -0
- package/dinou/core/ppr-runtime.js +263 -0
- package/dinou/core/redirect.jsx +20 -19
- package/dinou/core/render-jsx-to-client-jsx.js +91 -87
- package/dinou/core/request-context.js +47 -13
- package/dinou/core/resolve-module-ppr.js +34 -0
- package/dinou/core/revalidating.js +28 -9
- package/dinou/core/route-generator.js +107 -0
- package/dinou/core/rsc-renderer.js +90 -0
- package/dinou/core/run-ssg.js +19 -0
- package/dinou/core/scan-dependency-components.js +218 -0
- package/dinou/core/server-function-proxy-webpack.js +3 -0
- package/dinou/core/server-function-proxy.js +3 -0
- package/dinou/core/slot.js +302 -0
- package/dinou/core/status-manifest.js +41 -1
- package/dinou/core/storage-adapter.js +522 -0
- package/dinou/core/url-resolver.js +16 -16
- package/dinou/core/vfs.js +139 -68
- package/dinou/deno/build.mjs +1112 -0
- package/dinou/deno/seed-kv.js +115 -0
- package/dinou/esbuild/build.mjs +51 -1
- package/dinou/esbuild/dev.mjs +290 -224
- package/dinou/esbuild/helpers-esbuild/get-config-esbuild-prod.mjs +3 -2
- package/dinou/esbuild/helpers-esbuild/get-config-esbuild.mjs +104 -16
- package/dinou/esbuild/helpers-esbuild/get-esbuild-entries.mjs +31 -19
- package/dinou/esbuild/helpers-esbuild/strip-namespace.mjs +9 -0
- package/dinou/esbuild/helpers-esbuild/swc-disk-cache.mjs +225 -0
- package/dinou/esbuild/helpers-esbuild/update-manifest-for-module.mjs +7 -0
- package/dinou/esbuild/helpers-esbuild/write.mjs +111 -2
- package/dinou/esbuild/plugins-esbuild/assets-plugin.mjs +236 -161
- package/dinou/esbuild/plugins-esbuild/css-processor-plugin.mjs +101 -5
- package/dinou/esbuild/plugins-esbuild/react-client-manifest-plugin.mjs +63 -24
- package/dinou/esbuild/plugins-esbuild/server-functions-plugin.mjs +177 -56
- package/dinou/esbuild/plugins-esbuild/skip-missing-entry-points-plugin.mjs +3 -0
- package/dinou/esbuild/plugins-esbuild/stable-chunk-names-and-maps-plugin.mjs +182 -137
- package/dinou/esbuild/plugins-postcss/postcss-extract-plugin.js +38 -11
- package/dinou/esbuild/react-refresh/esm-hmr/client.mjs +55 -8
- package/dinou/esbuild/react-refresh/esm-hmr/server.js +1 -1
- package/dinou/esbuild/react-refresh/esm-hmr-plugin.mjs +238 -101
- package/dinou/esbuild/react-refresh/is-react-refresh-boundary.mjs +18 -4
- package/dinou/index.d.ts +315 -11
- package/dinou/index.js +12 -6
- package/dinou/index.mjs +9 -0
- package/dinou/node/build.mjs +195 -0
- package/dinou/node/bundle-dual-engine.mjs +1012 -0
- package/dinou/node/dev.mjs +2589 -0
- package/dinou/node/port-selector.mjs +164 -0
- package/dinou/node/terminal-status.mjs +345 -0
- package/dinou/package.json +16 -14
- package/dinou/rollup/react-refresh/esm-hmr/client.js +66 -10
- package/dinou/rollup/react-refresh/esm-hmr/server.js +1 -1
- package/dinou/rollup/react-refresh/is-react-refresh-boundary.js +24 -5
- package/dinou/rollup/react-refresh/react-refresh-runtime.js +13 -6
- package/dinou/rollup/react-refresh/react-refresh-wrap-modules.js +56 -11
- package/dinou/rollup/react-refresh/rollup-plugin-esm-hmr.js +176 -48
- package/dinou/rollup/rollup-plugins/dinou-asset-plugin.js +9 -5
- package/dinou/rollup/rollup-plugins/rollup-plugin-memory.js +66 -0
- package/dinou/rollup/rollup-plugins/rollup-plugin-react-client-manifest.js +468 -123
- package/dinou/rollup/rollup-plugins/rollup-plugin-server-functions.js +44 -12
- package/dinou/rollup/rollup-plugins/rollup-plugin-swc.js +100 -0
- package/dinou/rollup/rollup.config.js +155 -43
- package/dinou/server.d.ts +114 -12
- package/dinou/server.js +35 -10
- package/dinou/server.mjs +30 -9
- package/dinou/webpack/helpers/get-webpack-entries.js +14 -19
- package/dinou/webpack/loaders/server-functions-loader.js +22 -1
- package/dinou/webpack/loaders/swc-loader.js +76 -0
- package/dinou/webpack/plugins/manifest-generator-plugin.js +1 -0
- package/dinou/webpack/plugins/server-functions-plugin.js +75 -19
- package/dinou/webpack/plugins/webpack-memory-plugin.js +66 -0
- package/dinou/webpack/webpack.config.js +227 -29
- package/eject.js +113 -34
- package/package.json +18 -16
- package/dinou/core/asset-require-hook.js +0 -58
- package/dinou/core/client-error-webpack.jsx +0 -432
- package/dinou/core/client-error.jsx +0 -432
- package/dinou/core/concurrency-manager.js +0 -48
- package/dinou/core/context-proxy.js +0 -68
- package/dinou/core/generate-static-pages.js +0 -130
- package/dinou/core/generate-static-rscs.js +0 -151
- package/dinou/core/get-ssg-metadata.js +0 -31
- package/dinou/core/register-loader.mjs +0 -11
- package/dinou/core/register-paths.js +0 -27
- package/dinou/core/render-app-to-html.js +0 -430
- package/dinou/core/render-html.js +0 -407
- package/dinou/core/server.js +0 -1542
- package/dinou/esbuild/plugins-esbuild/write-metafile-plugin.mjs +0 -16
|
@@ -0,0 +1,387 @@
|
|
|
1
|
+
// dinou/core/cache-slot.js
|
|
2
|
+
// Primitive for Vertical Segmentation (Micro-ISR) in React Server Components.
|
|
3
|
+
// Caches and invalidates component sub-trees inside pages using revalidate & revalidateTag.
|
|
4
|
+
|
|
5
|
+
const path = require("path");
|
|
6
|
+
const { pathToFileURL } = require("url");
|
|
7
|
+
const React = require("react");
|
|
8
|
+
const { getStorageAdapter } = require("./storage-adapter.js");
|
|
9
|
+
const { asyncRenderJSXToClientJSX } = require("./render-jsx-to-client-jsx.js");
|
|
10
|
+
|
|
11
|
+
const INFLIGHT_KEY = Symbol.for("dinou.slot.inflight.renders");
|
|
12
|
+
if (!globalThis[INFLIGHT_KEY]) {
|
|
13
|
+
globalThis[INFLIGHT_KEY] = new Map();
|
|
14
|
+
}
|
|
15
|
+
const inflightSlotRenders = globalThis[INFLIGHT_KEY];
|
|
16
|
+
|
|
17
|
+
const BG_REVAL_KEY = Symbol.for("dinou.slot.background.revalidations");
|
|
18
|
+
if (!globalThis[BG_REVAL_KEY]) {
|
|
19
|
+
globalThis[BG_REVAL_KEY] = new Set();
|
|
20
|
+
}
|
|
21
|
+
const backgroundRevalidations = globalThis[BG_REVAL_KEY];
|
|
22
|
+
|
|
23
|
+
const SLOT_REGISTRY_KEY = Symbol.for("dinou.slot.registry");
|
|
24
|
+
if (!globalThis[SLOT_REGISTRY_KEY]) {
|
|
25
|
+
globalThis[SLOT_REGISTRY_KEY] = new Map();
|
|
26
|
+
}
|
|
27
|
+
const slotRegistry = globalThis[SLOT_REGISTRY_KEY];
|
|
28
|
+
|
|
29
|
+
let _DinouCacheSlotBoundary = null;
|
|
30
|
+
function getCacheSlotBoundary() {
|
|
31
|
+
if (!_DinouCacheSlotBoundary) {
|
|
32
|
+
let { DinouCacheSlotBoundary } = require("./slot.js");
|
|
33
|
+
if (
|
|
34
|
+
typeof DinouCacheSlotBoundary === "function" &&
|
|
35
|
+
DinouCacheSlotBoundary.$$typeof !== Symbol.for("react.client.reference")
|
|
36
|
+
) {
|
|
37
|
+
const slotPath = path.resolve(__dirname, "slot.js");
|
|
38
|
+
const fileUrl = pathToFileURL(slotPath).href;
|
|
39
|
+
let regFn = null;
|
|
40
|
+
try {
|
|
41
|
+
const pkg = require("react-server-dom-webpack/server");
|
|
42
|
+
regFn = pkg.registerClientReference;
|
|
43
|
+
} catch (e) {}
|
|
44
|
+
if (!regFn) {
|
|
45
|
+
try {
|
|
46
|
+
const pkg = require("@roggc/react-server-dom-esm/server.node.js");
|
|
47
|
+
regFn = pkg.registerClientReference;
|
|
48
|
+
} catch (e) {}
|
|
49
|
+
}
|
|
50
|
+
if (regFn) {
|
|
51
|
+
DinouCacheSlotBoundary = regFn(
|
|
52
|
+
DinouCacheSlotBoundary,
|
|
53
|
+
fileUrl,
|
|
54
|
+
"DinouCacheSlotBoundary"
|
|
55
|
+
);
|
|
56
|
+
} else {
|
|
57
|
+
Object.defineProperties(DinouCacheSlotBoundary, {
|
|
58
|
+
$$typeof: { value: Symbol.for("react.client.reference") },
|
|
59
|
+
$$id: { value: fileUrl + "#DinouCacheSlotBoundary" },
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
_DinouCacheSlotBoundary = DinouCacheSlotBoundary;
|
|
64
|
+
}
|
|
65
|
+
return _DinouCacheSlotBoundary;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Serializes a resolved JSX element tree into a JSON-safe string,
|
|
70
|
+
* preserving symbols, React 19 transitional elements, and client component references.
|
|
71
|
+
*/
|
|
72
|
+
function serializeJSX(jsx) {
|
|
73
|
+
return JSON.stringify(jsx, (key, value) => {
|
|
74
|
+
if (typeof value === "symbol") {
|
|
75
|
+
return { __dinou_symbol__: Symbol.keyFor(value) || value.description };
|
|
76
|
+
}
|
|
77
|
+
if (
|
|
78
|
+
(typeof value === "function" || typeof value === "object") &&
|
|
79
|
+
value !== null &&
|
|
80
|
+
value.$$typeof === Symbol.for("react.client.reference")
|
|
81
|
+
) {
|
|
82
|
+
return {
|
|
83
|
+
__dinou_client_ref__: true,
|
|
84
|
+
id: value.$$id || value.name,
|
|
85
|
+
name: value.name,
|
|
86
|
+
async: value.$$async,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
if (
|
|
90
|
+
value &&
|
|
91
|
+
typeof value === "object" &&
|
|
92
|
+
value.$$typeof === Symbol.for("react.transitional.element")
|
|
93
|
+
) {
|
|
94
|
+
return {
|
|
95
|
+
__dinou_element__: true,
|
|
96
|
+
type: value.type,
|
|
97
|
+
props: value.props,
|
|
98
|
+
key: value.key,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
return value;
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Deserializes a JSON string back into a genuine React 19 element tree.
|
|
107
|
+
*/
|
|
108
|
+
function deserializeJSX(json) {
|
|
109
|
+
const parsed = JSON.parse(json, (key, value) => {
|
|
110
|
+
if (value && typeof value === "object") {
|
|
111
|
+
if (value.__dinou_symbol__) {
|
|
112
|
+
return Symbol.for(value.__dinou_symbol__);
|
|
113
|
+
}
|
|
114
|
+
if (value.__dinou_client_ref__) {
|
|
115
|
+
const fakeRef = function () {};
|
|
116
|
+
Object.defineProperties(fakeRef, {
|
|
117
|
+
$$typeof: { value: Symbol.for("react.client.reference") },
|
|
118
|
+
$$id: { value: value.id },
|
|
119
|
+
name: { value: value.name },
|
|
120
|
+
$$async: { value: value.async },
|
|
121
|
+
});
|
|
122
|
+
return fakeRef;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return value;
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
function toReactElement(node) {
|
|
129
|
+
if (node == null || typeof node !== "object") return node;
|
|
130
|
+
if (Array.isArray(node)) return node.map(toReactElement);
|
|
131
|
+
|
|
132
|
+
if (node.__dinou_element__) {
|
|
133
|
+
const type = node.type;
|
|
134
|
+
const props = {};
|
|
135
|
+
if (node.props) {
|
|
136
|
+
for (const [k, v] of Object.entries(node.props)) {
|
|
137
|
+
if (k === "key") continue;
|
|
138
|
+
props[k] = toReactElement(v);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
if (node.key != null) {
|
|
142
|
+
props.key = node.key;
|
|
143
|
+
}
|
|
144
|
+
const children = props.children;
|
|
145
|
+
if (Array.isArray(children)) {
|
|
146
|
+
delete props.children;
|
|
147
|
+
return React.createElement(type, props, ...children);
|
|
148
|
+
}
|
|
149
|
+
return React.createElement(type, props);
|
|
150
|
+
}
|
|
151
|
+
return node;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return toReactElement(parsed);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* DinouCacheSlot component.
|
|
159
|
+
*
|
|
160
|
+
* @param {object} props
|
|
161
|
+
* @param {string} [props.id] Unique slot identifier
|
|
162
|
+
* @param {string} [props.tag] Cache tag for granular revalidateTag invalidation
|
|
163
|
+
* @param {string[]} [props.tags] Array of cache tags
|
|
164
|
+
* @param {number} [props.revalidate] Revalidation TTL in seconds (0 = no-cache, >0 = ISR)
|
|
165
|
+
* @param {React.ReactNode | Function} props.children Child component(s)
|
|
166
|
+
*/
|
|
167
|
+
async function DinouCacheSlot(props) {
|
|
168
|
+
const { id, tag, tags, revalidate, children } = props || {};
|
|
169
|
+
|
|
170
|
+
const slotId = id || tag || (Array.isArray(tags) ? tags[0] : null);
|
|
171
|
+
if (!slotId) {
|
|
172
|
+
return typeof children === "function" ? await children() : children;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const allTags = Array.isArray(tags)
|
|
176
|
+
? [...tags]
|
|
177
|
+
: tag
|
|
178
|
+
? [tag]
|
|
179
|
+
: [];
|
|
180
|
+
|
|
181
|
+
const storageKey = `slots/${slotId}/slot.json`;
|
|
182
|
+
const storage = getStorageAdapter();
|
|
183
|
+
|
|
184
|
+
function wrapWithBoundary(content) {
|
|
185
|
+
const Boundary = getCacheSlotBoundary();
|
|
186
|
+
if (!Boundary) return content;
|
|
187
|
+
return React.createElement(Boundary, { id: slotId }, content);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Register in slotRegistry for on-demand endpoint re-rendering
|
|
191
|
+
slotRegistry.set(slotId, {
|
|
192
|
+
render: async () => {
|
|
193
|
+
const childElement = typeof children === "function" ? await children() : children;
|
|
194
|
+
return await asyncRenderJSXToClientJSX(childElement);
|
|
195
|
+
},
|
|
196
|
+
tags: allTags,
|
|
197
|
+
revalidate: typeof revalidate === "number" ? revalidate : undefined,
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
if (!storage) {
|
|
201
|
+
const directContent = typeof children === "function" ? await children() : children;
|
|
202
|
+
return wrapWithBoundary(directContent);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// 1. Check if cached in storage
|
|
206
|
+
let cached = null;
|
|
207
|
+
try {
|
|
208
|
+
cached = await storage.get(storageKey);
|
|
209
|
+
} catch (err) {}
|
|
210
|
+
|
|
211
|
+
const now = Date.now();
|
|
212
|
+
if (cached && cached.content) {
|
|
213
|
+
const meta = cached.metadata || {};
|
|
214
|
+
const generatedAt = meta.generatedAt || 0;
|
|
215
|
+
const revalidateSeconds =
|
|
216
|
+
typeof revalidate === "number"
|
|
217
|
+
? revalidate
|
|
218
|
+
: typeof meta.revalidate === "number"
|
|
219
|
+
? meta.revalidate
|
|
220
|
+
: undefined;
|
|
221
|
+
|
|
222
|
+
const isExpired =
|
|
223
|
+
typeof revalidateSeconds === "number" &&
|
|
224
|
+
revalidateSeconds > 0 &&
|
|
225
|
+
now > generatedAt + revalidateSeconds * 1000;
|
|
226
|
+
|
|
227
|
+
if (!isExpired) {
|
|
228
|
+
try {
|
|
229
|
+
const restored = deserializeJSX(cached.content);
|
|
230
|
+
return wrapWithBoundary(restored);
|
|
231
|
+
} catch (err) {}
|
|
232
|
+
} else {
|
|
233
|
+
// Stale-While-Revalidate
|
|
234
|
+
try {
|
|
235
|
+
const staleJSX = deserializeJSX(cached.content);
|
|
236
|
+
|
|
237
|
+
if (!backgroundRevalidations.has(storageKey)) {
|
|
238
|
+
backgroundRevalidations.add(storageKey);
|
|
239
|
+
const bgPromise = (async () => {
|
|
240
|
+
try {
|
|
241
|
+
const childElement = typeof children === "function" ? await children() : children;
|
|
242
|
+
const resolved = await asyncRenderJSXToClientJSX(childElement);
|
|
243
|
+
const serialized = serializeJSX(resolved);
|
|
244
|
+
await storage.set(storageKey, serialized, {
|
|
245
|
+
tags: allTags,
|
|
246
|
+
revalidate: revalidateSeconds,
|
|
247
|
+
generatedAt: Date.now(),
|
|
248
|
+
slotId,
|
|
249
|
+
});
|
|
250
|
+
} catch (e) {
|
|
251
|
+
console.error(`[DinouCacheSlot] Background revalidation failed for slot "${slotId}":`, e);
|
|
252
|
+
} finally {
|
|
253
|
+
backgroundRevalidations.delete(storageKey);
|
|
254
|
+
}
|
|
255
|
+
})();
|
|
256
|
+
|
|
257
|
+
try {
|
|
258
|
+
const { getContext } = require("./request-context.js");
|
|
259
|
+
const reqContext = getContext();
|
|
260
|
+
const ctx = reqContext?.ctx || reqContext?.platformContext?.ctx || reqContext?.req?.ctx;
|
|
261
|
+
if (ctx && typeof ctx.waitUntil === "function") {
|
|
262
|
+
ctx.waitUntil(bgPromise);
|
|
263
|
+
}
|
|
264
|
+
} catch (e) {}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
return wrapWithBoundary(staleJSX);
|
|
268
|
+
} catch (err) {}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// 2. Cache MISS or expired without usable stale content: Deduplicate synchronous render
|
|
273
|
+
if (inflightSlotRenders.has(storageKey)) {
|
|
274
|
+
try {
|
|
275
|
+
const deduped = await inflightSlotRenders.get(storageKey);
|
|
276
|
+
return wrapWithBoundary(deduped);
|
|
277
|
+
} catch (e) {}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const renderPromise = (async () => {
|
|
281
|
+
const childElement = typeof children === "function" ? await children() : children;
|
|
282
|
+
const resolved = await asyncRenderJSXToClientJSX(childElement);
|
|
283
|
+
const serialized = serializeJSX(resolved);
|
|
284
|
+
|
|
285
|
+
try {
|
|
286
|
+
await storage.set(storageKey, serialized, {
|
|
287
|
+
tags: allTags,
|
|
288
|
+
revalidate: typeof revalidate === "number" ? revalidate : undefined,
|
|
289
|
+
generatedAt: Date.now(),
|
|
290
|
+
slotId,
|
|
291
|
+
});
|
|
292
|
+
} catch (err) {
|
|
293
|
+
console.error(`[DinouCacheSlot] Failed to write cache for slot "${slotId}":`, err);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
return deserializeJSX(serialized);
|
|
297
|
+
})();
|
|
298
|
+
|
|
299
|
+
inflightSlotRenders.set(storageKey, renderPromise);
|
|
300
|
+
try {
|
|
301
|
+
const fresh = await renderPromise;
|
|
302
|
+
return wrapWithBoundary(fresh);
|
|
303
|
+
} finally {
|
|
304
|
+
inflightSlotRenders.delete(storageKey);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Resolves a slot's JSX tree for the granular /____rsc_slot____/:id endpoint.
|
|
310
|
+
*/
|
|
311
|
+
async function getSlotJSX(slotId, options = {}) {
|
|
312
|
+
const storage = getStorageAdapter();
|
|
313
|
+
const storageKey = `slots/${slotId}/slot.json`;
|
|
314
|
+
|
|
315
|
+
// 1. Check storage cache
|
|
316
|
+
let cached = null;
|
|
317
|
+
if (storage) {
|
|
318
|
+
try {
|
|
319
|
+
cached = await storage.get(storageKey);
|
|
320
|
+
} catch (e) {}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
const now = Date.now();
|
|
324
|
+
if (cached && cached.content) {
|
|
325
|
+
const meta = cached.metadata || {};
|
|
326
|
+
const generatedAt = meta.generatedAt || 0;
|
|
327
|
+
const revalidateSeconds =
|
|
328
|
+
typeof meta.revalidate === "number" ? meta.revalidate : undefined;
|
|
329
|
+
|
|
330
|
+
const isExpired =
|
|
331
|
+
typeof revalidateSeconds === "number" &&
|
|
332
|
+
revalidateSeconds > 0 &&
|
|
333
|
+
now > generatedAt + revalidateSeconds * 1000;
|
|
334
|
+
|
|
335
|
+
if (!isExpired && !options.fresh) {
|
|
336
|
+
try {
|
|
337
|
+
return deserializeJSX(cached.content);
|
|
338
|
+
} catch (e) {}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// 2. If missing from storage (e.g. was invalidated) or expired or forced fresh:
|
|
343
|
+
if (!slotRegistry.has(slotId) && options.currentPath) {
|
|
344
|
+
try {
|
|
345
|
+
const { getJSX } = require("./get-jsx.js");
|
|
346
|
+
const isNotFound = { value: false };
|
|
347
|
+
await getJSX(options.currentPath, {}, isNotFound, false, false, { segment: "page" });
|
|
348
|
+
} catch (e) {
|
|
349
|
+
console.warn(`[getSlotJSX] Could not pre-evaluate route "${options.currentPath}" to register slot "${slotId}":`, e);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
if (slotRegistry.has(slotId)) {
|
|
354
|
+
const entry = slotRegistry.get(slotId);
|
|
355
|
+
try {
|
|
356
|
+
const resolved = await entry.render();
|
|
357
|
+
const serialized = serializeJSX(resolved);
|
|
358
|
+
if (storage) {
|
|
359
|
+
await storage.set(storageKey, serialized, {
|
|
360
|
+
tags: entry.tags,
|
|
361
|
+
revalidate: entry.revalidate,
|
|
362
|
+
generatedAt: Date.now(),
|
|
363
|
+
slotId,
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
return deserializeJSX(serialized);
|
|
367
|
+
} catch (e) {
|
|
368
|
+
console.error(`[getSlotJSX] Failed to regenerate slot "${slotId}":`, e);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// Fallback to cached content if regeneration failed
|
|
373
|
+
if (cached && cached.content) {
|
|
374
|
+
try {
|
|
375
|
+
return deserializeJSX(cached.content);
|
|
376
|
+
} catch (e) {}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
return null;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
module.exports = {
|
|
383
|
+
DinouCacheSlot,
|
|
384
|
+
getSlotJSX,
|
|
385
|
+
serializeJSX,
|
|
386
|
+
deserializeJSX,
|
|
387
|
+
};
|
|
@@ -1,22 +1,27 @@
|
|
|
1
1
|
// dinou/core/client-redirect.jsx
|
|
2
2
|
"use client";
|
|
3
3
|
|
|
4
|
+
import { useEffect } from "react";
|
|
4
5
|
import { useRouter } from "./navigation.js";
|
|
5
6
|
|
|
6
7
|
export function ClientRedirect({ to }) {
|
|
7
8
|
const router = useRouter();
|
|
8
9
|
|
|
9
|
-
|
|
10
|
-
|
|
10
|
+
useEffect(() => {
|
|
11
|
+
if (typeof window !== "undefined") {
|
|
12
|
+
document.body.setAttribute("data-hydrated", "true");
|
|
11
13
|
if (window.__DINOU_ROUTER_NAVIGATE__) {
|
|
12
14
|
window.__DINOU_ROUTER_NAVIGATE__(to, { replace: true });
|
|
13
|
-
} else {
|
|
15
|
+
} else if (router && router.replace) {
|
|
14
16
|
router.replace(to);
|
|
17
|
+
} else {
|
|
18
|
+
window.location.href = to;
|
|
15
19
|
}
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
throw new Promise(() => {});
|
|
19
|
-
}
|
|
20
|
+
}
|
|
21
|
+
}, [to, router]);
|
|
20
22
|
|
|
21
23
|
return null;
|
|
22
24
|
}
|
|
25
|
+
|
|
26
|
+
export default ClientRedirect;
|
|
27
|
+
|