@farm.js/plugin 0.1.0-beta.7 → 0.1.0-beta.70

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,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
@@ -39,5 +39,5 @@
39
39
  * })
40
40
  * ```
41
41
  */
42
- export declare const version = "0.1.0-beta.7";
42
+ export declare const version = "0.1.0-beta.70";
43
43
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AAEH,eAAO,MAAM,OAAO,iBAAiB,CAAC"}
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
@@ -39,7 +39,7 @@
39
39
  * })
40
40
  * ```
41
41
  */
42
- export const version = "0.1.0-beta.7";
42
+ export const version = "0.1.0-beta.70";
43
43
  // Note: Import plugins from their respective subpaths:
44
44
  // - @farm.js/plugin/rsc
45
45
  // - @farm.js/plugin/middleware
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/middleware/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/middleware/index.ts"],"names":[],"mappings":"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"}
@@ -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(".") && !pathname.endsWith("/"))) {
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
- const error = new Error(p.returnValue?.data?.message || 'Server function failed');
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,CAg7B1D"}
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"}
@@ -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
- console.error('[Farm.js] Server action failed:', e);
646
- returnValue = { ok: false, data: sanitizeServerActionError(e) };
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
- console.error('[Farm.js] Form action failed:', e);
678
- debug('Form action failed:', e);
679
- return new Response('Server function failed', {
680
- status: 500,
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
- const searchParams = Object.fromEntries(url.searchParams);
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 = Object.fromEntries(url.searchParams);
889
+ const errSearchParams = searchParamsToObject(url.searchParams);
882
890
  const errorElement = h(ErrorComponent, {
883
891
  error: err,
884
892
  reset: () => {},
@@ -49,8 +49,9 @@ export interface FarmRscConfig {
49
49
  */
50
50
  serverActions?: boolean;
51
51
  /**
52
- * Enable server-only optimized boundaries through
53
- * `@farm.js/plugin/rsc/optimized-boundary`.
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
@@ -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;AA2B9E,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;;;;;;WAMG;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,CAsnC5E"}
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"}