@farm.js/plugin 0.1.0-beta.101 → 0.1.0-beta.103
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/api/index.d.ts.map +1 -1
- package/dist/api/index.js +22 -12
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/middleware/index.d.ts +2 -3
- package/dist/middleware/index.d.ts.map +1 -1
- package/dist/middleware/index.js +16 -10
- package/dist/rsc/entries/client.d.ts.map +1 -1
- package/dist/rsc/entries/client.js +35 -2
- package/dist/rsc/entries/rsc.d.ts.map +1 -1
- package/dist/rsc/entries/rsc.js +25 -5
- package/dist/rsc/index.d.ts.map +1 -1
- package/dist/rsc/index.js +6 -0
- package/dist/rsc/server-fn-transform.d.ts.map +1 -1
- package/dist/rsc/server-fn-transform.js +27 -0
- package/package.json +3 -3
package/dist/api/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/api/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAiB,MAAM,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/api/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAiB,MAAM,MAAM,CAAC;AAgBlD,MAAM,WAAW,cAAc;IAC7B,kEAAkE;IAClE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,2BAA2B;IAC3B,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,uDAAuD;IACvD,aAAa,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;CACjC;AAED,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CAChC;AAED;;GAEG;AACH,MAAM,CAAC,OAAO,UAAU,OAAO,CAAC,OAAO,GAAE,cAAmB,GAAG,MAAM,CA6hBpE;AAED,OAAO,EAAE,OAAO,EAAE,CAAC"}
|
package/dist/api/index.js
CHANGED
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
*/
|
|
24
24
|
import { _withAfterNodeMiddleware } from "@farm.js/core/after";
|
|
25
25
|
import { _runWithAPIRequestRuntime, createFarmRequestBodyErrorResponse, readNodeRequestBody, resolveFarmServerConfig, } from "@farm.js/core/internal/production-runtime";
|
|
26
|
-
import { invokeAPIRouteEndpoint } from "@farm.js/core/api/runtime";
|
|
26
|
+
import { getAllowedAPIRouteMethods, invokeAPIRouteEndpoint, matchAPIRouteAtBasePath, resolveAPIRouteEndpoint, } from "@farm.js/core/api/runtime";
|
|
27
27
|
import { sendWebResponse } from "@farm.js/core/server";
|
|
28
28
|
/**
|
|
29
29
|
* Farm.js API Routes Plugin
|
|
@@ -88,23 +88,30 @@ export default function farmApi(options = {}) {
|
|
|
88
88
|
const url = new URL(request.url);
|
|
89
89
|
const method = request.method;
|
|
90
90
|
const pathname = url.pathname;
|
|
91
|
-
//
|
|
92
|
-
|
|
93
|
-
|
|
91
|
+
// Match through core's matcher, the same one the production entry
|
|
92
|
+
// uses, so dynamic segments resolve here too. An exact Map lookup
|
|
93
|
+
// registers `src/api/users/[id]/route.ts` under the literal
|
|
94
|
+
// "/api/users/[id]" and 404s every real request for it.
|
|
95
|
+
const match = matchAPIRouteAtBasePath(apiRoutesCache, pathname);
|
|
96
|
+
if (!match) {
|
|
94
97
|
return new Response(JSON.stringify({ error: "Not Found" }), {
|
|
95
98
|
status: 404,
|
|
96
99
|
headers: { "Content-Type": "application/json" },
|
|
97
100
|
});
|
|
98
101
|
}
|
|
99
|
-
const
|
|
102
|
+
const route = match.route;
|
|
103
|
+
const endpoint = resolveAPIRouteEndpoint(route, method);
|
|
100
104
|
if (!endpoint) {
|
|
101
105
|
return new Response(JSON.stringify({ error: "Method Not Allowed" }), {
|
|
102
106
|
status: 405,
|
|
103
|
-
headers: {
|
|
107
|
+
headers: {
|
|
108
|
+
Allow: getAllowedAPIRouteMethods(route).join(", "),
|
|
109
|
+
"Content-Type": "application/json",
|
|
110
|
+
},
|
|
104
111
|
});
|
|
105
112
|
}
|
|
106
113
|
try {
|
|
107
|
-
return await invokeAPIRouteEndpoint(endpoint, request);
|
|
114
|
+
return await invokeAPIRouteEndpoint(endpoint, request, match.params);
|
|
108
115
|
}
|
|
109
116
|
catch (error) {
|
|
110
117
|
return new Response(JSON.stringify({ error: error.message || "Internal Server Error" }), {
|
|
@@ -121,19 +128,22 @@ export default function farmApi(options = {}) {
|
|
|
121
128
|
return async (request) => {
|
|
122
129
|
const url = new URL(request.url);
|
|
123
130
|
const method = request.method;
|
|
124
|
-
//
|
|
125
|
-
const
|
|
126
|
-
if (!
|
|
131
|
+
// Same core matcher as above so dynamic segments resolve here too.
|
|
132
|
+
const match = matchAPIRouteAtBasePath(apiRoutesCache, url.pathname);
|
|
133
|
+
if (!match) {
|
|
127
134
|
return new Response(JSON.stringify({ error: "Not Found" }), {
|
|
128
135
|
status: 404,
|
|
129
136
|
headers: { "Content-Type": "application/json" },
|
|
130
137
|
});
|
|
131
138
|
}
|
|
132
|
-
const endpoint = route
|
|
139
|
+
const endpoint = resolveAPIRouteEndpoint(match.route, method);
|
|
133
140
|
if (!endpoint) {
|
|
134
141
|
return new Response(JSON.stringify({ error: "Method Not Allowed" }), {
|
|
135
142
|
status: 405,
|
|
136
|
-
headers: {
|
|
143
|
+
headers: {
|
|
144
|
+
Allow: getAllowedAPIRouteMethods(match.route).join(", "),
|
|
145
|
+
"Content-Type": "application/json",
|
|
146
|
+
},
|
|
137
147
|
});
|
|
138
148
|
}
|
|
139
149
|
try {
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
*/
|
|
16
16
|
import type { Plugin, ViteDevServer } from "vite";
|
|
17
17
|
import type { IncomingMessage, ServerResponse } from "http";
|
|
18
|
+
import type { MiddlewareConfig } from "@farm.js/core/middleware";
|
|
18
19
|
export interface FarmMiddlewareOptions {
|
|
19
20
|
/** Source directory containing middleware files (default: 'src') */
|
|
20
21
|
srcDir?: string;
|
|
@@ -85,9 +86,7 @@ export interface DiscoveredMiddleware {
|
|
|
85
86
|
};
|
|
86
87
|
setBasePath?: (path: string) => void;
|
|
87
88
|
};
|
|
88
|
-
config?:
|
|
89
|
-
matcher?: string[];
|
|
90
|
-
};
|
|
89
|
+
config?: MiddlewareConfig;
|
|
91
90
|
}
|
|
92
91
|
/**
|
|
93
92
|
* Farm.js Middleware Plugin
|
|
@@ -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
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
import { createRequire } from "node:module";
|
|
17
17
|
import { _withAfterNodeMiddleware } from "@farm.js/core/after";
|
|
18
18
|
import { devServableFileExists } from "../dev-static.js";
|
|
19
|
+
import { matchesMiddlewareConfig } from "@farm.js/core/middleware";
|
|
19
20
|
import { middlewareMatchesPath, parseCookies, serializeCookie } from "./matching.js";
|
|
20
21
|
// Create require for ESM compatibility
|
|
21
22
|
const require_ = createRequire(import.meta.url);
|
|
@@ -266,9 +267,22 @@ export default function farmMiddleware(options = {}) {
|
|
|
266
267
|
if (discoveryPromise && !discoveryComplete) {
|
|
267
268
|
await discoveryPromise;
|
|
268
269
|
}
|
|
269
|
-
//
|
|
270
|
+
// Create the context first: a function matcher in `export const
|
|
271
|
+
// config` receives it, so it has to exist before the filter runs.
|
|
272
|
+
const ctx = createContext(req, res, server);
|
|
273
|
+
// Use shared data if provided
|
|
274
|
+
if (sharedData) {
|
|
275
|
+
for (const [key, value] of sharedData) {
|
|
276
|
+
ctx.data.set(key, value);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
// Find applicable middleware (cascading from root to specific). The
|
|
280
|
+
// directory path decides the subtree; `export const config` then
|
|
281
|
+
// narrows it, using core's own matcher so a matcher/exclude behaves
|
|
282
|
+
// here exactly as it does in the production runner.
|
|
270
283
|
const applicable = Array.from(middlewareCache.values())
|
|
271
|
-
.filter((mw) => middlewareMatchesPath(pathname, mw.path)
|
|
284
|
+
.filter((mw) => middlewareMatchesPath(pathname, mw.path) &&
|
|
285
|
+
(!mw.config || matchesMiddlewareConfig(pathname, mw.config, ctx).matched))
|
|
272
286
|
.sort((a, b) => a.path.split("/").length - b.path.split("/").length);
|
|
273
287
|
if (applicable.length === 0)
|
|
274
288
|
return false;
|
|
@@ -285,14 +299,6 @@ export default function farmMiddleware(options = {}) {
|
|
|
285
299
|
pc.dim(` (${applicable.length} middleware)`),
|
|
286
300
|
].join(" ");
|
|
287
301
|
console.log(logMsg);
|
|
288
|
-
// Create full middleware context
|
|
289
|
-
const ctx = createContext(req, res, server);
|
|
290
|
-
// Use shared data if provided
|
|
291
|
-
if (sharedData) {
|
|
292
|
-
for (const [key, value] of sharedData) {
|
|
293
|
-
ctx.data.set(key, value);
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
302
|
for (const mw of applicable) {
|
|
297
303
|
const middleware = mw.module;
|
|
298
304
|
// Handle middleware chain object (has .build method)
|
|
@@ -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,eAAO,MAAM,mCAAmC,62BA6B9C,CAAC;AAEH;;;;;;;;;;GAUG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,YAAY,GAAG,MAAM,
|
|
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,CAyT7D"}
|
|
@@ -78,8 +78,11 @@ import {
|
|
|
78
78
|
actionSetup = `
|
|
79
79
|
${serverFnTransportErrorClientRuntime}
|
|
80
80
|
|
|
81
|
-
//
|
|
81
|
+
// Refs for payload setter and router navigation. refetch() is defined inside
|
|
82
|
+
// main(), so the action callback reaches it through this ref rather than by
|
|
83
|
+
// closing over it.
|
|
82
84
|
const setPayloadRef = { current: null };
|
|
85
|
+
const farmNavigateRef = { current: null };
|
|
83
86
|
|
|
84
87
|
// Ensure __viteRscCallServer is a function before any other chunk may call it (avoids "is not a function")
|
|
85
88
|
if (typeof globalThis.__viteRscCallServer !== 'function') {
|
|
@@ -130,6 +133,14 @@ setServerCallback(async (id, args) => {
|
|
|
130
133
|
applyFarmCacheInvalidations(p.returnValue?.invalidations);
|
|
131
134
|
}
|
|
132
135
|
if (!p.returnValue || !p.returnValue.ok) {
|
|
136
|
+
// A redirect() from the action comes back as data, not as a 3xx, so the
|
|
137
|
+
// router performs the navigation here.
|
|
138
|
+
if (p.returnValue?.redirect?.url) {
|
|
139
|
+
debug('Server action redirected:', p.returnValue.redirect.url);
|
|
140
|
+
history.pushState(null, '', p.returnValue.redirect.url);
|
|
141
|
+
await farmNavigateRef.current?.(location.href);
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
133
144
|
debug('Server action failed:', id);
|
|
134
145
|
throw createFarmServerFnTransportError(p.returnValue?.data);
|
|
135
146
|
}
|
|
@@ -140,6 +151,7 @@ setServerCallback(async (id, args) => {
|
|
|
140
151
|
else {
|
|
141
152
|
actionSetup = `
|
|
142
153
|
const setPayloadRef = { current: null };
|
|
154
|
+
const farmNavigateRef = { current: null };
|
|
143
155
|
`;
|
|
144
156
|
}
|
|
145
157
|
return `${imports}
|
|
@@ -230,7 +242,14 @@ async function main() {
|
|
|
230
242
|
}
|
|
231
243
|
if (e.metaKey || e.altKey || e.ctrlKey || e.shiftKey) return;
|
|
232
244
|
if (e.button !== 0) return;
|
|
233
|
-
|
|
245
|
+
|
|
246
|
+
// Same-document fragment link: let the browser update the hash and
|
|
247
|
+
// scroll to the anchor natively. Refetching the route here would
|
|
248
|
+
// discard the browser's fragment scroll and re-render for nothing.
|
|
249
|
+
if (a.hash && a.pathname === location.pathname && a.search === location.search) {
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
|
|
234
253
|
e.preventDefault();
|
|
235
254
|
history.pushState(null, '', a.href);
|
|
236
255
|
nav();
|
|
@@ -256,6 +275,7 @@ async function main() {
|
|
|
256
275
|
}
|
|
257
276
|
|
|
258
277
|
// Fetch new RSC payload for a URL
|
|
278
|
+
farmNavigateRef.current = (url) => refetch(url);
|
|
259
279
|
async function refetch(url) {
|
|
260
280
|
debug('Fetching RSC for:', url);
|
|
261
281
|
|
|
@@ -279,6 +299,19 @@ async function main() {
|
|
|
279
299
|
return;
|
|
280
300
|
}
|
|
281
301
|
|
|
302
|
+
// A server redirect during navigation is followed transparently by
|
|
303
|
+
// fetch, so the payload is for res.url while the address bar still shows
|
|
304
|
+
// the URL the click pushed. Reconcile it so refresh, share, and back all
|
|
305
|
+
// reflect the page actually rendered (auth gates redirect this way).
|
|
306
|
+
if (res.redirected && res.url) {
|
|
307
|
+
const dest = new URL(res.url);
|
|
308
|
+
if (dest.origin === location.origin) {
|
|
309
|
+
const target = dest.pathname + dest.search + dest.hash;
|
|
310
|
+
if (target !== location.pathname + location.search + location.hash) {
|
|
311
|
+
history.replaceState(null, '', target);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
282
315
|
const newPayload = await createFromReadableStream(res.body);
|
|
283
316
|
setPayloadRef.current?.(newPayload);
|
|
284
317
|
debug('RSC navigation complete');
|
|
@@ -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,CAk8B1D"}
|
package/dist/rsc/entries/rsc.js
CHANGED
|
@@ -599,12 +599,22 @@ async function handleFarmRequest(request) {
|
|
|
599
599
|
if (request.signal.aborted) {
|
|
600
600
|
return new Response(null, { status: 499, headers: { 'Cache-Control': 'no-store' } });
|
|
601
601
|
}
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
602
|
+
// redirect() unwinds by throwing, so it arrives here looking like a
|
|
603
|
+
// failure. It is control flow, not an error: carry it to the client as
|
|
604
|
+
// data so the router can navigate. Everything else still goes through
|
|
605
|
+
// sanitizeServerActionError untouched.
|
|
606
|
+
const actionRedirect = getFarmRedirectError(e);
|
|
607
|
+
if (actionRedirect) {
|
|
608
|
+
returnValue = { ok: false, redirect: { url: actionRedirect.url, status: actionRedirect.status } };
|
|
609
|
+
debug('Server action redirected:', actionId, actionRedirect.url);
|
|
610
|
+
} else {
|
|
611
|
+
const actionError = sanitizeServerActionError(e);
|
|
612
|
+
if (actionError.name === 'ServerActionError') {
|
|
613
|
+
console.error('[Farm.js] Server action failed:', e);
|
|
614
|
+
}
|
|
615
|
+
returnValue = { ok: false, data: actionError };
|
|
616
|
+
debug('Server action failed:', actionId, e);
|
|
605
617
|
}
|
|
606
|
-
returnValue = { ok: false, data: actionError };
|
|
607
|
-
debug('Server action failed:', actionId, e);
|
|
608
618
|
}
|
|
609
619
|
} else {
|
|
610
620
|
// Progressive enhancement (form submitted before JS loaded)
|
|
@@ -634,6 +644,16 @@ async function handleFarmRequest(request) {
|
|
|
634
644
|
if (request.signal.aborted) {
|
|
635
645
|
return new Response(null, { status: 499, headers: { 'Cache-Control': 'no-store' } });
|
|
636
646
|
}
|
|
647
|
+
// No client router here, so the redirect has to be a real HTTP
|
|
648
|
+
// redirect the browser follows on its own.
|
|
649
|
+
const formRedirect = getFarmRedirectError(e);
|
|
650
|
+
if (formRedirect) {
|
|
651
|
+
debug('Form action redirected:', formRedirect.url);
|
|
652
|
+
return new Response(null, {
|
|
653
|
+
status: formRedirect.status,
|
|
654
|
+
headers: { location: formRedirect.url, 'Cache-Control': 'no-store' },
|
|
655
|
+
});
|
|
656
|
+
}
|
|
637
657
|
const actionError = sanitizeServerActionError(e);
|
|
638
658
|
if (actionError.name === 'ServerActionError') {
|
|
639
659
|
console.error('[Farm.js] Form action failed:', e);
|
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,EAIL,KAAK,MAAM,EACX,KAAK,UAAU,EAChB,MAAM,MAAM,CAAC;AAEd,OAAO,KAAK,EAAE,oBAAoB,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AACrE,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,sCAAsC,CAAC;AACpF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AACvD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,2CAA2C,CAAC;AAClF,OAAO,KAAK,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAwD9E,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,GAAG,CAAC,EAAE,aAAa,CAAC;IACpB,MAAM,CAAC,EAAE,gBAAgB,CAAC;IAC1B,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,CAuDnE;AA8TD;;;;;GAKG;AACH,MAAM,CAAC,OAAO,UAAU,OAAO,CAAC,OAAO,GAAE,oBAAyB,GAAG,MAAM,EAAE,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/rsc/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,EAIL,KAAK,MAAM,EACX,KAAK,UAAU,EAChB,MAAM,MAAM,CAAC;AAEd,OAAO,KAAK,EAAE,oBAAoB,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AACrE,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,sCAAsC,CAAC;AACpF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AACvD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,2CAA2C,CAAC;AAClF,OAAO,KAAK,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAwD9E,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,GAAG,CAAC,EAAE,aAAa,CAAC;IACpB,MAAM,CAAC,EAAE,gBAAgB,CAAC;IAC1B,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,CAuDnE;AA8TD;;;;;GAKG;AACH,MAAM,CAAC,OAAO,UAAU,OAAO,CAAC,OAAO,GAAE,oBAAyB,GAAG,MAAM,EAAE,CAsvC5E"}
|
package/dist/rsc/index.js
CHANGED
|
@@ -950,6 +950,12 @@ setServerCallback(async (id, args) => {
|
|
|
950
950
|
if (!res.ok) throw new Error('Server action failed: ' + res.status);
|
|
951
951
|
const p = await createFromReadableStream(res.body, { temporaryReferences: refs });
|
|
952
952
|
if (p?.returnValue?.ok) return p.returnValue.data;
|
|
953
|
+
// This entry hydrates a single page and has no client router, so a redirect
|
|
954
|
+
// from the action becomes a full document navigation.
|
|
955
|
+
if (p?.returnValue?.redirect?.url) {
|
|
956
|
+
location.assign(p.returnValue.redirect.url);
|
|
957
|
+
return;
|
|
958
|
+
}
|
|
953
959
|
throw createFarmServerFnTransportError(p?.returnValue?.data);
|
|
954
960
|
});
|
|
955
961
|
`
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server-fn-transform.d.ts","sourceRoot":"","sources":["../../src/rsc/server-fn-transform.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"server-fn-transform.d.ts","sourceRoot":"","sources":["../../src/rsc/server-fn-transform.ts"],"names":[],"mappings":"AASA,MAAM,MAAM,2BAA2B,GAAG;IACxC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB,CAAC;AAEF,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,MAAM,EACZ,EAAE,EAAE,MAAM,GACT,2BAA2B,GAAG,IAAI,CAiCpC"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const SERVER_FUNCTION_FACTORY_IMPORT_RE = /\bimport\s+(?:type\s+)?(?:[A-Za-z_$][\w$]*\s*,\s*)?(\{[^}]*\})\s+from\s*["']@farm.js\/core(?:\/(?:server-fn|server-query))?["']\s*;?/g;
|
|
2
2
|
const EXPORT_SERVER_FUNCTION_FACTORY_RE = /\bexport\s+(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*([A-Za-z_$][\w$]*)\s*(?:<[^<>]*(?:<[^<>]*>[^<>]*)*>)?\s*\(/g;
|
|
3
|
+
const EXPORT_DEFAULT_SERVER_FUNCTION_FACTORY_RE = /\bexport\s+default\s+([A-Za-z_$][\w$]*)\s*(?:<[^<>]*(?:<[^<>]*>[^<>]*)*>)?\s*\(/g;
|
|
3
4
|
const MODULE_EXT_RE = /\.[cm]?[jt]sx?$/;
|
|
4
5
|
const DECLARATION_RE = /(^|\n)\s*["']([^"']+)["']\s*;?/g;
|
|
5
6
|
export function transformFarmServerFns(code, id) {
|
|
@@ -79,6 +80,32 @@ function findServerFnExportReplacements(code, factoryNames) {
|
|
|
79
80
|
code: `const ${privateName} = ${callExpression};\nexport async function ${exportName}(input) {\n return ${privateName}(input);\n}`,
|
|
80
81
|
});
|
|
81
82
|
}
|
|
83
|
+
// `export default createServerFn(...)` needs the same treatment. Without it the
|
|
84
|
+
// module gets no "use server" directive and its handler ships to the client.
|
|
85
|
+
for (const match of code.matchAll(EXPORT_DEFAULT_SERVER_FUNCTION_FACTORY_RE)) {
|
|
86
|
+
const calleeName = match[1];
|
|
87
|
+
const factory = calleeName ? factoryNames.get(calleeName) : undefined;
|
|
88
|
+
if (!calleeName || !factory)
|
|
89
|
+
continue;
|
|
90
|
+
const openParenIndex = (match.index ?? 0) + match[0].length - 1;
|
|
91
|
+
const callEnd = findMatchingParen(code, openParenIndex);
|
|
92
|
+
if (callEnd === -1)
|
|
93
|
+
continue;
|
|
94
|
+
let declarationEnd = callEnd + 1;
|
|
95
|
+
while (/\s/.test(code[declarationEnd] ?? ""))
|
|
96
|
+
declarationEnd++;
|
|
97
|
+
if (code[declarationEnd] === ";")
|
|
98
|
+
declarationEnd++;
|
|
99
|
+
const calleeIndex = code.indexOf(calleeName, match.index);
|
|
100
|
+
const callExpression = code.slice(calleeIndex, callEnd + 1);
|
|
101
|
+
const privateName = `$$farm_server_${factory}_default`;
|
|
102
|
+
replacements.push({
|
|
103
|
+
start: match.index ?? 0,
|
|
104
|
+
end: declarationEnd,
|
|
105
|
+
exportName: "default",
|
|
106
|
+
code: `const ${privateName} = ${callExpression};\nexport default async function (input) {\n return ${privateName}(input);\n}`,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
82
109
|
return replacements;
|
|
83
110
|
}
|
|
84
111
|
function hasModuleDirective(code, directive) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@farm.js/plugin",
|
|
3
|
-
"version": "0.1.0-beta.
|
|
3
|
+
"version": "0.1.0-beta.103",
|
|
4
4
|
"description": "Official plugins for Farm.js framework",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Farm.js Team",
|
|
@@ -61,7 +61,7 @@
|
|
|
61
61
|
"nitro": "3.0.1-alpha.0",
|
|
62
62
|
"picocolors": "^1.0.0",
|
|
63
63
|
"rsc-html-stream": "^0.0.4",
|
|
64
|
-
"@farm.js/core": "0.1.0-beta.
|
|
64
|
+
"@farm.js/core": "0.1.0-beta.103"
|
|
65
65
|
},
|
|
66
66
|
"devDependencies": {
|
|
67
67
|
"@types/node": "^20.19.21",
|
|
@@ -82,7 +82,7 @@
|
|
|
82
82
|
"react-dom": "19.2.8",
|
|
83
83
|
"react-server-dom-webpack": "19.2.8",
|
|
84
84
|
"vite": ">=6.0.0",
|
|
85
|
-
"@farm.js/core": "0.1.0-beta.
|
|
85
|
+
"@farm.js/core": "0.1.0-beta.103"
|
|
86
86
|
},
|
|
87
87
|
"peerDependenciesMeta": {
|
|
88
88
|
"@vitejs/plugin-rsc": {
|