@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.
Files changed (54) hide show
  1. package/dist/api/index.d.ts +2 -0
  2. package/dist/api/index.d.ts.map +1 -1
  3. package/dist/api/index.js +25 -95
  4. package/dist/dev-static.d.ts +8 -0
  5. package/dist/dev-static.d.ts.map +1 -0
  6. package/dist/dev-static.js +37 -0
  7. package/dist/index.d.ts +1 -1
  8. package/dist/index.d.ts.map +1 -1
  9. package/dist/index.js +1 -1
  10. package/dist/middleware/index.d.ts.map +1 -1
  11. package/dist/middleware/index.js +28 -53
  12. package/dist/middleware/matching.d.ts +29 -0
  13. package/dist/middleware/matching.d.ts.map +1 -0
  14. package/dist/middleware/matching.js +79 -0
  15. package/dist/rsc/automatic-optimized-boundary.d.ts +11 -0
  16. package/dist/rsc/automatic-optimized-boundary.d.ts.map +1 -0
  17. package/dist/rsc/automatic-optimized-boundary.js +162 -0
  18. package/dist/rsc/compatibility.d.ts +8 -0
  19. package/dist/rsc/compatibility.d.ts.map +1 -0
  20. package/dist/rsc/compatibility.js +46 -0
  21. package/dist/rsc/dev-response.d.ts +3 -0
  22. package/dist/rsc/dev-response.d.ts.map +1 -0
  23. package/dist/rsc/dev-response.js +10 -0
  24. package/dist/rsc/entries/client.d.ts +1 -0
  25. package/dist/rsc/entries/client.d.ts.map +1 -1
  26. package/dist/rsc/entries/client.js +42 -5
  27. package/dist/rsc/entries/error-fallback.d.ts +3 -0
  28. package/dist/rsc/entries/error-fallback.d.ts.map +1 -0
  29. package/dist/rsc/entries/error-fallback.js +12 -0
  30. package/dist/rsc/entries/rsc.d.ts.map +1 -1
  31. package/dist/rsc/entries/rsc.js +187 -189
  32. package/dist/rsc/entries/ssr.d.ts.map +1 -1
  33. package/dist/rsc/entries/ssr.js +44 -74
  34. package/dist/rsc/index.d.ts +15 -2
  35. package/dist/rsc/index.d.ts.map +1 -1
  36. package/dist/rsc/index.js +167 -69
  37. package/dist/rsc/nitro-build.d.ts.map +1 -1
  38. package/dist/rsc/nitro-build.js +28 -8
  39. package/dist/rsc/optimized-boundary.d.ts +10 -1
  40. package/dist/rsc/optimized-boundary.d.ts.map +1 -1
  41. package/dist/rsc/optimized-boundary.js +229 -1
  42. package/dist/rsc/route-action-transform.d.ts +11 -0
  43. package/dist/rsc/route-action-transform.d.ts.map +1 -0
  44. package/dist/rsc/route-action-transform.js +369 -0
  45. package/dist/rsc/server-fn-transform.js +1 -1
  46. package/dist/rsc/test-fixture-dependencies.d.ts +2 -0
  47. package/dist/rsc/test-fixture-dependencies.d.ts.map +1 -0
  48. package/dist/rsc/test-fixture-dependencies.js +23 -0
  49. package/dist/rsc/types.d.ts +9 -0
  50. package/dist/rsc/types.d.ts.map +1 -1
  51. package/dist/rsc/vite-plugin-nitro.d.ts +20 -7
  52. package/dist/rsc/vite-plugin-nitro.d.ts.map +1 -1
  53. package/dist/rsc/vite-plugin-nitro.js +51 -66
  54. package/package.json +15 -8
@@ -27,6 +27,8 @@ export interface FarmApiOptions {
27
27
  srcDir?: string;
28
28
  /** Enable debug logging */
29
29
  debug?: boolean;
30
+ /** Maximum request body size, for example `"10mb"`. */
31
+ bodySizeLimit?: number | string;
30
32
  }
31
33
  export interface ApiRoute {
32
34
  path: string;
@@ -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;AAGlD,MAAM,WAAW,cAAc;IAC7B,kEAAkE;IAClE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,2BAA2B;IAC3B,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;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,CAgmBpE;AAED,OAAO,EAAE,OAAO,EAAE,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
- // Parse query params
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
- server.middlewares.use(_withAfterNodeMiddleware(async (req, res, next) => {
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 new Promise((resolve) => {
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 || undefined,
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
- // Send response
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
@@ -39,5 +39,5 @@
39
39
  * })
40
40
  * ```
41
41
  */
42
- export declare const version = "0.1.0-beta.1";
42
+ export declare const version = "0.1.0-beta.100";
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,mBAAmB,CAAC"}
package/dist/index.js CHANGED
@@ -39,7 +39,7 @@
39
39
  * })
40
40
  * ```
41
41
  */
42
- export const version = "0.1.0-beta.1";
42
+ export const version = "0.1.0-beta.100";
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
  */
@@ -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(".") && !pathname.endsWith("/"))) {
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"}