@farm.js/plugin 0.1.0-beta.10 → 0.1.0-beta.13
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 +42 -0
- package/dist/api/index.d.ts.map +1 -0
- package/dist/api/index.js +493 -0
- package/dist/context/index.d.ts +61 -0
- package/dist/context/index.d.ts.map +1 -0
- package/dist/context/index.js +75 -0
- package/dist/index.d.ts +43 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +49 -0
- package/dist/middleware/index.d.ts +97 -0
- package/dist/middleware/index.d.ts.map +1 -0
- package/dist/middleware/index.js +469 -0
- package/dist/observability/index.d.ts +190 -0
- package/dist/observability/index.d.ts.map +1 -0
- package/dist/observability/index.js +399 -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/build-paths.d.ts +3 -0
- package/dist/rsc/build-paths.d.ts.map +1 -0
- package/dist/rsc/build-paths.js +8 -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/entries/client.d.ts +14 -0
- package/dist/rsc/entries/client.d.ts.map +1 -0
- package/dist/rsc/entries/client.js +283 -0
- package/dist/rsc/entries/rsc.d.ts +13 -0
- package/dist/rsc/entries/rsc.d.ts.map +1 -0
- package/dist/rsc/entries/rsc.js +932 -0
- package/dist/rsc/entries/ssr.d.ts +13 -0
- package/dist/rsc/entries/ssr.d.ts.map +1 -0
- package/dist/rsc/entries/ssr.js +245 -0
- package/dist/rsc/index.d.ts +87 -0
- package/dist/rsc/index.d.ts.map +1 -0
- package/dist/rsc/index.js +1389 -0
- package/dist/rsc/nitro-build.d.ts +36 -0
- package/dist/rsc/nitro-build.d.ts.map +1 -0
- package/dist/rsc/nitro-build.js +396 -0
- package/dist/rsc/optimized-boundary.d.ts +29 -0
- package/dist/rsc/optimized-boundary.d.ts.map +1 -0
- package/dist/rsc/optimized-boundary.js +243 -0
- package/dist/rsc/server-fn-transform.d.ts +6 -0
- package/dist/rsc/server-fn-transform.d.ts.map +1 -0
- package/dist/rsc/server-fn-transform.js +152 -0
- package/dist/rsc/types.d.ts +123 -0
- package/dist/rsc/types.d.ts.map +1 -0
- package/dist/rsc/types.js +1 -0
- package/dist/rsc/vite-plugin-nitro.d.ts +33 -0
- package/dist/rsc/vite-plugin-nitro.d.ts.map +1 -0
- package/dist/rsc/vite-plugin-nitro.js +163 -0
- package/package.json +3 -3
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @farm.js/plugin - Official plugins for Farm.js framework
|
|
3
|
+
*
|
|
4
|
+
* This package provides official plugins that extend Farm.js functionality.
|
|
5
|
+
*
|
|
6
|
+
* Available plugins:
|
|
7
|
+
* - @farm.js/plugin/rsc - React Server Components support
|
|
8
|
+
* - @farm.js/plugin/middleware - Standalone middleware support
|
|
9
|
+
* - @farm.js/plugin/api - Standalone API routes support
|
|
10
|
+
* - @farm.js/plugin/observability - request/render/build observability lifecycle hooks
|
|
11
|
+
* - @farm.js/plugin/context - example request context exposure plugin
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```ts
|
|
15
|
+
* // Using RSC (includes middleware and API support)
|
|
16
|
+
* import { defineConfig } from '@farm.js/plugin/rsc'
|
|
17
|
+
*
|
|
18
|
+
* export default defineConfig({
|
|
19
|
+
* srcDir: 'src',
|
|
20
|
+
* experimental: {
|
|
21
|
+
* serverComponents: true,
|
|
22
|
+
* serverActions: true,
|
|
23
|
+
* },
|
|
24
|
+
* })
|
|
25
|
+
* ```
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```ts
|
|
29
|
+
* // Using standalone plugins (without RSC)
|
|
30
|
+
* import { defineConfig } from 'vite'
|
|
31
|
+
* import { farmMiddleware } from '@farm.js/plugin/middleware'
|
|
32
|
+
* import { farmApi } from '@farm.js/plugin/api'
|
|
33
|
+
*
|
|
34
|
+
* export default defineConfig({
|
|
35
|
+
* plugins: [
|
|
36
|
+
* farmMiddleware({ srcDir: 'src', debug: true }),
|
|
37
|
+
* farmApi({ srcDir: 'src', debug: true }),
|
|
38
|
+
* ],
|
|
39
|
+
* })
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
export declare const version = "0.1.0-beta.12";
|
|
43
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AAEH,eAAO,MAAM,OAAO,kBAAkB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @farm.js/plugin - Official plugins for Farm.js framework
|
|
3
|
+
*
|
|
4
|
+
* This package provides official plugins that extend Farm.js functionality.
|
|
5
|
+
*
|
|
6
|
+
* Available plugins:
|
|
7
|
+
* - @farm.js/plugin/rsc - React Server Components support
|
|
8
|
+
* - @farm.js/plugin/middleware - Standalone middleware support
|
|
9
|
+
* - @farm.js/plugin/api - Standalone API routes support
|
|
10
|
+
* - @farm.js/plugin/observability - request/render/build observability lifecycle hooks
|
|
11
|
+
* - @farm.js/plugin/context - example request context exposure plugin
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```ts
|
|
15
|
+
* // Using RSC (includes middleware and API support)
|
|
16
|
+
* import { defineConfig } from '@farm.js/plugin/rsc'
|
|
17
|
+
*
|
|
18
|
+
* export default defineConfig({
|
|
19
|
+
* srcDir: 'src',
|
|
20
|
+
* experimental: {
|
|
21
|
+
* serverComponents: true,
|
|
22
|
+
* serverActions: true,
|
|
23
|
+
* },
|
|
24
|
+
* })
|
|
25
|
+
* ```
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```ts
|
|
29
|
+
* // Using standalone plugins (without RSC)
|
|
30
|
+
* import { defineConfig } from 'vite'
|
|
31
|
+
* import { farmMiddleware } from '@farm.js/plugin/middleware'
|
|
32
|
+
* import { farmApi } from '@farm.js/plugin/api'
|
|
33
|
+
*
|
|
34
|
+
* export default defineConfig({
|
|
35
|
+
* plugins: [
|
|
36
|
+
* farmMiddleware({ srcDir: 'src', debug: true }),
|
|
37
|
+
* farmApi({ srcDir: 'src', debug: true }),
|
|
38
|
+
* ],
|
|
39
|
+
* })
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
export const version = "0.1.0-beta.12";
|
|
43
|
+
// Note: Import plugins from their respective subpaths:
|
|
44
|
+
// - @farm.js/plugin/rsc
|
|
45
|
+
// - @farm.js/plugin/middleware
|
|
46
|
+
// - @farm.js/plugin/api
|
|
47
|
+
// - @farm.js/plugin/observability
|
|
48
|
+
// - @farm.js/plugin/context
|
|
49
|
+
// This avoids bundling unused code
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Farm.js Middleware Plugin
|
|
3
|
+
*
|
|
4
|
+
* Standalone middleware support that works with or without RSC.
|
|
5
|
+
* Discovers and executes middleware.ts files in the source directory.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```ts
|
|
9
|
+
* import { farmMiddleware } from '@farm.js/plugin/middleware'
|
|
10
|
+
*
|
|
11
|
+
* export default defineConfig({
|
|
12
|
+
* plugins: [farmMiddleware({ srcDir: 'src' })],
|
|
13
|
+
* })
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
import type { Plugin, ViteDevServer } from "vite";
|
|
17
|
+
import type { IncomingMessage, ServerResponse } from "http";
|
|
18
|
+
export interface FarmMiddlewareOptions {
|
|
19
|
+
/** Source directory containing middleware files (default: 'src') */
|
|
20
|
+
srcDir?: string;
|
|
21
|
+
/** Enable debug logging */
|
|
22
|
+
debug?: boolean;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Cookie options
|
|
26
|
+
*/
|
|
27
|
+
export interface CookieOptions {
|
|
28
|
+
maxAge?: number;
|
|
29
|
+
expires?: Date;
|
|
30
|
+
path?: string;
|
|
31
|
+
domain?: string;
|
|
32
|
+
secure?: boolean;
|
|
33
|
+
httpOnly?: boolean;
|
|
34
|
+
sameSite?: "strict" | "lax" | "none";
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Cookie management interface
|
|
38
|
+
*/
|
|
39
|
+
export interface CookieJar {
|
|
40
|
+
get(name: string): string | undefined;
|
|
41
|
+
set(name: string, value: string, options?: CookieOptions): void;
|
|
42
|
+
delete(name: string): void;
|
|
43
|
+
getAll(): Record<string, string>;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Full middleware context - compatible with @farm.js/core/middleware
|
|
47
|
+
*/
|
|
48
|
+
export interface MiddlewareContext {
|
|
49
|
+
request: IncomingMessage;
|
|
50
|
+
response: ServerResponse;
|
|
51
|
+
url: URL;
|
|
52
|
+
pathname: string;
|
|
53
|
+
searchParams: URLSearchParams;
|
|
54
|
+
method: string;
|
|
55
|
+
params: Record<string, string>;
|
|
56
|
+
route: string;
|
|
57
|
+
parent?: {
|
|
58
|
+
data: Map<string, any>;
|
|
59
|
+
headers: Record<string, string>;
|
|
60
|
+
};
|
|
61
|
+
vite: {
|
|
62
|
+
isDev: boolean;
|
|
63
|
+
hmr: boolean;
|
|
64
|
+
server?: ViteDevServer;
|
|
65
|
+
};
|
|
66
|
+
data: Map<string, any>;
|
|
67
|
+
headers: Map<string, string>;
|
|
68
|
+
cookies: CookieJar;
|
|
69
|
+
_handled: boolean;
|
|
70
|
+
_redirectUrl?: string;
|
|
71
|
+
_rewriteUrl?: string;
|
|
72
|
+
redirect(url: string, status?: number): void;
|
|
73
|
+
rewrite(url: string): void;
|
|
74
|
+
json(data: any, status?: number): void;
|
|
75
|
+
text(content: string, status?: number): void;
|
|
76
|
+
html(content: string, status?: number): void;
|
|
77
|
+
}
|
|
78
|
+
export type MiddlewareHandler = (ctx: MiddlewareContext, next: () => Promise<void>) => Promise<void> | void;
|
|
79
|
+
export interface DiscoveredMiddleware {
|
|
80
|
+
path: string;
|
|
81
|
+
filePath: string;
|
|
82
|
+
module: MiddlewareHandler | {
|
|
83
|
+
build: () => {
|
|
84
|
+
handlers: MiddlewareHandler[];
|
|
85
|
+
};
|
|
86
|
+
setBasePath?: (path: string) => void;
|
|
87
|
+
};
|
|
88
|
+
config?: {
|
|
89
|
+
matcher?: string[];
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Farm.js Middleware Plugin
|
|
94
|
+
*/
|
|
95
|
+
export default function farmMiddleware(options?: FarmMiddlewareOptions): Plugin;
|
|
96
|
+
export { farmMiddleware };
|
|
97
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/middleware/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,469 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Farm.js Middleware Plugin
|
|
3
|
+
*
|
|
4
|
+
* Standalone middleware support that works with or without RSC.
|
|
5
|
+
* Discovers and executes middleware.ts files in the source directory.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```ts
|
|
9
|
+
* import { farmMiddleware } from '@farm.js/plugin/middleware'
|
|
10
|
+
*
|
|
11
|
+
* export default defineConfig({
|
|
12
|
+
* plugins: [farmMiddleware({ srcDir: 'src' })],
|
|
13
|
+
* })
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
import { createRequire } from "node:module";
|
|
17
|
+
import { _withAfterNodeMiddleware } from "@farm.js/core/after";
|
|
18
|
+
// Create require for ESM compatibility
|
|
19
|
+
const require_ = createRequire(import.meta.url);
|
|
20
|
+
// Get picocolors with forced color support (to handle NO_COLOR env being set)
|
|
21
|
+
const getColors = () => {
|
|
22
|
+
try {
|
|
23
|
+
const pico = require_("picocolors");
|
|
24
|
+
if (typeof pico?.createColors === "function") {
|
|
25
|
+
return pico.createColors(true);
|
|
26
|
+
}
|
|
27
|
+
if (typeof pico?.green === "function")
|
|
28
|
+
return pico;
|
|
29
|
+
}
|
|
30
|
+
catch { }
|
|
31
|
+
const id = (s) => s;
|
|
32
|
+
return {
|
|
33
|
+
bold: id,
|
|
34
|
+
dim: id,
|
|
35
|
+
cyan: id,
|
|
36
|
+
red: id,
|
|
37
|
+
yellow: id,
|
|
38
|
+
blue: id,
|
|
39
|
+
white: id,
|
|
40
|
+
gray: id,
|
|
41
|
+
green: id,
|
|
42
|
+
magenta: id,
|
|
43
|
+
};
|
|
44
|
+
};
|
|
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
|
+
/**
|
|
92
|
+
* Create a cookie jar for managing cookies
|
|
93
|
+
*/
|
|
94
|
+
function createCookieJar(req, res) {
|
|
95
|
+
const cookies = parseCookies(req.headers.cookie);
|
|
96
|
+
const setCookies = [];
|
|
97
|
+
return {
|
|
98
|
+
get(name) {
|
|
99
|
+
return cookies[name];
|
|
100
|
+
},
|
|
101
|
+
set(name, value, options = {}) {
|
|
102
|
+
cookies[name] = value;
|
|
103
|
+
const cookieString = serializeCookie(name, value, options);
|
|
104
|
+
setCookies.push(cookieString);
|
|
105
|
+
res.setHeader("Set-Cookie", setCookies);
|
|
106
|
+
},
|
|
107
|
+
delete(name) {
|
|
108
|
+
delete cookies[name];
|
|
109
|
+
const cookieString = serializeCookie(name, "", {
|
|
110
|
+
maxAge: 0,
|
|
111
|
+
expires: new Date(0),
|
|
112
|
+
});
|
|
113
|
+
setCookies.push(cookieString);
|
|
114
|
+
res.setHeader("Set-Cookie", setCookies);
|
|
115
|
+
},
|
|
116
|
+
getAll() {
|
|
117
|
+
return { ...cookies };
|
|
118
|
+
},
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Create a full middleware context
|
|
123
|
+
*/
|
|
124
|
+
function createContext(req, res, viteServer) {
|
|
125
|
+
const url = new URL(req.url || "/", `http://${req.headers.host || "localhost"}`);
|
|
126
|
+
const headers = new Map();
|
|
127
|
+
const data = new Map();
|
|
128
|
+
const cookies = createCookieJar(req, res);
|
|
129
|
+
let handled = false;
|
|
130
|
+
const ctx = {
|
|
131
|
+
request: req,
|
|
132
|
+
response: res,
|
|
133
|
+
url,
|
|
134
|
+
pathname: url.pathname,
|
|
135
|
+
searchParams: url.searchParams,
|
|
136
|
+
method: req.method || "GET",
|
|
137
|
+
params: {},
|
|
138
|
+
route: url.pathname,
|
|
139
|
+
vite: {
|
|
140
|
+
isDev: process.env.NODE_ENV !== "production",
|
|
141
|
+
hmr: !!viteServer?.hot,
|
|
142
|
+
server: viteServer,
|
|
143
|
+
},
|
|
144
|
+
data,
|
|
145
|
+
headers,
|
|
146
|
+
cookies,
|
|
147
|
+
_handled: false,
|
|
148
|
+
redirect(redirectUrl, status = 307) {
|
|
149
|
+
if (handled) {
|
|
150
|
+
console.warn("Response already sent, cannot redirect");
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
ctx._redirectUrl = redirectUrl;
|
|
154
|
+
ctx._handled = true;
|
|
155
|
+
handled = true;
|
|
156
|
+
res.writeHead(status, {
|
|
157
|
+
Location: redirectUrl,
|
|
158
|
+
"Content-Type": "text/plain",
|
|
159
|
+
});
|
|
160
|
+
res.end(`Redirecting to ${redirectUrl}`);
|
|
161
|
+
},
|
|
162
|
+
rewrite(rewriteUrl) {
|
|
163
|
+
ctx._rewriteUrl = rewriteUrl;
|
|
164
|
+
// Update the URL for downstream middleware
|
|
165
|
+
const newUrl = new URL(rewriteUrl, `http://${req.headers.host || "localhost"}`);
|
|
166
|
+
ctx.url = newUrl;
|
|
167
|
+
ctx.pathname = newUrl.pathname;
|
|
168
|
+
ctx.searchParams = newUrl.searchParams;
|
|
169
|
+
// Update the original request URL
|
|
170
|
+
req.url = rewriteUrl;
|
|
171
|
+
},
|
|
172
|
+
json(jsonData, status = 200) {
|
|
173
|
+
if (handled) {
|
|
174
|
+
console.warn("Response already sent, cannot send JSON");
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
ctx._handled = true;
|
|
178
|
+
handled = true;
|
|
179
|
+
res.writeHead(status, {
|
|
180
|
+
"Content-Type": "application/json",
|
|
181
|
+
});
|
|
182
|
+
res.end(JSON.stringify(jsonData));
|
|
183
|
+
},
|
|
184
|
+
text(content, status = 200) {
|
|
185
|
+
if (handled) {
|
|
186
|
+
console.warn("Response already sent, cannot send text");
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
ctx._handled = true;
|
|
190
|
+
handled = true;
|
|
191
|
+
res.writeHead(status, {
|
|
192
|
+
"Content-Type": "text/plain",
|
|
193
|
+
});
|
|
194
|
+
res.end(content);
|
|
195
|
+
},
|
|
196
|
+
html(content, status = 200) {
|
|
197
|
+
if (handled) {
|
|
198
|
+
console.warn("Response already sent, cannot send HTML");
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
ctx._handled = true;
|
|
202
|
+
handled = true;
|
|
203
|
+
res.writeHead(status, {
|
|
204
|
+
"Content-Type": "text/html",
|
|
205
|
+
});
|
|
206
|
+
res.end(content);
|
|
207
|
+
},
|
|
208
|
+
};
|
|
209
|
+
return ctx;
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Farm.js Middleware Plugin
|
|
213
|
+
*/
|
|
214
|
+
export default function farmMiddleware(options = {}) {
|
|
215
|
+
const srcDir = options.srcDir ?? "src";
|
|
216
|
+
const debug = options.debug ?? false;
|
|
217
|
+
// Middleware cache
|
|
218
|
+
let middlewareCache = new Map();
|
|
219
|
+
let discoveryComplete = false;
|
|
220
|
+
let discoveryPromise = null;
|
|
221
|
+
const log = (message) => {
|
|
222
|
+
if (!debug)
|
|
223
|
+
return;
|
|
224
|
+
try {
|
|
225
|
+
const pc = require("picocolors");
|
|
226
|
+
console.log(pc.dim("[") + pc.bold(pc.blue("FARM")) + pc.dim("]") + " " + pc.gray(message));
|
|
227
|
+
}
|
|
228
|
+
catch {
|
|
229
|
+
console.log(`[FARM] ${message}`);
|
|
230
|
+
}
|
|
231
|
+
};
|
|
232
|
+
return {
|
|
233
|
+
name: "@farm.js/plugin/middleware",
|
|
234
|
+
enforce: "pre",
|
|
235
|
+
configureServer(server) {
|
|
236
|
+
// Discover middleware
|
|
237
|
+
const discoverMiddleware = async (dir, routePath = "/") => {
|
|
238
|
+
const fs = await import("fs");
|
|
239
|
+
const path = await import("path");
|
|
240
|
+
if (!fs.existsSync(dir))
|
|
241
|
+
return;
|
|
242
|
+
// Check for middleware file
|
|
243
|
+
const extensions = [".ts", ".tsx", ".js", ".jsx"];
|
|
244
|
+
for (const ext of extensions) {
|
|
245
|
+
const middlewareFile = path.join(dir, `middleware${ext}`);
|
|
246
|
+
if (fs.existsSync(middlewareFile)) {
|
|
247
|
+
try {
|
|
248
|
+
const module = await server.ssrLoadModule(middlewareFile);
|
|
249
|
+
if (module.default) {
|
|
250
|
+
middlewareCache.set(routePath, {
|
|
251
|
+
path: routePath,
|
|
252
|
+
filePath: middlewareFile,
|
|
253
|
+
module: module.default,
|
|
254
|
+
config: module.config,
|
|
255
|
+
});
|
|
256
|
+
log(`Middleware discovered: ${routePath}`);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
catch (e) {
|
|
260
|
+
log(`Middleware load failed at ${routePath}: ${e.message}`);
|
|
261
|
+
}
|
|
262
|
+
break;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
// Recursively check subdirectories
|
|
266
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
267
|
+
for (const entry of entries) {
|
|
268
|
+
if (entry.isDirectory() &&
|
|
269
|
+
!entry.name.startsWith(".") &&
|
|
270
|
+
!entry.name.startsWith("_") &&
|
|
271
|
+
entry.name !== "api") {
|
|
272
|
+
const subDir = path.join(dir, entry.name);
|
|
273
|
+
const subRoutePath = routePath === "/" ? `/${entry.name}` : `${routePath}/${entry.name}`;
|
|
274
|
+
await discoverMiddleware(subDir, subRoutePath);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
};
|
|
278
|
+
// Initialize discovery
|
|
279
|
+
const initializeDiscovery = async () => {
|
|
280
|
+
const path = await import("path");
|
|
281
|
+
const appDir = path.join(server.config.root, srcDir);
|
|
282
|
+
await discoverMiddleware(appDir);
|
|
283
|
+
discoveryComplete = true;
|
|
284
|
+
};
|
|
285
|
+
discoveryPromise = initializeDiscovery().catch((e) => {
|
|
286
|
+
console.error("[FARM] Middleware discovery error:", e);
|
|
287
|
+
});
|
|
288
|
+
// Execute middleware chain
|
|
289
|
+
const executeMiddleware = async (req, res, pathname, sharedData) => {
|
|
290
|
+
// Wait for discovery
|
|
291
|
+
if (discoveryPromise && !discoveryComplete) {
|
|
292
|
+
await discoveryPromise;
|
|
293
|
+
}
|
|
294
|
+
// Find applicable middleware (cascading from root to specific)
|
|
295
|
+
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
|
+
})
|
|
301
|
+
.sort((a, b) => a.path.split("/").length - b.path.split("/").length);
|
|
302
|
+
if (applicable.length === 0)
|
|
303
|
+
return false;
|
|
304
|
+
const startTime = Date.now();
|
|
305
|
+
const method = req.method || "GET";
|
|
306
|
+
// Log middleware execution in the same format as @farm.js/core
|
|
307
|
+
const pc = getColors();
|
|
308
|
+
const logMsg = [
|
|
309
|
+
pc.dim("[") + pc.bold(pc.blue("FARM")) + pc.dim("]"),
|
|
310
|
+
pc.dim("[") + pc.bold(pc.magenta("MIDDLEWARE")) + pc.dim("]"),
|
|
311
|
+
pc.dim("[") + pc.bold(pc.white(method.padEnd(3))) + pc.dim("]"),
|
|
312
|
+
pc.gray("Executing middleware: "),
|
|
313
|
+
pc.gray(pathname),
|
|
314
|
+
pc.dim(` (${applicable.length} middleware)`),
|
|
315
|
+
].join(" ");
|
|
316
|
+
console.log(logMsg);
|
|
317
|
+
// Create full middleware context
|
|
318
|
+
const ctx = createContext(req, res, server);
|
|
319
|
+
// Use shared data if provided
|
|
320
|
+
if (sharedData) {
|
|
321
|
+
for (const [key, value] of sharedData) {
|
|
322
|
+
ctx.data.set(key, value);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
for (const mw of applicable) {
|
|
326
|
+
const middleware = mw.module;
|
|
327
|
+
// Handle middleware chain object (has .build method)
|
|
328
|
+
if (middleware && typeof middleware === "object" && "build" in middleware) {
|
|
329
|
+
if (typeof middleware.setBasePath === "function") {
|
|
330
|
+
middleware.setBasePath(mw.path);
|
|
331
|
+
}
|
|
332
|
+
const built = middleware.build();
|
|
333
|
+
const handlers = built.handlers || [];
|
|
334
|
+
let handlerIndex = 0;
|
|
335
|
+
const executeNext = async () => {
|
|
336
|
+
if (handlerIndex < handlers.length) {
|
|
337
|
+
const handler = handlers[handlerIndex++];
|
|
338
|
+
await handler(ctx, executeNext);
|
|
339
|
+
}
|
|
340
|
+
};
|
|
341
|
+
await executeNext();
|
|
342
|
+
}
|
|
343
|
+
else if (typeof middleware === "function") {
|
|
344
|
+
// Plain middleware function
|
|
345
|
+
await middleware(ctx, async () => { });
|
|
346
|
+
}
|
|
347
|
+
// Check if response was sent
|
|
348
|
+
if (ctx._handled || res.headersSent || res.writableEnded) {
|
|
349
|
+
return true;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
// Apply collected headers to response
|
|
353
|
+
for (const [key, value] of ctx.headers) {
|
|
354
|
+
try {
|
|
355
|
+
res.setHeader(key, value);
|
|
356
|
+
}
|
|
357
|
+
catch (e) { }
|
|
358
|
+
}
|
|
359
|
+
// Store middleware data on request for pages to access
|
|
360
|
+
req.__FARM_MIDDLEWARE_DATA__ = Object.fromEntries(ctx.data);
|
|
361
|
+
// Log middleware completion
|
|
362
|
+
const duration = Date.now() - startTime;
|
|
363
|
+
const pc2 = getColors();
|
|
364
|
+
const completeLogMsg = [
|
|
365
|
+
pc2.dim("[") + pc2.bold(pc2.blue("FARM")) + pc2.dim("]"),
|
|
366
|
+
pc2.dim("[") + pc2.bold(pc2.magenta("MIDDLEWARE")) + pc2.dim("]"),
|
|
367
|
+
pc2.dim("[") + pc2.bold(pc2.white(method.padEnd(3))) + pc2.dim("]"),
|
|
368
|
+
pc2.gray("Completed"),
|
|
369
|
+
pc2.gray(pathname),
|
|
370
|
+
pc2.dim(`(${duration}ms)`),
|
|
371
|
+
].join(" ");
|
|
372
|
+
console.log(completeLogMsg);
|
|
373
|
+
return false;
|
|
374
|
+
};
|
|
375
|
+
// Expose middleware execution for other plugins
|
|
376
|
+
server.__farmMiddleware__ = {
|
|
377
|
+
execute: executeMiddleware,
|
|
378
|
+
getCache: () => middlewareCache,
|
|
379
|
+
isReady: () => discoveryComplete,
|
|
380
|
+
waitForDiscovery: () => discoveryPromise,
|
|
381
|
+
};
|
|
382
|
+
// Add middleware to handle requests
|
|
383
|
+
return () => {
|
|
384
|
+
server.middlewares.use(_withAfterNodeMiddleware(async (req, res, next) => {
|
|
385
|
+
const url = req.url || "/";
|
|
386
|
+
const pathname = url.split("?")[0];
|
|
387
|
+
// Skip Vite internal requests
|
|
388
|
+
if (pathname.startsWith("/@") ||
|
|
389
|
+
pathname.startsWith("/__") ||
|
|
390
|
+
pathname.startsWith("/node_modules") ||
|
|
391
|
+
(pathname.includes(".") && !pathname.endsWith("/"))) {
|
|
392
|
+
return next();
|
|
393
|
+
}
|
|
394
|
+
try {
|
|
395
|
+
// Execute middleware
|
|
396
|
+
const middlewareData = new Map();
|
|
397
|
+
const handled = await executeMiddleware(req, res, pathname, middlewareData);
|
|
398
|
+
if (handled) {
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
catch (e) {
|
|
403
|
+
console.error("[FARM] Middleware error:", e);
|
|
404
|
+
}
|
|
405
|
+
next();
|
|
406
|
+
}));
|
|
407
|
+
};
|
|
408
|
+
},
|
|
409
|
+
async handleHotUpdate({ file, server, modules }) {
|
|
410
|
+
const fileName = file.split("/").pop() || "";
|
|
411
|
+
if (fileName.startsWith("middleware.")) {
|
|
412
|
+
log(`Middleware updated: ${fileName}`);
|
|
413
|
+
// Invalidate the module in Vite's module graph
|
|
414
|
+
for (const mod of modules) {
|
|
415
|
+
server.moduleGraph.invalidateModule(mod);
|
|
416
|
+
}
|
|
417
|
+
// Find and update the cached middleware
|
|
418
|
+
for (const [routePath, mw] of middlewareCache.entries()) {
|
|
419
|
+
if (mw.filePath === file) {
|
|
420
|
+
try {
|
|
421
|
+
// Reload the module
|
|
422
|
+
const module = await server.ssrLoadModule(file);
|
|
423
|
+
if (module.default) {
|
|
424
|
+
middlewareCache.set(routePath, {
|
|
425
|
+
path: routePath,
|
|
426
|
+
filePath: file,
|
|
427
|
+
module: module.default,
|
|
428
|
+
config: module.config,
|
|
429
|
+
});
|
|
430
|
+
log(`Middleware reloaded: ${routePath}`);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
catch (e) {
|
|
434
|
+
log(`Middleware reload failed: ${e.message}`);
|
|
435
|
+
}
|
|
436
|
+
break;
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
// Also check if this is a new middleware file not yet in cache
|
|
440
|
+
if (!Array.from(middlewareCache.values()).some((mw) => mw.filePath === file)) {
|
|
441
|
+
try {
|
|
442
|
+
const path = await import("path");
|
|
443
|
+
const srcPath = path.join(server.config.root, srcDir);
|
|
444
|
+
const relativePath = file
|
|
445
|
+
.replace(srcPath, "")
|
|
446
|
+
.replace(/\/middleware\.(ts|tsx|js|jsx)$/, "");
|
|
447
|
+
const routePath = relativePath === "" ? "/" : relativePath;
|
|
448
|
+
const module = await server.ssrLoadModule(file);
|
|
449
|
+
if (module.default) {
|
|
450
|
+
middlewareCache.set(routePath, {
|
|
451
|
+
path: routePath,
|
|
452
|
+
filePath: file,
|
|
453
|
+
module: module.default,
|
|
454
|
+
config: module.config,
|
|
455
|
+
});
|
|
456
|
+
log(`New middleware discovered: ${routePath}`);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
catch (e) {
|
|
460
|
+
log(`New middleware load failed: ${e.message}`);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
server.ws.send({ type: "full-reload", path: "*" });
|
|
464
|
+
return [];
|
|
465
|
+
}
|
|
466
|
+
},
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
export { farmMiddleware };
|