@farm.js/plugin 0.1.0-beta.7 → 0.1.0-beta.71
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/dist/dev-static.d.ts +8 -0
- package/dist/dev-static.d.ts.map +1 -0
- package/dist/dev-static.js +37 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/middleware/index.d.ts.map +1 -1
- package/dist/middleware/index.js +9 -53
- package/dist/middleware/matching.d.ts +29 -0
- package/dist/middleware/matching.d.ts.map +1 -0
- package/dist/middleware/matching.js +79 -0
- package/dist/rsc/automatic-optimized-boundary.d.ts +11 -0
- package/dist/rsc/automatic-optimized-boundary.d.ts.map +1 -0
- package/dist/rsc/automatic-optimized-boundary.js +162 -0
- package/dist/rsc/entries/client.d.ts +1 -0
- package/dist/rsc/entries/client.d.ts.map +1 -1
- package/dist/rsc/entries/client.js +33 -3
- package/dist/rsc/entries/rsc.d.ts.map +1 -1
- package/dist/rsc/entries/rsc.js +17 -9
- package/dist/rsc/index.d.ts +3 -2
- package/dist/rsc/index.d.ts.map +1 -1
- package/dist/rsc/index.js +49 -6
- package/dist/rsc/nitro-build.d.ts.map +1 -1
- package/dist/rsc/nitro-build.js +9 -1
- package/dist/rsc/optimized-boundary.d.ts +9 -1
- package/dist/rsc/optimized-boundary.d.ts.map +1 -1
- package/dist/rsc/optimized-boundary.js +229 -1
- package/dist/rsc/route-action-transform.d.ts +11 -0
- package/dist/rsc/route-action-transform.d.ts.map +1 -0
- package/dist/rsc/route-action-transform.js +369 -0
- package/package.json +4 -4
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Route segments may legitimately contain dots (e.g. /kinfish/farm.js), so a
|
|
3
|
+
* dot alone cannot classify a dev request as a static asset. A dotted path is
|
|
4
|
+
* only treated as an asset when it maps to a real file under the project root
|
|
5
|
+
* or public dir, matching the filesystem-first behavior of production hosting.
|
|
6
|
+
*/
|
|
7
|
+
export declare function devServableFileExists(pathname: string, baseDirs: Array<string | false | undefined>): boolean;
|
|
8
|
+
//# sourceMappingURL=dev-static.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dev-static.d.ts","sourceRoot":"","sources":["../src/dev-static.ts"],"names":[],"mappings":"AAGA;;;;;GAKG;AACH,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,KAAK,CAAC,MAAM,GAAG,KAAK,GAAG,SAAS,CAAC,GAC1C,OAAO,CAuBT"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
/**
|
|
4
|
+
* Route segments may legitimately contain dots (e.g. /kinfish/farm.js), so a
|
|
5
|
+
* dot alone cannot classify a dev request as a static asset. A dotted path is
|
|
6
|
+
* only treated as an asset when it maps to a real file under the project root
|
|
7
|
+
* or public dir, matching the filesystem-first behavior of production hosting.
|
|
8
|
+
*/
|
|
9
|
+
export function devServableFileExists(pathname, baseDirs) {
|
|
10
|
+
let decodedPathname;
|
|
11
|
+
try {
|
|
12
|
+
decodedPathname = decodeURIComponent(pathname);
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
const relativePathname = decodedPathname.replace(/^\/+/, "");
|
|
18
|
+
if (!relativePathname)
|
|
19
|
+
return false;
|
|
20
|
+
for (const baseDir of baseDirs) {
|
|
21
|
+
if (typeof baseDir !== "string" || baseDir.length === 0)
|
|
22
|
+
continue;
|
|
23
|
+
const resolvedBase = path.resolve(baseDir);
|
|
24
|
+
const candidate = path.resolve(resolvedBase, relativePathname);
|
|
25
|
+
if (!candidate.startsWith(resolvedBase + path.sep))
|
|
26
|
+
continue;
|
|
27
|
+
try {
|
|
28
|
+
if (fs.existsSync(candidate) && fs.statSync(candidate).isFile()) {
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
// Ignore filesystem errors and keep checking other base dirs.
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return false;
|
|
37
|
+
}
|
package/dist/index.d.ts
CHANGED
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AAEH,eAAO,MAAM,OAAO,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AAEH,eAAO,MAAM,OAAO,kBAAkB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/middleware/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,MAAM,CAAC;AAClD,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/middleware/index.ts"],"names":[],"mappings":"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"}
|
package/dist/middleware/index.js
CHANGED
|
@@ -15,6 +15,8 @@
|
|
|
15
15
|
*/
|
|
16
16
|
import { createRequire } from "node:module";
|
|
17
17
|
import { _withAfterNodeMiddleware } from "@farm.js/core/after";
|
|
18
|
+
import { devServableFileExists } from "../dev-static.js";
|
|
19
|
+
import { middlewareMatchesPath, parseCookies, serializeCookie } from "./matching.js";
|
|
18
20
|
// Create require for ESM compatibility
|
|
19
21
|
const require_ = createRequire(import.meta.url);
|
|
20
22
|
// Get picocolors with forced color support (to handle NO_COLOR env being set)
|
|
@@ -42,52 +44,6 @@ const getColors = () => {
|
|
|
42
44
|
magenta: id,
|
|
43
45
|
};
|
|
44
46
|
};
|
|
45
|
-
/**
|
|
46
|
-
* Parse cookies from Cookie header
|
|
47
|
-
*/
|
|
48
|
-
function parseCookies(cookieHeader) {
|
|
49
|
-
if (!cookieHeader)
|
|
50
|
-
return {};
|
|
51
|
-
return cookieHeader.split(";").reduce((cookies, cookie) => {
|
|
52
|
-
const [name, ...rest] = cookie.split("=");
|
|
53
|
-
const value = rest.join("=").trim();
|
|
54
|
-
if (name && value) {
|
|
55
|
-
cookies[name.trim()] = decodeURIComponent(value);
|
|
56
|
-
}
|
|
57
|
-
return cookies;
|
|
58
|
-
}, {});
|
|
59
|
-
}
|
|
60
|
-
/**
|
|
61
|
-
* Serialize a cookie
|
|
62
|
-
*/
|
|
63
|
-
function serializeCookie(name, value, options = {}) {
|
|
64
|
-
let cookie = `${encodeURIComponent(name)}=${encodeURIComponent(value)}`;
|
|
65
|
-
if (options.maxAge) {
|
|
66
|
-
cookie += `; Max-Age=${options.maxAge}`;
|
|
67
|
-
}
|
|
68
|
-
if (options.expires) {
|
|
69
|
-
cookie += `; Expires=${options.expires.toUTCString()}`;
|
|
70
|
-
}
|
|
71
|
-
if (options.path) {
|
|
72
|
-
cookie += `; Path=${options.path}`;
|
|
73
|
-
}
|
|
74
|
-
else {
|
|
75
|
-
cookie += "; Path=/";
|
|
76
|
-
}
|
|
77
|
-
if (options.domain) {
|
|
78
|
-
cookie += `; Domain=${options.domain}`;
|
|
79
|
-
}
|
|
80
|
-
if (options.secure) {
|
|
81
|
-
cookie += "; Secure";
|
|
82
|
-
}
|
|
83
|
-
if (options.httpOnly) {
|
|
84
|
-
cookie += "; HttpOnly";
|
|
85
|
-
}
|
|
86
|
-
if (options.sameSite) {
|
|
87
|
-
cookie += `; SameSite=${options.sameSite.charAt(0).toUpperCase() + options.sameSite.slice(1)}`;
|
|
88
|
-
}
|
|
89
|
-
return cookie;
|
|
90
|
-
}
|
|
91
47
|
/**
|
|
92
48
|
* Create a cookie jar for managing cookies
|
|
93
49
|
*/
|
|
@@ -293,11 +249,7 @@ export default function farmMiddleware(options = {}) {
|
|
|
293
249
|
}
|
|
294
250
|
// Find applicable middleware (cascading from root to specific)
|
|
295
251
|
const applicable = Array.from(middlewareCache.values())
|
|
296
|
-
.filter((mw) =>
|
|
297
|
-
if (mw.path === "/")
|
|
298
|
-
return true;
|
|
299
|
-
return pathname.startsWith(mw.path) || pathname === mw.path;
|
|
300
|
-
})
|
|
252
|
+
.filter((mw) => middlewareMatchesPath(pathname, mw.path))
|
|
301
253
|
.sort((a, b) => a.path.split("/").length - b.path.split("/").length);
|
|
302
254
|
if (applicable.length === 0)
|
|
303
255
|
return false;
|
|
@@ -384,11 +336,15 @@ export default function farmMiddleware(options = {}) {
|
|
|
384
336
|
server.middlewares.use(_withAfterNodeMiddleware(async (req, res, next) => {
|
|
385
337
|
const url = req.url || "/";
|
|
386
338
|
const pathname = url.split("?")[0];
|
|
387
|
-
// Skip Vite internal requests
|
|
339
|
+
// Skip Vite internal requests and real static assets. Dotted
|
|
340
|
+
// paths that don't map to a file on disk are app routes (route
|
|
341
|
+
// segments may contain dots) and must still run middleware.
|
|
388
342
|
if (pathname.startsWith("/@") ||
|
|
389
343
|
pathname.startsWith("/__") ||
|
|
390
344
|
pathname.startsWith("/node_modules") ||
|
|
391
|
-
(pathname.includes(".") &&
|
|
345
|
+
(pathname.includes(".") &&
|
|
346
|
+
!pathname.endsWith("/") &&
|
|
347
|
+
devServableFileExists(pathname, [server.config.publicDir, server.config.root]))) {
|
|
392
348
|
return next();
|
|
393
349
|
}
|
|
394
350
|
try {
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { CookieOptions } from "./index.js";
|
|
2
|
+
/**
|
|
3
|
+
* Parse cookies from a Cookie header.
|
|
4
|
+
*
|
|
5
|
+
* A single request cookie whose value is not valid UTF-8 percent-encoding
|
|
6
|
+
* (a latin-1 value from an old link, a crawler, or a bare `%`) must not take
|
|
7
|
+
* the whole map down: an unguarded `decodeURIComponent` throws `URIError`,
|
|
8
|
+
* which — because the middleware runner swallows the throw and calls `next()` —
|
|
9
|
+
* would silently skip every middleware for that request (a dev-time auth
|
|
10
|
+
* bypass). Decoding falls back to the raw value per entry instead.
|
|
11
|
+
*/
|
|
12
|
+
export declare function parseCookies(cookieHeader?: string): Record<string, string>;
|
|
13
|
+
/**
|
|
14
|
+
* Whether a middleware registered at `middlewarePath` applies to `pathname`.
|
|
15
|
+
*
|
|
16
|
+
* Matching is by path segment, not raw string prefix: middleware at `/admin`
|
|
17
|
+
* covers `/admin` and everything under `/admin/`, but not sibling routes like
|
|
18
|
+
* `/administrator` or `/admin-public` that merely share a textual prefix.
|
|
19
|
+
*/
|
|
20
|
+
export declare function middlewareMatchesPath(pathname: string, middlewarePath: string): boolean;
|
|
21
|
+
/**
|
|
22
|
+
* Serialize a cookie.
|
|
23
|
+
*
|
|
24
|
+
* `Max-Age=0` is the canonical immediate-expiry directive, so the attribute is
|
|
25
|
+
* emitted whenever `maxAge` is provided — a `0` must not be dropped the way a
|
|
26
|
+
* plain truthiness check would drop it.
|
|
27
|
+
*/
|
|
28
|
+
export declare function serializeCookie(name: string, value: string, options?: CookieOptions): string;
|
|
29
|
+
//# sourceMappingURL=matching.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"matching.d.ts","sourceRoot":"","sources":["../../src/middleware/matching.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEhD;;;;;;;;;GASG;AACH,wBAAgB,YAAY,CAAC,YAAY,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAc1E;AAUD;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,OAAO,CAIvF;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,GAAE,aAAkB,GAAG,MAAM,CAkChG"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parse cookies from a Cookie header.
|
|
3
|
+
*
|
|
4
|
+
* A single request cookie whose value is not valid UTF-8 percent-encoding
|
|
5
|
+
* (a latin-1 value from an old link, a crawler, or a bare `%`) must not take
|
|
6
|
+
* the whole map down: an unguarded `decodeURIComponent` throws `URIError`,
|
|
7
|
+
* which — because the middleware runner swallows the throw and calls `next()` —
|
|
8
|
+
* would silently skip every middleware for that request (a dev-time auth
|
|
9
|
+
* bypass). Decoding falls back to the raw value per entry instead.
|
|
10
|
+
*/
|
|
11
|
+
export function parseCookies(cookieHeader) {
|
|
12
|
+
if (!cookieHeader)
|
|
13
|
+
return {};
|
|
14
|
+
return cookieHeader.split(";").reduce((cookies, cookie) => {
|
|
15
|
+
const [name, ...rest] = cookie.split("=");
|
|
16
|
+
const value = rest.join("=").trim();
|
|
17
|
+
if (name && value) {
|
|
18
|
+
cookies[name.trim()] = decodeCookieValue(value);
|
|
19
|
+
}
|
|
20
|
+
return cookies;
|
|
21
|
+
}, {});
|
|
22
|
+
}
|
|
23
|
+
function decodeCookieValue(value) {
|
|
24
|
+
try {
|
|
25
|
+
return decodeURIComponent(value);
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return value;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Whether a middleware registered at `middlewarePath` applies to `pathname`.
|
|
33
|
+
*
|
|
34
|
+
* Matching is by path segment, not raw string prefix: middleware at `/admin`
|
|
35
|
+
* covers `/admin` and everything under `/admin/`, but not sibling routes like
|
|
36
|
+
* `/administrator` or `/admin-public` that merely share a textual prefix.
|
|
37
|
+
*/
|
|
38
|
+
export function middlewareMatchesPath(pathname, middlewarePath) {
|
|
39
|
+
if (middlewarePath === "/")
|
|
40
|
+
return true;
|
|
41
|
+
if (pathname === middlewarePath)
|
|
42
|
+
return true;
|
|
43
|
+
return pathname.startsWith(`${middlewarePath}/`);
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Serialize a cookie.
|
|
47
|
+
*
|
|
48
|
+
* `Max-Age=0` is the canonical immediate-expiry directive, so the attribute is
|
|
49
|
+
* emitted whenever `maxAge` is provided — a `0` must not be dropped the way a
|
|
50
|
+
* plain truthiness check would drop it.
|
|
51
|
+
*/
|
|
52
|
+
export function serializeCookie(name, value, options = {}) {
|
|
53
|
+
let cookie = `${encodeURIComponent(name)}=${encodeURIComponent(value)}`;
|
|
54
|
+
if (options.maxAge != null) {
|
|
55
|
+
cookie += `; Max-Age=${options.maxAge}`;
|
|
56
|
+
}
|
|
57
|
+
if (options.expires) {
|
|
58
|
+
cookie += `; Expires=${options.expires.toUTCString()}`;
|
|
59
|
+
}
|
|
60
|
+
if (options.path) {
|
|
61
|
+
cookie += `; Path=${options.path}`;
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
cookie += "; Path=/";
|
|
65
|
+
}
|
|
66
|
+
if (options.domain) {
|
|
67
|
+
cookie += `; Domain=${options.domain}`;
|
|
68
|
+
}
|
|
69
|
+
if (options.secure) {
|
|
70
|
+
cookie += "; Secure";
|
|
71
|
+
}
|
|
72
|
+
if (options.httpOnly) {
|
|
73
|
+
cookie += "; HttpOnly";
|
|
74
|
+
}
|
|
75
|
+
if (options.sameSite) {
|
|
76
|
+
cookie += `; SameSite=${options.sameSite.charAt(0).toUpperCase() + options.sameSite.slice(1)}`;
|
|
77
|
+
}
|
|
78
|
+
return cookie;
|
|
79
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export interface AutomaticOptimizedBoundaryTransform {
|
|
2
|
+
code: string;
|
|
3
|
+
boundaryCount: number;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Wrap React JSX-runtime calls for native host boundaries with Farm's
|
|
7
|
+
* server-only optimizer. Eligibility is decided against the fully evaluated
|
|
8
|
+
* React element at runtime, so unsupported trees retain normal React behavior.
|
|
9
|
+
*/
|
|
10
|
+
export declare function transformAutomaticOptimizedBoundaries(code: string, id: string): AutomaticOptimizedBoundaryTransform | null;
|
|
11
|
+
//# sourceMappingURL=automatic-optimized-boundary.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"automatic-optimized-boundary.d.ts","sourceRoot":"","sources":["../../src/rsc/automatic-optimized-boundary.ts"],"names":[],"mappings":"AAwBA,MAAM,WAAW,mCAAmC;IAClD,IAAI,EAAE,MAAM,CAAC;IACb,aAAa,EAAE,MAAM,CAAC;CACvB;AAiGD;;;;GAIG;AACH,wBAAgB,qCAAqC,CACnD,IAAI,EAAE,MAAM,EACZ,EAAE,EAAE,MAAM,GACT,mCAAmC,GAAG,IAAI,CA8C5C"}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { parseAst } from "vite";
|
|
2
|
+
const JSX_RUNTIME_MODULES = new Set(["react/jsx-runtime", "react/jsx-dev-runtime"]);
|
|
3
|
+
const JSX_FACTORY_EXPORTS = new Set(["jsx", "jsxs", "jsxDEV"]);
|
|
4
|
+
const AUTOMATIC_BOUNDARY_TAGS = new Set([
|
|
5
|
+
"article",
|
|
6
|
+
"aside",
|
|
7
|
+
"div",
|
|
8
|
+
"footer",
|
|
9
|
+
"header",
|
|
10
|
+
"main",
|
|
11
|
+
"nav",
|
|
12
|
+
"section",
|
|
13
|
+
"span",
|
|
14
|
+
]);
|
|
15
|
+
const AUTOMATIC_BOUNDARY_IMPORT = "@farm.js/plugin/rsc/optimized-boundary";
|
|
16
|
+
function nodeName(node) {
|
|
17
|
+
if (!node || typeof node !== "object")
|
|
18
|
+
return undefined;
|
|
19
|
+
const candidate = node;
|
|
20
|
+
if (typeof candidate.name === "string")
|
|
21
|
+
return candidate.name;
|
|
22
|
+
if (typeof candidate.value === "string")
|
|
23
|
+
return candidate.value;
|
|
24
|
+
return undefined;
|
|
25
|
+
}
|
|
26
|
+
function isClientModule(body) {
|
|
27
|
+
for (const statement of body) {
|
|
28
|
+
if (statement.type === "ImportDeclaration")
|
|
29
|
+
return false;
|
|
30
|
+
if (statement.type !== "ExpressionStatement")
|
|
31
|
+
return false;
|
|
32
|
+
const expression = statement.expression;
|
|
33
|
+
if (expression?.type !== "Literal")
|
|
34
|
+
return false;
|
|
35
|
+
if (expression.value === "use client")
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
function collectJsxFactories(body) {
|
|
41
|
+
const factories = new Set();
|
|
42
|
+
for (const statement of body) {
|
|
43
|
+
if (statement.type !== "ImportDeclaration")
|
|
44
|
+
continue;
|
|
45
|
+
const source = nodeName(statement.source);
|
|
46
|
+
if (!source || !JSX_RUNTIME_MODULES.has(source))
|
|
47
|
+
continue;
|
|
48
|
+
for (const specifier of statement.specifiers || []) {
|
|
49
|
+
if (specifier.type !== "ImportSpecifier")
|
|
50
|
+
continue;
|
|
51
|
+
const imported = nodeName(specifier.imported);
|
|
52
|
+
const local = nodeName(specifier.local);
|
|
53
|
+
if (imported && local && JSX_FACTORY_EXPORTS.has(imported))
|
|
54
|
+
factories.add(local);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return factories;
|
|
58
|
+
}
|
|
59
|
+
function collectBoundaryCalls(node, factories, calls) {
|
|
60
|
+
if (!node || typeof node !== "object")
|
|
61
|
+
return;
|
|
62
|
+
if (Array.isArray(node)) {
|
|
63
|
+
for (const child of node)
|
|
64
|
+
collectBoundaryCalls(child, factories, calls);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
const candidate = node;
|
|
68
|
+
if (candidate.type === "CallExpression") {
|
|
69
|
+
const callee = candidate.callee;
|
|
70
|
+
const args = candidate.arguments || [];
|
|
71
|
+
const tag = args[0];
|
|
72
|
+
if (callee?.type === "Identifier" &&
|
|
73
|
+
factories.has(nodeName(callee) || "") &&
|
|
74
|
+
tag?.type === "Literal" &&
|
|
75
|
+
AUTOMATIC_BOUNDARY_TAGS.has(String(tag.value)) &&
|
|
76
|
+
typeof candidate.start === "number" &&
|
|
77
|
+
typeof candidate.end === "number") {
|
|
78
|
+
calls.push(candidate);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
for (const [key, value] of Object.entries(candidate)) {
|
|
82
|
+
if (key === "start" || key === "end" || key === "loc")
|
|
83
|
+
continue;
|
|
84
|
+
collectBoundaryCalls(value, factories, calls);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
function createHelperName(code) {
|
|
88
|
+
const base = "__farmOptimizeBoundary";
|
|
89
|
+
let name = base;
|
|
90
|
+
let suffix = 1;
|
|
91
|
+
while (new RegExp(`\\b${name}\\b`).test(code))
|
|
92
|
+
name = `${base}${suffix++}`;
|
|
93
|
+
return name;
|
|
94
|
+
}
|
|
95
|
+
function importInsertionPoint(body) {
|
|
96
|
+
let position = 0;
|
|
97
|
+
for (const statement of body) {
|
|
98
|
+
if (statement.type === "ImportDeclaration" && typeof statement.end === "number") {
|
|
99
|
+
position = statement.end;
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
if (statement.type === "ExpressionStatement") {
|
|
103
|
+
const expression = statement.expression;
|
|
104
|
+
if (expression?.type === "Literal" && typeof statement.end === "number") {
|
|
105
|
+
position = statement.end;
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
break;
|
|
110
|
+
}
|
|
111
|
+
return position;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Wrap React JSX-runtime calls for native host boundaries with Farm's
|
|
115
|
+
* server-only optimizer. Eligibility is decided against the fully evaluated
|
|
116
|
+
* React element at runtime, so unsupported trees retain normal React behavior.
|
|
117
|
+
*/
|
|
118
|
+
export function transformAutomaticOptimizedBoundaries(code, id) {
|
|
119
|
+
const cleanId = id.split("?", 1)[0].replace(/\\/g, "/");
|
|
120
|
+
if (!/\.[cm]?[jt]sx?$/.test(cleanId))
|
|
121
|
+
return null;
|
|
122
|
+
if (cleanId.includes("/node_modules/") || cleanId.includes("/.farm/"))
|
|
123
|
+
return null;
|
|
124
|
+
if (!code.includes("react/jsx-runtime") && !code.includes("react/jsx-dev-runtime"))
|
|
125
|
+
return null;
|
|
126
|
+
let ast;
|
|
127
|
+
try {
|
|
128
|
+
ast = parseAst(code);
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
const body = ast.body || [];
|
|
134
|
+
if (isClientModule(body))
|
|
135
|
+
return null;
|
|
136
|
+
const factories = collectJsxFactories(body);
|
|
137
|
+
if (factories.size === 0)
|
|
138
|
+
return null;
|
|
139
|
+
const calls = [];
|
|
140
|
+
collectBoundaryCalls(ast, factories, calls);
|
|
141
|
+
if (calls.length === 0)
|
|
142
|
+
return null;
|
|
143
|
+
const helperName = createHelperName(code);
|
|
144
|
+
const insertions = [];
|
|
145
|
+
for (const call of calls) {
|
|
146
|
+
insertions.push({ position: call.start, text: `${helperName}(`, order: 1 });
|
|
147
|
+
insertions.push({ position: call.end, text: ")", order: 0 });
|
|
148
|
+
}
|
|
149
|
+
insertions.push({
|
|
150
|
+
position: importInsertionPoint(body),
|
|
151
|
+
text: `${importInsertionPoint(body) === 0 ? "" : "\n"}import { _optimizeBoundary as ${helperName} } from ${JSON.stringify(AUTOMATIC_BOUNDARY_IMPORT)};\n`,
|
|
152
|
+
order: 2,
|
|
153
|
+
});
|
|
154
|
+
let transformed = code;
|
|
155
|
+
for (const insertion of insertions.sort((left, right) => right.position - left.position || left.order - right.order)) {
|
|
156
|
+
transformed =
|
|
157
|
+
transformed.slice(0, insertion.position) +
|
|
158
|
+
insertion.text +
|
|
159
|
+
transformed.slice(insertion.position);
|
|
160
|
+
}
|
|
161
|
+
return { code: transformed, boundaryCount: calls.length };
|
|
162
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { EntryContext } from "../types.js";
|
|
2
|
+
export declare const serverFnTransportErrorClientRuntime = "function createFarmServerFnTransportError(value) {\n const message = value && typeof value === 'object' && typeof value.message === 'string'\n ? value.message\n : 'Server function failed';\n const error = new Error(message);\n\n if (\n value &&\n typeof value === 'object' &&\n value.name === 'ServerFnFailure' &&\n typeof value.code === 'string' &&\n Number.isInteger(value.status) &&\n value.status >= 400 &&\n value.status <= 599 &&\n Object.prototype.hasOwnProperty.call(value, 'data')\n ) {\n error.name = 'ServerFnFailure';\n error.code = value.code;\n error.status = value.status;\n error.data = value.data;\n Object.defineProperty(error, Symbol.for('farm.server-fn.failure'), {\n value: true,\n enumerable: false,\n });\n return error;\n }\n\n error.name = 'ServerActionError';\n return error;\n}";
|
|
2
3
|
/**
|
|
3
4
|
* Generates the browser entry file.
|
|
4
5
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../../src/rsc/entries/client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD;;;;;;;;;;GAUG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,YAAY,GAAG,MAAM,CAiR7D"}
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../../src/rsc/entries/client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD,eAAO,MAAM,mCAAmC,62BA6B9C,CAAC;AAEH;;;;;;;;;;GAUG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,YAAY,GAAG,MAAM,CAiR7D"}
|
|
@@ -1,3 +1,33 @@
|
|
|
1
|
+
export const serverFnTransportErrorClientRuntime = `function createFarmServerFnTransportError(value) {
|
|
2
|
+
const message = value && typeof value === 'object' && typeof value.message === 'string'
|
|
3
|
+
? value.message
|
|
4
|
+
: 'Server function failed';
|
|
5
|
+
const error = new Error(message);
|
|
6
|
+
|
|
7
|
+
if (
|
|
8
|
+
value &&
|
|
9
|
+
typeof value === 'object' &&
|
|
10
|
+
value.name === 'ServerFnFailure' &&
|
|
11
|
+
typeof value.code === 'string' &&
|
|
12
|
+
Number.isInteger(value.status) &&
|
|
13
|
+
value.status >= 400 &&
|
|
14
|
+
value.status <= 599 &&
|
|
15
|
+
Object.prototype.hasOwnProperty.call(value, 'data')
|
|
16
|
+
) {
|
|
17
|
+
error.name = 'ServerFnFailure';
|
|
18
|
+
error.code = value.code;
|
|
19
|
+
error.status = value.status;
|
|
20
|
+
error.data = value.data;
|
|
21
|
+
Object.defineProperty(error, Symbol.for('farm.server-fn.failure'), {
|
|
22
|
+
value: true,
|
|
23
|
+
enumerable: false,
|
|
24
|
+
});
|
|
25
|
+
return error;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
error.name = 'ServerActionError';
|
|
29
|
+
return error;
|
|
30
|
+
}`;
|
|
1
31
|
/**
|
|
2
32
|
* Generates the browser entry file.
|
|
3
33
|
*
|
|
@@ -45,6 +75,8 @@ import {
|
|
|
45
75
|
let actionSetup = "";
|
|
46
76
|
if (ctx.actionsEnabled) {
|
|
47
77
|
actionSetup = `
|
|
78
|
+
${serverFnTransportErrorClientRuntime}
|
|
79
|
+
|
|
48
80
|
// Ref for payload setter (used by server action callback and refetch)
|
|
49
81
|
const setPayloadRef = { current: null };
|
|
50
82
|
|
|
@@ -96,9 +128,7 @@ setServerCallback(async (id, args) => {
|
|
|
96
128
|
applyFarmCacheInvalidations(p.returnValue?.invalidations);
|
|
97
129
|
if (!p.returnValue || !p.returnValue.ok) {
|
|
98
130
|
debug('Server action failed:', id);
|
|
99
|
-
|
|
100
|
-
error.name = 'ServerActionError';
|
|
101
|
-
throw error;
|
|
131
|
+
throw createFarmServerFnTransportError(p.returnValue?.data);
|
|
102
132
|
}
|
|
103
133
|
return completeFarmServerQueryAction(serverQueryInvocation, p.returnValue.data);
|
|
104
134
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rsc.d.ts","sourceRoot":"","sources":["../../../src/rsc/entries/rsc.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,YAAY,GAAG,MAAM,
|
|
1
|
+
{"version":3,"file":"rsc.d.ts","sourceRoot":"","sources":["../../../src/rsc/entries/rsc.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,YAAY,GAAG,MAAM,CAw7B1D"}
|
package/dist/rsc/entries/rsc.js
CHANGED
|
@@ -51,7 +51,7 @@ import {
|
|
|
51
51
|
} from '@farm.js/core/middleware';
|
|
52
52
|
import { invokeAPIRouteEndpoint, matchAPIRoute } from '@farm.js/core/api/runtime';
|
|
53
53
|
import { _runWithAfterRequest } from '@farm.js/core/after';
|
|
54
|
-
import { _runWithCurrentRequest } from '@farm.js/core/internal/production-runtime';
|
|
54
|
+
import { _runWithCurrentRequest, searchParamsToObject } from '@farm.js/core/internal/production-runtime';
|
|
55
55
|
|
|
56
56
|
const farmDeploymentId = ${JSON.stringify(ctx.deploymentId)};
|
|
57
57
|
`;
|
|
@@ -642,8 +642,11 @@ async function handleFarmRequest(request) {
|
|
|
642
642
|
if (request.signal.aborted) {
|
|
643
643
|
return new Response(null, { status: 499, headers: { 'Cache-Control': 'no-store' } });
|
|
644
644
|
}
|
|
645
|
-
|
|
646
|
-
|
|
645
|
+
const actionError = sanitizeServerActionError(e);
|
|
646
|
+
if (actionError.name === 'ServerActionError') {
|
|
647
|
+
console.error('[Farm.js] Server action failed:', e);
|
|
648
|
+
}
|
|
649
|
+
returnValue = { ok: false, data: actionError };
|
|
647
650
|
debug('Server action failed:', actionId, e);
|
|
648
651
|
}
|
|
649
652
|
} else {
|
|
@@ -674,10 +677,13 @@ async function handleFarmRequest(request) {
|
|
|
674
677
|
if (request.signal.aborted) {
|
|
675
678
|
return new Response(null, { status: 499, headers: { 'Cache-Control': 'no-store' } });
|
|
676
679
|
}
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
680
|
+
const actionError = sanitizeServerActionError(e);
|
|
681
|
+
if (actionError.name === 'ServerActionError') {
|
|
682
|
+
console.error('[Farm.js] Form action failed:', e);
|
|
683
|
+
}
|
|
684
|
+
debug('Form action failed:', actionError);
|
|
685
|
+
return new Response(actionError.message, {
|
|
686
|
+
status: actionError.name === 'ServerFnFailure' ? actionError.status : 500,
|
|
681
687
|
headers: {
|
|
682
688
|
'Cache-Control': 'no-store',
|
|
683
689
|
'Content-Type': 'text/plain; charset=utf-8',
|
|
@@ -713,7 +719,9 @@ async function handleFarmRequest(request) {
|
|
|
713
719
|
const globalsCssPath = '/${ctx.srcDir}' + routesPath + '/globals.css';
|
|
714
720
|
|
|
715
721
|
// Parse search params
|
|
716
|
-
|
|
722
|
+
// Collect repeated keys into arrays, matching the searchParams prop shape
|
|
723
|
+
// every non-RSC environment produces (see core's search-params.ts).
|
|
724
|
+
const searchParams = searchParamsToObject(url.searchParams);
|
|
717
725
|
|
|
718
726
|
// Page props passed to components (includes middleware shared data)
|
|
719
727
|
const pageProps = { params, searchParams, middlewareData };
|
|
@@ -878,7 +886,7 @@ async function handleFarmRequest(request) {
|
|
|
878
886
|
const Layout = LayoutModules[LayoutModules.length - 1]?.default;
|
|
879
887
|
const LayoutComp = Layout || (function PassThrough({ children }) { return children; });
|
|
880
888
|
const errParams = matched ? matched.params : {};
|
|
881
|
-
const errSearchParams =
|
|
889
|
+
const errSearchParams = searchParamsToObject(url.searchParams);
|
|
882
890
|
const errorElement = h(ErrorComponent, {
|
|
883
891
|
error: err,
|
|
884
892
|
reset: () => {},
|
package/dist/rsc/index.d.ts
CHANGED
|
@@ -49,8 +49,9 @@ export interface FarmRscConfig {
|
|
|
49
49
|
*/
|
|
50
50
|
serverActions?: boolean;
|
|
51
51
|
/**
|
|
52
|
-
*
|
|
53
|
-
*
|
|
52
|
+
* Automatically optimize eligible server-only host-element subtrees with
|
|
53
|
+
* the native Strata renderer. Unsupported trees keep normal React
|
|
54
|
+
* rendering; application components do not need a boundary import.
|
|
54
55
|
*
|
|
55
56
|
* @experimental
|
|
56
57
|
* @default false
|
package/dist/rsc/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/rsc/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,EAA4B,KAAK,MAAM,EAAE,KAAK,UAAU,EAAE,MAAM,MAAM,CAAC;AAE9E,OAAO,KAAK,EAAE,oBAAoB,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AACrE,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,sCAAsC,CAAC;AACpF,OAAO,KAAK,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/rsc/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,EAA4B,KAAK,MAAM,EAAE,KAAK,UAAU,EAAE,MAAM,MAAM,CAAC;AAE9E,OAAO,KAAK,EAAE,oBAAoB,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AACrE,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,sCAAsC,CAAC;AACpF,OAAO,KAAK,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAiC9E,YAAY,EAAE,oBAAoB,EAAE,YAAY,EAAE,CAAC;AACnD,OAAO,EAAE,aAAa,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAExF,YAAY,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAC7D,OAAO,EAAE,OAAO,IAAI,KAAK,EAAE,MAAM,wBAAwB,CAAC;AAC1D,YAAY,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAGjE,MAAM,WAAW,aAAa;IAC5B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,SAAS,cAAc,EAAE,CAAC;IACpC,MAAM,CAAC,EAAE,SAAS,iBAAiB,EAAE,CAAC;IACtC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,YAAY,CAAC,EAAE;QACb;;;WAGG;QACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;QAC3B;;;WAGG;QACH,aAAa,CAAC,EAAE,OAAO,CAAC;QACxB;;;;;;;WAOG;QACH,iBAAiB,CAAC,EAAE,OAAO,CAAC;KAC7B,CAAC;IACF,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,aAAa,CAAC,EAAE,uBAAuB,CAAC;IACxC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,eAAe,CAAC,EAAE,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACjD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE;QACR,GAAG,CAAC,EAAE,MAAM,CAAC;QACb,GAAG,CAAC,EAAE,MAAM,CAAC;QACb,MAAM,CAAC,EAAE,MAAM,CAAC;KACjB,CAAC;IACF,gHAAgH;IAChH,OAAO,CAAC,EAAE,GAAG,EAAE,CAAC;CACjB;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,MAAM,GAAE,aAAkB,GAAG,UAAU,CAiDnE;AA8TD;;;;;GAKG;AACH,MAAM,CAAC,OAAO,UAAU,OAAO,CAAC,OAAO,GAAE,oBAAyB,GAAG,MAAM,EAAE,CAkqC5E"}
|