@farm.js/plugin 0.1.0-beta.1 → 0.1.0-beta.100
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 +2 -0
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/index.js +25 -95
- 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 +28 -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/compatibility.d.ts +8 -0
- package/dist/rsc/compatibility.d.ts.map +1 -0
- package/dist/rsc/compatibility.js +46 -0
- package/dist/rsc/dev-response.d.ts +3 -0
- package/dist/rsc/dev-response.d.ts.map +1 -0
- package/dist/rsc/dev-response.js +10 -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 +42 -5
- package/dist/rsc/entries/error-fallback.d.ts +3 -0
- package/dist/rsc/entries/error-fallback.d.ts.map +1 -0
- package/dist/rsc/entries/error-fallback.js +12 -0
- package/dist/rsc/entries/rsc.d.ts.map +1 -1
- package/dist/rsc/entries/rsc.js +187 -189
- package/dist/rsc/entries/ssr.d.ts.map +1 -1
- package/dist/rsc/entries/ssr.js +44 -74
- package/dist/rsc/index.d.ts +15 -2
- package/dist/rsc/index.d.ts.map +1 -1
- package/dist/rsc/index.js +167 -69
- package/dist/rsc/nitro-build.d.ts.map +1 -1
- package/dist/rsc/nitro-build.js +28 -8
- package/dist/rsc/optimized-boundary.d.ts +10 -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/dist/rsc/server-fn-transform.js +1 -1
- package/dist/rsc/test-fixture-dependencies.d.ts +2 -0
- package/dist/rsc/test-fixture-dependencies.d.ts.map +1 -0
- package/dist/rsc/test-fixture-dependencies.js +23 -0
- package/dist/rsc/types.d.ts +9 -0
- package/dist/rsc/types.d.ts.map +1 -1
- package/dist/rsc/vite-plugin-nitro.d.ts +20 -7
- package/dist/rsc/vite-plugin-nitro.d.ts.map +1 -1
- package/dist/rsc/vite-plugin-nitro.js +51 -66
- package/package.json +15 -8
package/dist/api/index.d.ts
CHANGED
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;AAWlD,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,CAmhBpE;AAED,OAAO,EAAE,OAAO,EAAE,CAAC"}
|
package/dist/api/index.js
CHANGED
|
@@ -22,12 +22,18 @@
|
|
|
22
22
|
* ```
|
|
23
23
|
*/
|
|
24
24
|
import { _withAfterNodeMiddleware } from "@farm.js/core/after";
|
|
25
|
+
import { _runWithAPIRequestRuntime, createFarmRequestBodyErrorResponse, readNodeRequestBody, resolveFarmServerConfig, } from "@farm.js/core/internal/production-runtime";
|
|
26
|
+
import { invokeAPIRouteEndpoint } from "@farm.js/core/api/runtime";
|
|
27
|
+
import { sendWebResponse } from "@farm.js/core/server";
|
|
25
28
|
/**
|
|
26
29
|
* Farm.js API Routes Plugin
|
|
27
30
|
*/
|
|
28
31
|
export default function farmApi(options = {}) {
|
|
29
32
|
const srcDir = options.srcDir ?? "src";
|
|
30
33
|
const debug = options.debug ?? false;
|
|
34
|
+
const bodySizeLimit = resolveFarmServerConfig({
|
|
35
|
+
bodySizeLimit: options.bodySizeLimit,
|
|
36
|
+
}).bodySizeLimit;
|
|
31
37
|
// API routes cache
|
|
32
38
|
let apiRoutesCache = new Map();
|
|
33
39
|
let apiRouterHandler = null;
|
|
@@ -98,82 +104,7 @@ export default function farmApi(options = {}) {
|
|
|
98
104
|
});
|
|
99
105
|
}
|
|
100
106
|
try {
|
|
101
|
-
|
|
102
|
-
const queryObj = {};
|
|
103
|
-
url.searchParams.forEach((value, key) => {
|
|
104
|
-
queryObj[key] = value;
|
|
105
|
-
});
|
|
106
|
-
// Parse body for non-GET requests
|
|
107
|
-
let bodyObj = {};
|
|
108
|
-
if (method !== "GET" && method !== "HEAD") {
|
|
109
|
-
try {
|
|
110
|
-
const bodyText = await request.text();
|
|
111
|
-
if (bodyText) {
|
|
112
|
-
bodyObj = JSON.parse(bodyText);
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
catch {
|
|
116
|
-
// Body parsing failed, use empty object
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
// Parse headers
|
|
120
|
-
const headersObj = {};
|
|
121
|
-
request.headers.forEach((value, key) => {
|
|
122
|
-
headersObj[key] = value;
|
|
123
|
-
});
|
|
124
|
-
// Validate with Zod if schemas are defined
|
|
125
|
-
const types = endpoint.__types || {};
|
|
126
|
-
let validatedQuery = queryObj;
|
|
127
|
-
let validatedBody = bodyObj;
|
|
128
|
-
if (types.query && typeof types.query.parse === "function") {
|
|
129
|
-
try {
|
|
130
|
-
validatedQuery = types.query.parse(queryObj);
|
|
131
|
-
}
|
|
132
|
-
catch (e) {
|
|
133
|
-
return new Response(JSON.stringify({
|
|
134
|
-
error: "Invalid query parameters",
|
|
135
|
-
details: e.errors || e.message,
|
|
136
|
-
}), {
|
|
137
|
-
status: 400,
|
|
138
|
-
headers: { "Content-Type": "application/json" },
|
|
139
|
-
});
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
if (types.body && typeof types.body.parse === "function") {
|
|
143
|
-
try {
|
|
144
|
-
validatedBody = types.body.parse(bodyObj);
|
|
145
|
-
}
|
|
146
|
-
catch (e) {
|
|
147
|
-
return new Response(JSON.stringify({
|
|
148
|
-
error: "Invalid request body",
|
|
149
|
-
details: e.errors || e.message,
|
|
150
|
-
}), {
|
|
151
|
-
status: 400,
|
|
152
|
-
headers: { "Content-Type": "application/json" },
|
|
153
|
-
});
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
// Build context object
|
|
157
|
-
const ctx = {
|
|
158
|
-
query: validatedQuery,
|
|
159
|
-
body: validatedBody,
|
|
160
|
-
headers: headersObj,
|
|
161
|
-
request,
|
|
162
|
-
context: {},
|
|
163
|
-
params: {},
|
|
164
|
-
};
|
|
165
|
-
// Call the handler (use __handler if available, otherwise the endpoint itself)
|
|
166
|
-
const handlerFn = endpoint.__handler || endpoint;
|
|
167
|
-
const result = await handlerFn(ctx);
|
|
168
|
-
// If result is already a Response, return it
|
|
169
|
-
if (result instanceof Response) {
|
|
170
|
-
return result;
|
|
171
|
-
}
|
|
172
|
-
// Otherwise, serialize to JSON
|
|
173
|
-
return new Response(JSON.stringify(result), {
|
|
174
|
-
status: 200,
|
|
175
|
-
headers: { "Content-Type": "application/json" },
|
|
176
|
-
});
|
|
107
|
+
return await invokeAPIRouteEndpoint(endpoint, request);
|
|
177
108
|
}
|
|
178
109
|
catch (error) {
|
|
179
110
|
return new Response(JSON.stringify({ error: error.message || "Internal Server Error" }), {
|
|
@@ -354,7 +285,7 @@ export default function farmApi(options = {}) {
|
|
|
354
285
|
};
|
|
355
286
|
// Add middleware to handle API requests
|
|
356
287
|
return () => {
|
|
357
|
-
|
|
288
|
+
const apiMiddleware = _withAfterNodeMiddleware(async (req, res, next) => {
|
|
358
289
|
const url = req.url || "/";
|
|
359
290
|
const pathname = url.split("?")[0];
|
|
360
291
|
const method = req.method || "GET";
|
|
@@ -394,33 +325,26 @@ export default function farmApi(options = {}) {
|
|
|
394
325
|
// Get body for non-GET requests
|
|
395
326
|
let body;
|
|
396
327
|
if (method !== "GET" && method !== "HEAD") {
|
|
397
|
-
body = await
|
|
398
|
-
let data = "";
|
|
399
|
-
req.on("data", (chunk) => {
|
|
400
|
-
data += chunk;
|
|
401
|
-
});
|
|
402
|
-
req.on("end", () => {
|
|
403
|
-
resolve(data);
|
|
404
|
-
});
|
|
405
|
-
});
|
|
328
|
+
body = await readNodeRequestBody(req, bodySizeLimit);
|
|
406
329
|
}
|
|
407
330
|
const request = new Request(fullUrl, {
|
|
408
331
|
method,
|
|
409
332
|
headers,
|
|
410
|
-
body: body
|
|
333
|
+
body: body
|
|
334
|
+
? body.buffer.slice(body.byteOffset, body.byteOffset + body.byteLength)
|
|
335
|
+
: undefined,
|
|
411
336
|
});
|
|
412
337
|
const response = await apiRouterHandler(request);
|
|
413
338
|
const duration = Date.now() - startTime;
|
|
414
339
|
logResponse(method, pathname, response.status, duration);
|
|
415
|
-
|
|
416
|
-
res.statusCode = response.status;
|
|
417
|
-
response.headers.forEach((value, key) => {
|
|
418
|
-
res.setHeader(key, value);
|
|
419
|
-
});
|
|
420
|
-
const responseBody = await response.text();
|
|
421
|
-
res.end(responseBody);
|
|
340
|
+
await sendWebResponse(res, response);
|
|
422
341
|
}
|
|
423
342
|
catch (error) {
|
|
343
|
+
const bodyErrorResponse = createFarmRequestBodyErrorResponse(error);
|
|
344
|
+
if (bodyErrorResponse) {
|
|
345
|
+
await sendWebResponse(res, bodyErrorResponse);
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
424
348
|
const duration = Date.now() - startTime;
|
|
425
349
|
logResponse(method, pathname, 500, duration);
|
|
426
350
|
console.error("[FARM] API error:", error);
|
|
@@ -428,7 +352,13 @@ export default function farmApi(options = {}) {
|
|
|
428
352
|
res.setHeader("Content-Type", "application/json");
|
|
429
353
|
res.end(JSON.stringify({ error: "Internal server error" }));
|
|
430
354
|
}
|
|
431
|
-
})
|
|
355
|
+
});
|
|
356
|
+
server.middlewares.use((req, res, next) => _runWithAPIRequestRuntime({
|
|
357
|
+
basePath: "/api",
|
|
358
|
+
dispatch: async (request) => apiRouterHandler
|
|
359
|
+
? apiRouterHandler(request)
|
|
360
|
+
: Response.json({ error: "Not Found" }, { status: 404 }),
|
|
361
|
+
}, () => apiMiddleware(req, res, next)));
|
|
432
362
|
};
|
|
433
363
|
},
|
|
434
364
|
async handleHotUpdate({ file, server, modules }) {
|
|
@@ -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,mBAAmB,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
|
*/
|
|
@@ -127,6 +83,21 @@ function createContext(req, res, viteServer) {
|
|
|
127
83
|
const data = new Map();
|
|
128
84
|
const cookies = createCookieJar(req, res);
|
|
129
85
|
let handled = false;
|
|
86
|
+
// Mirror @farm.js/core's createContext: a header set on ctx.headers in the
|
|
87
|
+
// same handler that then short-circuits via a helper must still reach the
|
|
88
|
+
// response. The runner's tail flush is unreachable once a helper sets
|
|
89
|
+
// _handled, so each helper applies ctx.headers to res before its writeHead.
|
|
90
|
+
const applyResponseHeaders = () => {
|
|
91
|
+
for (const [key, value] of headers) {
|
|
92
|
+
try {
|
|
93
|
+
res.setHeader(key, value);
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
// An invalid optional header must not prevent the response itself
|
|
97
|
+
// from completing.
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
};
|
|
130
101
|
const ctx = {
|
|
131
102
|
request: req,
|
|
132
103
|
response: res,
|
|
@@ -153,6 +124,7 @@ function createContext(req, res, viteServer) {
|
|
|
153
124
|
ctx._redirectUrl = redirectUrl;
|
|
154
125
|
ctx._handled = true;
|
|
155
126
|
handled = true;
|
|
127
|
+
applyResponseHeaders();
|
|
156
128
|
res.writeHead(status, {
|
|
157
129
|
Location: redirectUrl,
|
|
158
130
|
"Content-Type": "text/plain",
|
|
@@ -176,6 +148,7 @@ function createContext(req, res, viteServer) {
|
|
|
176
148
|
}
|
|
177
149
|
ctx._handled = true;
|
|
178
150
|
handled = true;
|
|
151
|
+
applyResponseHeaders();
|
|
179
152
|
res.writeHead(status, {
|
|
180
153
|
"Content-Type": "application/json",
|
|
181
154
|
});
|
|
@@ -188,6 +161,7 @@ function createContext(req, res, viteServer) {
|
|
|
188
161
|
}
|
|
189
162
|
ctx._handled = true;
|
|
190
163
|
handled = true;
|
|
164
|
+
applyResponseHeaders();
|
|
191
165
|
res.writeHead(status, {
|
|
192
166
|
"Content-Type": "text/plain",
|
|
193
167
|
});
|
|
@@ -200,6 +174,7 @@ function createContext(req, res, viteServer) {
|
|
|
200
174
|
}
|
|
201
175
|
ctx._handled = true;
|
|
202
176
|
handled = true;
|
|
177
|
+
applyResponseHeaders();
|
|
203
178
|
res.writeHead(status, {
|
|
204
179
|
"Content-Type": "text/html",
|
|
205
180
|
});
|
|
@@ -293,11 +268,7 @@ export default function farmMiddleware(options = {}) {
|
|
|
293
268
|
}
|
|
294
269
|
// Find applicable middleware (cascading from root to specific)
|
|
295
270
|
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
|
-
})
|
|
271
|
+
.filter((mw) => middlewareMatchesPath(pathname, mw.path))
|
|
301
272
|
.sort((a, b) => a.path.split("/").length - b.path.split("/").length);
|
|
302
273
|
if (applicable.length === 0)
|
|
303
274
|
return false;
|
|
@@ -384,11 +355,15 @@ export default function farmMiddleware(options = {}) {
|
|
|
384
355
|
server.middlewares.use(_withAfterNodeMiddleware(async (req, res, next) => {
|
|
385
356
|
const url = req.url || "/";
|
|
386
357
|
const pathname = url.split("?")[0];
|
|
387
|
-
// Skip Vite internal requests
|
|
358
|
+
// Skip Vite internal requests and real static assets. Dotted
|
|
359
|
+
// paths that don't map to a file on disk are app routes (route
|
|
360
|
+
// segments may contain dots) and must still run middleware.
|
|
388
361
|
if (pathname.startsWith("/@") ||
|
|
389
362
|
pathname.startsWith("/__") ||
|
|
390
363
|
pathname.startsWith("/node_modules") ||
|
|
391
|
-
(pathname.includes(".") &&
|
|
364
|
+
(pathname.includes(".") &&
|
|
365
|
+
!pathname.endsWith("/") &&
|
|
366
|
+
devServableFileExists(pathname, [server.config.publicDir, server.config.root]))) {
|
|
392
367
|
return next();
|
|
393
368
|
}
|
|
394
369
|
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"}
|