dinou 4.0.14 → 4.0.16
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 +15 -0
- package/dinou/core/get-error-jsx.js +1 -1
- package/dinou/core/get-file-path-and-dynamic-params.js +1 -1
- package/dinou/core/get-jsx.js +1 -1
- package/dinou/core/render-app-to-html.js +11 -4
- package/dinou/core/server.js +11 -4
- package/dinou/core/url-resolver.js +3 -0
- package/dinou/core/vfs.js +68 -0
- package/dinou/esbuild/plugins-esbuild/server-functions-plugin.mjs +1 -1
- package/dinou/index.d.ts +1 -1
- package/dinou/package.json +1 -1
- package/dinou/rollup/rollup-plugins/rollup-plugin-server-functions.js +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,21 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/).
|
|
7
7
|
|
|
8
|
+
## [4.0.16] - 2026-06-30
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
- **Cookie Deletion Attributes**: Reconstructed streaming cookie deletion to serialize and include `domain`, `secure`, and `sameSite` properties, ensuring browsers match scopes and delete cookies correctly.
|
|
12
|
+
- **Defensive Redirects**: Added a safety check in `resolveRelativeUrl` to fallback to `/` if the destination path is invalid (such as undefined or null), avoiding server crashes.
|
|
13
|
+
- **TypeScript Type Definitions**: Updated the `clearCookie` signature in `index.d.ts` to accept the full `CookieOptions` interface, resolving compilation errors when using `sameSite` or `secure` attributes.
|
|
14
|
+
|
|
15
|
+
## [4.0.15] - 2026-06-29
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
- **VFS Routing Cache**: Introduced an in-memory Virtual File System (VFS) to cache filesystem structures under `src/` at production startup, resolving dynamic routes entirely in-memory and eliminating synchronous event loop-blocking disk I/O.
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
- **Cross-Platform Separators**: Normalized Server Functions manifest keys and runtime lookup checks to consistently use forward slashes (`/`), preventing OS path format mismatches (`\` vs `/`) and subsequent `400 Bad Request` execution errors.
|
|
22
|
+
|
|
8
23
|
## [4.0.14] - 2026-06-29
|
|
9
24
|
### Added
|
|
10
25
|
- **Soft Redirects**: Exposes the SPA transition navigation internally via `window.__DINOU_ROUTER_NAVIGATE__` to enable smooth client-side redirects from both Server Functions and RSC payloads, keeping the React client state intact and preventing full browser page reloads.
|
package/dinou/core/get-jsx.js
CHANGED
|
@@ -121,14 +121,21 @@ function renderAppToHtml(
|
|
|
121
121
|
// --- CLEAR COOKIE (JS Injection - Only Non-HttpOnly) ---
|
|
122
122
|
if (command === "clearCookie") {
|
|
123
123
|
const [name, options] = args;
|
|
124
|
-
const path = options && options.path ? options.path : "/";
|
|
125
124
|
console.log(
|
|
126
125
|
`[Dinou] Streaming active. Clearing cookie '${name}' via JS.`,
|
|
127
126
|
);
|
|
128
|
-
|
|
129
|
-
const
|
|
127
|
+
let cookieStr = `${name}=; Max-Age=0`;
|
|
128
|
+
const path = options?.path || "/";
|
|
129
|
+
cookieStr += `; path=${path}`;
|
|
130
|
+
if (options) {
|
|
131
|
+
if (options.domain) cookieStr += `; domain=${options.domain}`;
|
|
132
|
+
if (options.secure) cookieStr += `; secure`;
|
|
133
|
+
if (options.sameSite) cookieStr += `; samesite=${options.sameSite}`;
|
|
134
|
+
}
|
|
135
|
+
cookieStr += ";";
|
|
136
|
+
const safeCookieStr = JSON.stringify(cookieStr);
|
|
130
137
|
res.write(
|
|
131
|
-
`<script>document.cookie = ${
|
|
138
|
+
`<script>document.cookie = ${safeCookieStr};</script>`,
|
|
132
139
|
);
|
|
133
140
|
return;
|
|
134
141
|
}
|
package/dinou/core/server.js
CHANGED
|
@@ -447,8 +447,6 @@ function getContextForServerFunctionEndpoint(req, res) {
|
|
|
447
447
|
},
|
|
448
448
|
|
|
449
449
|
clearCookie: (name, options) => {
|
|
450
|
-
const path = options?.path || "/";
|
|
451
|
-
|
|
452
450
|
// SCENARIO A: Native
|
|
453
451
|
if (!res.headersSent) {
|
|
454
452
|
res.setHeader("Content-Type", "text/x-component");
|
|
@@ -457,7 +455,15 @@ function getContextForServerFunctionEndpoint(req, res) {
|
|
|
457
455
|
}
|
|
458
456
|
|
|
459
457
|
// SCENARIO B: Custom stream command
|
|
460
|
-
|
|
458
|
+
let cookieStr = `${name}=; Max-Age=0`;
|
|
459
|
+
const path = options?.path || "/";
|
|
460
|
+
cookieStr += `; path=${path}`;
|
|
461
|
+
if (options) {
|
|
462
|
+
if (options.domain) cookieStr += `; domain=${options.domain}`;
|
|
463
|
+
if (options.secure) cookieStr += `; secure`;
|
|
464
|
+
if (options.sameSite) cookieStr += `; samesite=${options.sameSite}`;
|
|
465
|
+
}
|
|
466
|
+
cookieStr += ";";
|
|
461
467
|
const safeCookieStr = JSON.stringify(cookieStr);
|
|
462
468
|
res.write(`D:{"type":"cookie","cookie":${safeCookieStr}}\n`);
|
|
463
469
|
},
|
|
@@ -918,10 +924,11 @@ app.post("/____server_function____", async (req, res) => {
|
|
|
918
924
|
return res.status(404).json({ error: "File not found" });
|
|
919
925
|
}
|
|
920
926
|
|
|
927
|
+
const normalizedKey = relativePath.replace(/\\/g, "/");
|
|
921
928
|
let allowedExports;
|
|
922
929
|
if (serverFunctionsManifest) {
|
|
923
930
|
// Prod: use manifest (relativePath is already normalized)
|
|
924
|
-
allowedExports = serverFunctionsManifest[
|
|
931
|
+
allowedExports = serverFunctionsManifest[normalizedKey];
|
|
925
932
|
} else {
|
|
926
933
|
if (!isDevelopment) {
|
|
927
934
|
return res
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
|
|
4
|
+
const isDevelopment = process.env.NODE_ENV !== "production";
|
|
5
|
+
const vfs = {};
|
|
6
|
+
|
|
7
|
+
function buildVfs(dir) {
|
|
8
|
+
if (!fs.existsSync(dir)) return;
|
|
9
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
10
|
+
const children = [];
|
|
11
|
+
|
|
12
|
+
for (const entry of entries) {
|
|
13
|
+
const fullPath = path.join(dir, entry.name);
|
|
14
|
+
const isDirectory = entry.isDirectory();
|
|
15
|
+
children.push({
|
|
16
|
+
name: entry.name,
|
|
17
|
+
isDirectory
|
|
18
|
+
});
|
|
19
|
+
if (isDirectory) {
|
|
20
|
+
buildVfs(fullPath);
|
|
21
|
+
} else {
|
|
22
|
+
vfs[fullPath] = { type: "file" };
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
vfs[dir] = {
|
|
27
|
+
type: "directory",
|
|
28
|
+
children
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (!isDevelopment) {
|
|
33
|
+
const srcDir = path.resolve(process.cwd(), "src");
|
|
34
|
+
buildVfs(srcDir);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function existsSync(filePath) {
|
|
38
|
+
if (isDevelopment) {
|
|
39
|
+
return fs.existsSync(filePath);
|
|
40
|
+
}
|
|
41
|
+
const normalized = path.resolve(filePath);
|
|
42
|
+
return !!vfs[normalized];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function readdirSync(dirPath, options) {
|
|
46
|
+
if (isDevelopment) {
|
|
47
|
+
return fs.readdirSync(dirPath, options);
|
|
48
|
+
}
|
|
49
|
+
const normalized = path.resolve(dirPath);
|
|
50
|
+
const entry = vfs[normalized];
|
|
51
|
+
if (!entry || entry.type !== "directory") {
|
|
52
|
+
throw new Error(`ENOTDIR: not a directory, readdir '${dirPath}'`);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (options && options.withFileTypes) {
|
|
56
|
+
return entry.children.map(child => ({
|
|
57
|
+
name: child.name,
|
|
58
|
+
isDirectory: () => child.isDirectory,
|
|
59
|
+
isFile: () => !child.isDirectory
|
|
60
|
+
}));
|
|
61
|
+
}
|
|
62
|
+
return entry.children.map(child => child.name);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
module.exports = {
|
|
66
|
+
existsSync,
|
|
67
|
+
readdirSync
|
|
68
|
+
};
|
|
@@ -19,7 +19,7 @@ export default function serverFunctionsPlugin(manifestData = {}) {
|
|
|
19
19
|
const exports = parseExports(code);
|
|
20
20
|
if (exports.length === 0) return null;
|
|
21
21
|
|
|
22
|
-
const relativePath = path.relative(root, args.path);
|
|
22
|
+
const relativePath = path.relative(root, args.path).replace(/\\/g, "/");
|
|
23
23
|
serverFunctions.set(relativePath, new Set(exports)); // Save exports as a Set to guarantee uniqueness
|
|
24
24
|
|
|
25
25
|
const fileUrl = `file:///${relativePath}`;
|
package/dinou/index.d.ts
CHANGED
|
@@ -55,7 +55,7 @@ export interface ResponseProxy {
|
|
|
55
55
|
* @param name Name of the cookie to clear.
|
|
56
56
|
* @param options Cookie options (e.g., domain, path).
|
|
57
57
|
*/
|
|
58
|
-
clearCookie(name: string, options?:
|
|
58
|
+
clearCookie(name: string, options?: CookieOptions): void;
|
|
59
59
|
|
|
60
60
|
/**
|
|
61
61
|
* Sends a command to the main process to set an HTTP header.
|
package/dinou/package.json
CHANGED
|
@@ -17,7 +17,7 @@ function serverFunctionsPlugin() {
|
|
|
17
17
|
const exports = parseExports(code);
|
|
18
18
|
if (exports.length === 0) return null;
|
|
19
19
|
|
|
20
|
-
const relativePath = path.relative(root, id);
|
|
20
|
+
const relativePath = path.relative(root, id).replace(/\\/g, "/");
|
|
21
21
|
serverFunctions.set(relativePath, new Set(exports)); // Save exports as a Set for uniqueness
|
|
22
22
|
|
|
23
23
|
const fileUrl = `file:///${relativePath}`;
|