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