@farm.js/plugin 0.1.0-beta.10 → 0.1.0-beta.101

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 (70) hide show
  1. package/dist/api/index.d.ts +44 -0
  2. package/dist/api/index.d.ts.map +1 -0
  3. package/dist/api/index.js +497 -0
  4. package/dist/context/index.d.ts +61 -0
  5. package/dist/context/index.d.ts.map +1 -0
  6. package/dist/context/index.js +75 -0
  7. package/dist/dev-static.d.ts +8 -0
  8. package/dist/dev-static.d.ts.map +1 -0
  9. package/dist/dev-static.js +37 -0
  10. package/dist/index.d.ts +43 -0
  11. package/dist/index.d.ts.map +1 -0
  12. package/dist/index.js +49 -0
  13. package/dist/middleware/index.d.ts +97 -0
  14. package/dist/middleware/index.d.ts.map +1 -0
  15. package/dist/middleware/index.js +444 -0
  16. package/dist/middleware/matching.d.ts +29 -0
  17. package/dist/middleware/matching.d.ts.map +1 -0
  18. package/dist/middleware/matching.js +79 -0
  19. package/dist/observability/index.d.ts +190 -0
  20. package/dist/observability/index.d.ts.map +1 -0
  21. package/dist/observability/index.js +399 -0
  22. package/dist/rsc/automatic-optimized-boundary.d.ts +11 -0
  23. package/dist/rsc/automatic-optimized-boundary.d.ts.map +1 -0
  24. package/dist/rsc/automatic-optimized-boundary.js +162 -0
  25. package/dist/rsc/build-paths.d.ts +3 -0
  26. package/dist/rsc/build-paths.d.ts.map +1 -0
  27. package/dist/rsc/build-paths.js +8 -0
  28. package/dist/rsc/compatibility.d.ts +8 -0
  29. package/dist/rsc/compatibility.d.ts.map +1 -0
  30. package/dist/rsc/compatibility.js +46 -0
  31. package/dist/rsc/dev-response.d.ts +3 -0
  32. package/dist/rsc/dev-response.d.ts.map +1 -0
  33. package/dist/rsc/dev-response.js +10 -0
  34. package/dist/rsc/entries/client.d.ts +15 -0
  35. package/dist/rsc/entries/client.d.ts.map +1 -0
  36. package/dist/rsc/entries/client.js +320 -0
  37. package/dist/rsc/entries/error-fallback.d.ts +3 -0
  38. package/dist/rsc/entries/error-fallback.d.ts.map +1 -0
  39. package/dist/rsc/entries/error-fallback.js +12 -0
  40. package/dist/rsc/entries/rsc.d.ts +13 -0
  41. package/dist/rsc/entries/rsc.d.ts.map +1 -0
  42. package/dist/rsc/entries/rsc.js +930 -0
  43. package/dist/rsc/entries/ssr.d.ts +13 -0
  44. package/dist/rsc/entries/ssr.d.ts.map +1 -0
  45. package/dist/rsc/entries/ssr.js +215 -0
  46. package/dist/rsc/index.d.ts +91 -0
  47. package/dist/rsc/index.d.ts.map +1 -0
  48. package/dist/rsc/index.js +1466 -0
  49. package/dist/rsc/nitro-build.d.ts +36 -0
  50. package/dist/rsc/nitro-build.d.ts.map +1 -0
  51. package/dist/rsc/nitro-build.js +416 -0
  52. package/dist/rsc/optimized-boundary.d.ts +29 -0
  53. package/dist/rsc/optimized-boundary.d.ts.map +1 -0
  54. package/dist/rsc/optimized-boundary.js +243 -0
  55. package/dist/rsc/route-action-transform.d.ts +11 -0
  56. package/dist/rsc/route-action-transform.d.ts.map +1 -0
  57. package/dist/rsc/route-action-transform.js +369 -0
  58. package/dist/rsc/server-fn-transform.d.ts +6 -0
  59. package/dist/rsc/server-fn-transform.d.ts.map +1 -0
  60. package/dist/rsc/server-fn-transform.js +152 -0
  61. package/dist/rsc/test-fixture-dependencies.d.ts +2 -0
  62. package/dist/rsc/test-fixture-dependencies.d.ts.map +1 -0
  63. package/dist/rsc/test-fixture-dependencies.js +23 -0
  64. package/dist/rsc/types.d.ts +132 -0
  65. package/dist/rsc/types.d.ts.map +1 -0
  66. package/dist/rsc/types.js +1 -0
  67. package/dist/rsc/vite-plugin-nitro.d.ts +46 -0
  68. package/dist/rsc/vite-plugin-nitro.d.ts.map +1 -0
  69. package/dist/rsc/vite-plugin-nitro.js +148 -0
  70. package/package.json +4 -4
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Farm.js API Routes Plugin
3
+ *
4
+ * Standalone API route support that works with or without RSC.
5
+ *
6
+ * Supports two patterns:
7
+ * 1. File-based routing: /src/api/hello/route.ts -> /api/hello
8
+ * The path is auto-inferred from file location
9
+ *
10
+ * 2. Root routes.ts file: /src/routes.ts with explicit paths
11
+ * createEndpoint('/api/custom', { method: 'GET' }, handler)
12
+ *
13
+ * Uses better-call's createRouter for proper Zod validation and parsing.
14
+ *
15
+ * @example
16
+ * ```ts
17
+ * import { farmApi } from '@farm.js/plugin/api'
18
+ *
19
+ * export default defineConfig({
20
+ * plugins: [farmApi({ srcDir: 'src' })],
21
+ * })
22
+ * ```
23
+ */
24
+ import type { Plugin } from "vite";
25
+ export interface FarmApiOptions {
26
+ /** Source directory containing the api folder (default: 'src') */
27
+ srcDir?: string;
28
+ /** Enable debug logging */
29
+ debug?: boolean;
30
+ /** Maximum request body size, for example `"10mb"`. */
31
+ bodySizeLimit?: number | string;
32
+ }
33
+ export interface ApiRoute {
34
+ path: string;
35
+ filePath: string;
36
+ methods: string[];
37
+ endpoints: Record<string, any>;
38
+ }
39
+ /**
40
+ * Farm.js API Routes Plugin
41
+ */
42
+ export default function farmApi(options?: FarmApiOptions): Plugin;
43
+ export { farmApi };
44
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +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;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"}
@@ -0,0 +1,497 @@
1
+ /**
2
+ * Farm.js API Routes Plugin
3
+ *
4
+ * Standalone API route support that works with or without RSC.
5
+ *
6
+ * Supports two patterns:
7
+ * 1. File-based routing: /src/api/hello/route.ts -> /api/hello
8
+ * The path is auto-inferred from file location
9
+ *
10
+ * 2. Root routes.ts file: /src/routes.ts with explicit paths
11
+ * createEndpoint('/api/custom', { method: 'GET' }, handler)
12
+ *
13
+ * Uses better-call's createRouter for proper Zod validation and parsing.
14
+ *
15
+ * @example
16
+ * ```ts
17
+ * import { farmApi } from '@farm.js/plugin/api'
18
+ *
19
+ * export default defineConfig({
20
+ * plugins: [farmApi({ srcDir: 'src' })],
21
+ * })
22
+ * ```
23
+ */
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";
28
+ /**
29
+ * Farm.js API Routes Plugin
30
+ */
31
+ export default function farmApi(options = {}) {
32
+ const srcDir = options.srcDir ?? "src";
33
+ const debug = options.debug ?? false;
34
+ const bodySizeLimit = resolveFarmServerConfig({
35
+ bodySizeLimit: options.bodySizeLimit,
36
+ }).bodySizeLimit;
37
+ // API routes cache
38
+ let apiRoutesCache = new Map();
39
+ let apiRouterHandler = null;
40
+ let discoveryComplete = false;
41
+ let discoveryPromise = null;
42
+ let currentServer = null;
43
+ // Debug-only logging for discovery messages with [FARM] prefix
44
+ const log = (message) => {
45
+ if (!debug)
46
+ return;
47
+ try {
48
+ const pc = require("picocolors");
49
+ console.log(pc.dim("[") + pc.bold(pc.blue("FARM")) + pc.dim("]") + " " + pc.gray(message));
50
+ }
51
+ catch {
52
+ console.log(`[FARM] ${message}`);
53
+ }
54
+ };
55
+ // Always log API request/response with [FARM] [API] [METHOD] format
56
+ const logResponse = (method, urlPath, status, duration) => {
57
+ try {
58
+ const pc = require("picocolors");
59
+ let statusColor = pc.green;
60
+ if (status >= 500)
61
+ statusColor = pc.red;
62
+ else if (status >= 400)
63
+ statusColor = pc.yellow;
64
+ else if (status >= 300)
65
+ statusColor = pc.cyan;
66
+ const log = [
67
+ pc.dim("[") + pc.bold(pc.blue("FARM")) + pc.dim("]"),
68
+ pc.dim("[") + pc.bold(pc.cyan("API")) + pc.dim("]"),
69
+ pc.dim("[") + pc.bold(pc.white(method.padEnd(3))) + pc.dim("]"),
70
+ pc.gray(urlPath),
71
+ pc.dim("-"),
72
+ statusColor(status.toString()),
73
+ pc.dim(`(${duration}ms)`),
74
+ ].join(" ");
75
+ console.log(log);
76
+ }
77
+ catch {
78
+ console.log(`[FARM] [API] [${method}] ${urlPath} - ${status} (${duration}ms)`);
79
+ }
80
+ };
81
+ // Create API router handler
82
+ // We use direct invocation for better control over path matching
83
+ const createBetterCallRouter = async () => {
84
+ const totalEndpoints = Array.from(apiRoutesCache.values()).reduce((sum, route) => sum + route.methods.length, 0);
85
+ if (totalEndpoints > 0) {
86
+ // Create a handler that matches routes and invokes endpoints
87
+ apiRouterHandler = async (request) => {
88
+ const url = new URL(request.url);
89
+ const method = request.method;
90
+ const pathname = url.pathname;
91
+ // Find matching route
92
+ const route = apiRoutesCache.get(pathname);
93
+ if (!route) {
94
+ return new Response(JSON.stringify({ error: "Not Found" }), {
95
+ status: 404,
96
+ headers: { "Content-Type": "application/json" },
97
+ });
98
+ }
99
+ const endpoint = route.endpoints[method];
100
+ if (!endpoint) {
101
+ return new Response(JSON.stringify({ error: "Method Not Allowed" }), {
102
+ status: 405,
103
+ headers: { "Content-Type": "application/json" },
104
+ });
105
+ }
106
+ try {
107
+ return await invokeAPIRouteEndpoint(endpoint, request);
108
+ }
109
+ catch (error) {
110
+ return new Response(JSON.stringify({ error: error.message || "Internal Server Error" }), {
111
+ status: 500,
112
+ headers: { "Content-Type": "application/json" },
113
+ });
114
+ }
115
+ };
116
+ log(`better-call router created with ${totalEndpoints} endpoints`);
117
+ }
118
+ };
119
+ // Fallback handler when better-call is not available
120
+ const createFallbackHandler = () => {
121
+ return async (request) => {
122
+ const url = new URL(request.url);
123
+ const method = request.method;
124
+ // Find matching route
125
+ const route = apiRoutesCache.get(url.pathname);
126
+ if (!route) {
127
+ return new Response(JSON.stringify({ error: "Not Found" }), {
128
+ status: 404,
129
+ headers: { "Content-Type": "application/json" },
130
+ });
131
+ }
132
+ const endpoint = route.endpoints[method];
133
+ if (!endpoint) {
134
+ return new Response(JSON.stringify({ error: "Method Not Allowed" }), {
135
+ status: 405,
136
+ headers: { "Content-Type": "application/json" },
137
+ });
138
+ }
139
+ try {
140
+ // For fallback, call the endpoint directly (without Zod parsing)
141
+ const result = await endpoint(request);
142
+ if (result instanceof Response) {
143
+ return result;
144
+ }
145
+ return new Response(JSON.stringify(result), {
146
+ status: 200,
147
+ headers: { "Content-Type": "application/json" },
148
+ });
149
+ }
150
+ catch (error) {
151
+ return new Response(JSON.stringify({ error: error.message || "Internal Server Error" }), {
152
+ status: 500,
153
+ headers: { "Content-Type": "application/json" },
154
+ });
155
+ }
156
+ };
157
+ };
158
+ return {
159
+ name: "@farm.js/plugin/api",
160
+ enforce: "pre",
161
+ configureServer(server) {
162
+ currentServer = server;
163
+ // Discover API routes
164
+ const discoverAPIRoutes = async (apiDir) => {
165
+ const fs = await import("fs");
166
+ const path = await import("path");
167
+ if (!fs.existsSync(apiDir)) {
168
+ log("No api directory found");
169
+ return;
170
+ }
171
+ const findRouteFiles = (dir) => {
172
+ const files = [];
173
+ if (!fs.existsSync(dir))
174
+ return files;
175
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
176
+ for (const entry of entries) {
177
+ const fullPath = path.join(dir, entry.name);
178
+ if (entry.isDirectory()) {
179
+ files.push(...findRouteFiles(fullPath));
180
+ }
181
+ else if (entry.name === "route.ts" ||
182
+ entry.name === "route.tsx" ||
183
+ entry.name === "route.js") {
184
+ files.push(fullPath);
185
+ }
186
+ }
187
+ return files;
188
+ };
189
+ const routeFiles = findRouteFiles(apiDir);
190
+ // Clear cache for rediscovery
191
+ apiRoutesCache.clear();
192
+ for (const filePath of routeFiles) {
193
+ try {
194
+ const relativePath = path.relative(apiDir, path.dirname(filePath));
195
+ const routePath = "/api/" + (relativePath === "." ? "" : relativePath.replace(/\\/g, "/"));
196
+ const routeModule = await server.ssrLoadModule(filePath);
197
+ const methods = ["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"];
198
+ const endpoints = {};
199
+ const availableMethods = [];
200
+ for (const method of methods) {
201
+ if (routeModule[method]) {
202
+ availableMethods.push(method);
203
+ endpoints[method] = routeModule[method];
204
+ }
205
+ }
206
+ if (availableMethods.length > 0) {
207
+ apiRoutesCache.set(routePath, {
208
+ path: routePath,
209
+ filePath,
210
+ methods: availableMethods,
211
+ endpoints,
212
+ });
213
+ log(`API route discovered: ${availableMethods.join(", ")} ${routePath}`);
214
+ }
215
+ }
216
+ catch (e) {
217
+ log(`API route load failed at ${filePath}: ${e.message}`);
218
+ }
219
+ }
220
+ // Router is created after all routes are discovered (including root routes.ts)
221
+ };
222
+ // Discover routes from root routes.ts file
223
+ const discoverRootRoutes = async () => {
224
+ const fs = await import("fs");
225
+ const path = await import("path");
226
+ const routesFiles = [
227
+ path.join(server.config.root, srcDir, "routes.ts"),
228
+ path.join(server.config.root, srcDir, "routes.tsx"),
229
+ path.join(server.config.root, srcDir, "routes.js"),
230
+ ];
231
+ for (const routesFile of routesFiles) {
232
+ if (fs.existsSync(routesFile)) {
233
+ try {
234
+ const routesModule = await server.ssrLoadModule(routesFile);
235
+ // Look for exported endpoints with explicit paths
236
+ for (const [exportName, exportValue] of Object.entries(routesModule)) {
237
+ // Check both object and function exports (better-call returns functions)
238
+ const endpoint = exportValue;
239
+ if (endpoint && endpoint.__path) {
240
+ const routePath = endpoint.__path;
241
+ const method = endpoint.__method || "GET";
242
+ // Add to cache
243
+ const existing = apiRoutesCache.get(routePath);
244
+ if (existing) {
245
+ existing.methods.push(method);
246
+ existing.endpoints[method] = endpoint;
247
+ }
248
+ else {
249
+ apiRoutesCache.set(routePath, {
250
+ path: routePath,
251
+ filePath: routesFile,
252
+ methods: [method],
253
+ endpoints: { [method]: endpoint },
254
+ });
255
+ }
256
+ // Root route discovery log omitted
257
+ }
258
+ }
259
+ }
260
+ catch (e) {
261
+ log(`Root routes.ts load failed: ${e.message}`);
262
+ }
263
+ break; // Only load the first routes file found
264
+ }
265
+ }
266
+ };
267
+ // Initialize discovery
268
+ const initializeDiscovery = async () => {
269
+ const path = await import("path");
270
+ const apiDir = path.join(server.config.root, srcDir, "api");
271
+ await discoverAPIRoutes(apiDir);
272
+ await discoverRootRoutes();
273
+ await createBetterCallRouter();
274
+ discoveryComplete = true;
275
+ };
276
+ discoveryPromise = initializeDiscovery().catch((e) => {
277
+ console.error("[FARM] API discovery error:", e);
278
+ });
279
+ // Expose API router for other plugins
280
+ server.__farmApi__ = {
281
+ getHandler: () => apiRouterHandler,
282
+ getRoutes: () => apiRoutesCache,
283
+ isReady: () => discoveryComplete,
284
+ waitForDiscovery: () => discoveryPromise,
285
+ };
286
+ // Add middleware to handle API requests
287
+ return () => {
288
+ const apiMiddleware = _withAfterNodeMiddleware(async (req, res, next) => {
289
+ const url = req.url || "/";
290
+ const pathname = url.split("?")[0];
291
+ const method = req.method || "GET";
292
+ // Only handle /api/ routes
293
+ if (!pathname.startsWith("/api/")) {
294
+ return next();
295
+ }
296
+ // Wait for discovery
297
+ if (discoveryPromise && !discoveryComplete) {
298
+ await discoveryPromise;
299
+ }
300
+ if (!apiRouterHandler) {
301
+ return next();
302
+ }
303
+ const startTime = Date.now();
304
+ try {
305
+ // Execute middleware if available
306
+ const farmMiddleware = server.__farmMiddleware__;
307
+ if (farmMiddleware) {
308
+ await farmMiddleware.waitForDiscovery?.();
309
+ const middlewareData = new Map();
310
+ const handled = await farmMiddleware.execute(req, res, pathname, middlewareData);
311
+ if (handled) {
312
+ const duration = Date.now() - startTime;
313
+ logResponse(method, pathname, res.statusCode || 200, duration);
314
+ return;
315
+ }
316
+ }
317
+ // Convert Node request to Web Request
318
+ const fullUrl = `http://${req.headers.host || "localhost:3000"}${url}`;
319
+ const headers = new Headers();
320
+ for (const [key, value] of Object.entries(req.headers)) {
321
+ if (value) {
322
+ headers.set(key, Array.isArray(value) ? value.join(", ") : value);
323
+ }
324
+ }
325
+ // Get body for non-GET requests
326
+ let body;
327
+ if (method !== "GET" && method !== "HEAD") {
328
+ body = await readNodeRequestBody(req, bodySizeLimit);
329
+ }
330
+ const request = new Request(fullUrl, {
331
+ method,
332
+ headers,
333
+ body: body
334
+ ? body.buffer.slice(body.byteOffset, body.byteOffset + body.byteLength)
335
+ : undefined,
336
+ });
337
+ const response = await apiRouterHandler(request);
338
+ const duration = Date.now() - startTime;
339
+ logResponse(method, pathname, response.status, duration);
340
+ await sendWebResponse(res, response);
341
+ }
342
+ catch (error) {
343
+ const bodyErrorResponse = createFarmRequestBodyErrorResponse(error);
344
+ if (bodyErrorResponse) {
345
+ await sendWebResponse(res, bodyErrorResponse);
346
+ return;
347
+ }
348
+ const duration = Date.now() - startTime;
349
+ logResponse(method, pathname, 500, duration);
350
+ console.error("[FARM] API error:", error);
351
+ res.statusCode = 500;
352
+ res.setHeader("Content-Type", "application/json");
353
+ res.end(JSON.stringify({ error: "Internal server error" }));
354
+ }
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)));
362
+ };
363
+ },
364
+ async handleHotUpdate({ file, server, modules }) {
365
+ const fileName = file.split("/").pop() || "";
366
+ // Handle root routes.ts updates
367
+ if (fileName === "routes.ts" || fileName === "routes.tsx" || fileName === "routes.js") {
368
+ log(`Root routes file updated: ${fileName}`);
369
+ // Invalidate all affected modules
370
+ for (const mod of modules) {
371
+ server.moduleGraph.invalidateModule(mod);
372
+ }
373
+ // Clear routes that came from this file and rediscover
374
+ for (const [routePath, route] of apiRoutesCache) {
375
+ if (route.filePath === file) {
376
+ apiRoutesCache.delete(routePath);
377
+ }
378
+ }
379
+ try {
380
+ const routesModule = await server.ssrLoadModule(file);
381
+ // Look for exported endpoints with explicit paths
382
+ // Endpoints can be functions (from better-call) or objects
383
+ for (const [exportName, exportValue] of Object.entries(routesModule)) {
384
+ const endpoint = exportValue;
385
+ if (endpoint && endpoint.__path) {
386
+ const routePath = endpoint.__path;
387
+ const method = endpoint.__method || "GET";
388
+ // Add to cache
389
+ const existing = apiRoutesCache.get(routePath);
390
+ if (existing) {
391
+ existing.methods.push(method);
392
+ existing.endpoints[method] = endpoint;
393
+ }
394
+ else {
395
+ apiRoutesCache.set(routePath, {
396
+ path: routePath,
397
+ filePath: file,
398
+ methods: [method],
399
+ endpoints: { [method]: endpoint },
400
+ });
401
+ }
402
+ log(`Root route reloaded: ${method} ${routePath}`);
403
+ }
404
+ }
405
+ // Recreate better-call router
406
+ await createBetterCallRouter();
407
+ log(`better-call router recreated`);
408
+ }
409
+ catch (e) {
410
+ log(`Root routes.ts HMR failed: ${e.message}`);
411
+ }
412
+ return [];
413
+ }
414
+ if (file.includes("/api/") && fileName.startsWith("route.")) {
415
+ const shortPath = file.split("/api/")[1] || file;
416
+ log(`API route updated: ${shortPath}`);
417
+ // Invalidate all affected modules
418
+ for (const mod of modules) {
419
+ server.moduleGraph.invalidateModule(mod);
420
+ }
421
+ // Find the route path for this file
422
+ let routePathToUpdate = null;
423
+ for (const [routePath, route] of apiRoutesCache) {
424
+ if (route.filePath === file) {
425
+ routePathToUpdate = routePath;
426
+ break;
427
+ }
428
+ }
429
+ if (routePathToUpdate) {
430
+ try {
431
+ // Reload the module
432
+ const routeModule = await server.ssrLoadModule(file);
433
+ const methods = ["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"];
434
+ const endpoints = {};
435
+ const availableMethods = [];
436
+ for (const method of methods) {
437
+ if (routeModule[method]) {
438
+ availableMethods.push(method);
439
+ endpoints[method] = routeModule[method];
440
+ }
441
+ }
442
+ if (availableMethods.length > 0) {
443
+ apiRoutesCache.set(routePathToUpdate, {
444
+ path: routePathToUpdate,
445
+ filePath: file,
446
+ methods: availableMethods,
447
+ endpoints,
448
+ });
449
+ log(`API route reloaded: ${routePathToUpdate}`);
450
+ // Recreate better-call router with updated endpoints
451
+ await createBetterCallRouter();
452
+ log(`better-call router recreated`);
453
+ }
454
+ }
455
+ catch (e) {
456
+ log(`API route HMR failed: ${e.message}`);
457
+ }
458
+ }
459
+ else {
460
+ // New route file - discover it
461
+ try {
462
+ const path = await import("path");
463
+ const apiDir = path.join(server.config.root, srcDir, "api");
464
+ const relativePath = path.relative(apiDir, path.dirname(file));
465
+ const routePath = "/api/" + (relativePath === "." ? "" : relativePath.replace(/\\/g, "/"));
466
+ const routeModule = await server.ssrLoadModule(file);
467
+ const methods = ["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"];
468
+ const endpoints = {};
469
+ const availableMethods = [];
470
+ for (const method of methods) {
471
+ if (routeModule[method]) {
472
+ availableMethods.push(method);
473
+ endpoints[method] = routeModule[method];
474
+ }
475
+ }
476
+ if (availableMethods.length > 0) {
477
+ apiRoutesCache.set(routePath, {
478
+ path: routePath,
479
+ filePath: file,
480
+ methods: availableMethods,
481
+ endpoints,
482
+ });
483
+ log(`New API route discovered: ${routePath}`);
484
+ // Recreate better-call router with new endpoint
485
+ await createBetterCallRouter();
486
+ }
487
+ }
488
+ catch (e) {
489
+ log(`New API route load failed: ${e.message}`);
490
+ }
491
+ }
492
+ return [];
493
+ }
494
+ },
495
+ };
496
+ }
497
+ export { farmApi };
@@ -0,0 +1,61 @@
1
+ interface RequestLike {
2
+ url?: string;
3
+ headers: Record<string, string | string[] | undefined>;
4
+ }
5
+ interface PluginContextLike {
6
+ req: {
7
+ set: (key: string, value: any, options?: {
8
+ exposeToPage?: boolean;
9
+ }) => void;
10
+ get: <T = any>(key: string) => T | undefined;
11
+ };
12
+ }
13
+ interface ContextPluginLike {
14
+ name: string;
15
+ enforce?: "pre" | "post";
16
+ beforeRequest?: (req: RequestLike, res: unknown, context: PluginContextLike) => void | Promise<void>;
17
+ afterResponse?: (req: RequestLike, res: {
18
+ statusCode?: number;
19
+ } | unknown, context: PluginContextLike) => void | Promise<void>;
20
+ }
21
+ export interface ContextPluginOptions {
22
+ requestIdHeader?: string;
23
+ localeHeader?: string;
24
+ exposePathname?: boolean;
25
+ beforeRequest?: (payload: {
26
+ requestId: string;
27
+ locale: string;
28
+ pathname: string;
29
+ }) => void;
30
+ afterResponse?: (payload: {
31
+ requestId: string;
32
+ locale: string;
33
+ pathname: string;
34
+ statusCode: number;
35
+ durationMs: number;
36
+ }) => void;
37
+ }
38
+ /**
39
+ * Demo plugin that exposes request context to server page props as `props.context`.
40
+ *
41
+ * @example
42
+ * ```ts
43
+ * import { contextPlugin } from "@farm.js/plugin/context";
44
+ *
45
+ * export default defineConfig({
46
+ * plugins: [contextPlugin()],
47
+ * });
48
+ * ```
49
+ *
50
+ * @example
51
+ * ```tsx
52
+ * export default function Page(props: any) {
53
+ * const requestId = props.context?.data.get("requestId");
54
+ * const locale = props.context?.data.get("locale");
55
+ * return <div>{requestId} - {locale}</div>;
56
+ * }
57
+ * ```
58
+ */
59
+ export declare function contextPlugin(options?: ContextPluginOptions): ContextPluginLike;
60
+ export {};
61
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/context/index.ts"],"names":[],"mappings":"AAEA,UAAU,WAAW;IACnB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAC,CAAC;CACxD;AAED,UAAU,iBAAiB;IACzB,GAAG,EAAE;QACH,GAAG,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,EAAE,OAAO,CAAC,EAAE;YAAE,YAAY,CAAC,EAAE,OAAO,CAAA;SAAE,KAAK,IAAI,CAAC;QAC7E,GAAG,EAAE,CAAC,CAAC,GAAG,GAAG,EAAE,GAAG,EAAE,MAAM,KAAK,CAAC,GAAG,SAAS,CAAC;KAC9C,CAAC;CACH;AAED,UAAU,iBAAiB;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,KAAK,GAAG,MAAM,CAAC;IACzB,aAAa,CAAC,EAAE,CACd,GAAG,EAAE,WAAW,EAChB,GAAG,EAAE,OAAO,EACZ,OAAO,EAAE,iBAAiB,KACvB,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1B,aAAa,CAAC,EAAE,CACd,GAAG,EAAE,WAAW,EAChB,GAAG,EAAE;QAAE,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,EACtC,OAAO,EAAE,iBAAiB,KACvB,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3B;AAED,MAAM,WAAW,oBAAoB;IACnC,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,aAAa,CAAC,EAAE,CAAC,OAAO,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;IAC3F,aAAa,CAAC,EAAE,CAAC,OAAO,EAAE;QACxB,SAAS,EAAE,MAAM,CAAC;QAClB,MAAM,EAAE,MAAM,CAAC;QACf,QAAQ,EAAE,MAAM,CAAC;QACjB,UAAU,EAAE,MAAM,CAAC;QACnB,UAAU,EAAE,MAAM,CAAC;KACpB,KAAK,IAAI,CAAC;CACZ;AAeD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,aAAa,CAAC,OAAO,GAAE,oBAAyB,GAAG,iBAAiB,CA0CnF"}