@teamvelix/velix-core 5.2.9 → 5.3.0
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/index.d.ts +105 -122
- package/dist/index.js +228 -291
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,14 +1,25 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
|
-
import
|
|
2
|
+
import path4 from 'path';
|
|
3
3
|
import crypto from 'crypto';
|
|
4
4
|
import { pathToFileURL } from 'url';
|
|
5
5
|
import { z } from 'zod';
|
|
6
6
|
import pc from 'picocolors';
|
|
7
|
+
import { LRUCache } from 'lru-cache';
|
|
7
8
|
import { glob } from 'glob';
|
|
8
9
|
import { WebSocketServer } from 'ws';
|
|
9
10
|
import chokidar from 'chokidar';
|
|
10
11
|
|
|
11
12
|
// src/router/index.ts
|
|
13
|
+
|
|
14
|
+
// src/router/types.ts
|
|
15
|
+
var RouteType = {
|
|
16
|
+
PAGE: "page",
|
|
17
|
+
API: "api",
|
|
18
|
+
LAYOUT: "layout",
|
|
19
|
+
LOADING: "loading",
|
|
20
|
+
ERROR: "error",
|
|
21
|
+
NOT_FOUND: "not-found"
|
|
22
|
+
};
|
|
12
23
|
function generateHash(content) {
|
|
13
24
|
return crypto.createHash("md5").update(content).digest("hex").slice(0, 8);
|
|
14
25
|
}
|
|
@@ -26,7 +37,7 @@ function findFiles(dir, pattern, files = []) {
|
|
|
26
37
|
if (!fs.existsSync(dir)) return files;
|
|
27
38
|
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
28
39
|
for (const entry of entries) {
|
|
29
|
-
const fullPath =
|
|
40
|
+
const fullPath = path4.join(dir, entry.name);
|
|
30
41
|
if (entry.isDirectory()) findFiles(fullPath, pattern, files);
|
|
31
42
|
else if (pattern.test(entry.name)) files.push(fullPath);
|
|
32
43
|
}
|
|
@@ -43,8 +54,8 @@ function copyDir(src, dest) {
|
|
|
43
54
|
ensureDir(dest);
|
|
44
55
|
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
45
56
|
for (const entry of entries) {
|
|
46
|
-
const srcPath =
|
|
47
|
-
const destPath =
|
|
57
|
+
const srcPath = path4.join(src, entry.name);
|
|
58
|
+
const destPath = path4.join(dest, entry.name);
|
|
48
59
|
if (entry.isDirectory()) copyDir(srcPath, destPath);
|
|
49
60
|
else fs.copyFileSync(srcPath, destPath);
|
|
50
61
|
}
|
|
@@ -107,40 +118,29 @@ function isIsland(filePath) {
|
|
|
107
118
|
}
|
|
108
119
|
}
|
|
109
120
|
|
|
110
|
-
// src/router/
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
pages: [],
|
|
123
|
-
api: [],
|
|
124
|
-
layouts: /* @__PURE__ */ new Map(),
|
|
125
|
-
tree: { children: {}, routes: [] },
|
|
126
|
-
appRoutes: []
|
|
127
|
-
};
|
|
128
|
-
if (fs.existsSync(appDir)) {
|
|
129
|
-
scanAppDirectory(appDir, appDir, routes);
|
|
130
|
-
}
|
|
131
|
-
const serverApiDir = path2.join(projectRoot, "server", "api");
|
|
132
|
-
if (fs.existsSync(serverApiDir)) {
|
|
133
|
-
scanApiDirectory(serverApiDir, serverApiDir, routes);
|
|
121
|
+
// src/router/parser.ts
|
|
122
|
+
function createRoutePattern(routePath) {
|
|
123
|
+
let pattern = routePath.replace(/\*[^/]*/g, "(.*)").replace(/:[^/]+/g, "([^/]+)").replace(/\//g, "\\/");
|
|
124
|
+
return new RegExp(`^${pattern}$`);
|
|
125
|
+
}
|
|
126
|
+
function extractParams(routePath, match) {
|
|
127
|
+
const params = {};
|
|
128
|
+
const paramNames = [];
|
|
129
|
+
const paramRegex = /:([^/]+)|\*([^/]*)/g;
|
|
130
|
+
let paramMatch;
|
|
131
|
+
while ((paramMatch = paramRegex.exec(routePath)) !== null) {
|
|
132
|
+
paramNames.push(paramMatch[1] || paramMatch[2] || "splat");
|
|
134
133
|
}
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
routes.tree = buildTree(routes.appRoutes);
|
|
140
|
-
return routes;
|
|
134
|
+
paramNames.forEach((name, index) => {
|
|
135
|
+
params[name] = match[index + 1];
|
|
136
|
+
});
|
|
137
|
+
return params;
|
|
141
138
|
}
|
|
142
|
-
|
|
143
|
-
|
|
139
|
+
|
|
140
|
+
// src/router/scanner.ts
|
|
141
|
+
async function scanAppDirectory(baseDir, currentDir, parentSegments = [], parentLayout = null, parentMiddleware = null) {
|
|
142
|
+
const routes = [];
|
|
143
|
+
const entries = await fs.promises.readdir(currentDir, { withFileTypes: true });
|
|
144
144
|
const specialFiles = {
|
|
145
145
|
page: null,
|
|
146
146
|
layout: null,
|
|
@@ -153,8 +153,8 @@ function scanAppDirectory(baseDir, currentDir, routes, parentSegments = [], pare
|
|
|
153
153
|
for (const entry of entries) {
|
|
154
154
|
if (entry.isFile()) {
|
|
155
155
|
const name = entry.name.replace(/\.(jsx|js|tsx|ts)$/, "");
|
|
156
|
-
const fullPath =
|
|
157
|
-
const ext =
|
|
156
|
+
const fullPath = path4.join(currentDir, entry.name);
|
|
157
|
+
const ext = path4.extname(entry.name);
|
|
158
158
|
if (![".tsx", ".jsx", ".ts", ".js"].includes(ext)) continue;
|
|
159
159
|
if (name === "page") specialFiles.page = fullPath;
|
|
160
160
|
if (name === "layout") specialFiles.layout = fullPath;
|
|
@@ -172,7 +172,7 @@ function scanAppDirectory(baseDir, currentDir, routes, parentSegments = [], pare
|
|
|
172
172
|
segmentName = ":" + paramName;
|
|
173
173
|
}
|
|
174
174
|
const routePath = "/" + [...parentSegments, segmentName].join("/");
|
|
175
|
-
routes.
|
|
175
|
+
routes.push({
|
|
176
176
|
type: RouteType.PAGE,
|
|
177
177
|
path: routePath.replace(/\/+/g, "/"),
|
|
178
178
|
filePath: fullPath,
|
|
@@ -193,7 +193,7 @@ function scanAppDirectory(baseDir, currentDir, routes, parentSegments = [], pare
|
|
|
193
193
|
}
|
|
194
194
|
if (specialFiles.page) {
|
|
195
195
|
const routePath = "/" + parentSegments.join("/") || "/";
|
|
196
|
-
routes.
|
|
196
|
+
routes.push({
|
|
197
197
|
type: RouteType.PAGE,
|
|
198
198
|
path: routePath.replace(/\/+/g, "/") || "/",
|
|
199
199
|
filePath: specialFiles.page,
|
|
@@ -210,78 +210,58 @@ function scanAppDirectory(baseDir, currentDir, routes, parentSegments = [], pare
|
|
|
210
210
|
isIsland: isIsland(specialFiles.page)
|
|
211
211
|
});
|
|
212
212
|
}
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
213
|
+
const ignoredDirs = ["node_modules", ".velix", "dist"];
|
|
214
|
+
const dirPromises = entries.filter(
|
|
215
|
+
(entry) => entry.isDirectory() && !entry.name.startsWith("_") && !entry.name.startsWith(".") && !ignoredDirs.includes(entry.name)
|
|
216
|
+
).map(async (entry) => {
|
|
217
|
+
const fullPath = path4.join(currentDir, entry.name);
|
|
218
|
+
const isGroup = entry.name.startsWith("(") && entry.name.endsWith(")");
|
|
219
|
+
let segmentName = entry.name;
|
|
220
|
+
if (entry.name.startsWith("[") && entry.name.endsWith("]")) {
|
|
221
|
+
segmentName = ":" + entry.name.slice(1, -1);
|
|
222
|
+
if (entry.name.startsWith("[...")) {
|
|
223
|
+
segmentName = "*" + entry.name.slice(4, -1);
|
|
224
|
+
}
|
|
225
|
+
if (entry.name.startsWith("[[...")) {
|
|
226
|
+
segmentName = "*" + entry.name.slice(5, -2);
|
|
227
227
|
}
|
|
228
|
-
const newSegments = isGroup ? parentSegments : [...parentSegments, segmentName];
|
|
229
|
-
const newLayout = specialFiles.layout || parentLayout;
|
|
230
|
-
const newMiddleware = specialFiles.middleware || parentMiddleware;
|
|
231
|
-
scanAppDirectory(baseDir, fullPath, routes, newSegments, newLayout, newMiddleware);
|
|
232
228
|
}
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
229
|
+
const newSegments = isGroup ? parentSegments : [...parentSegments, segmentName];
|
|
230
|
+
const newLayout = specialFiles.layout || parentLayout;
|
|
231
|
+
const newMiddleware = specialFiles.middleware || parentMiddleware;
|
|
232
|
+
return scanAppDirectory(baseDir, fullPath, newSegments, newLayout, newMiddleware);
|
|
233
|
+
});
|
|
234
|
+
const subRoutes = await Promise.all(dirPromises);
|
|
235
|
+
return routes.concat(...subRoutes);
|
|
236
|
+
}
|
|
237
|
+
async function scanApiDirectory(baseDir, currentDir, parentSegments = []) {
|
|
238
|
+
const entries = await fs.promises.readdir(currentDir, { withFileTypes: true });
|
|
239
|
+
const routes = [];
|
|
240
|
+
const promises = entries.map(async (entry) => {
|
|
241
|
+
const fullPath = path4.join(currentDir, entry.name);
|
|
239
242
|
if (entry.isDirectory()) {
|
|
240
|
-
scanApiDirectory(baseDir, fullPath,
|
|
243
|
+
return scanApiDirectory(baseDir, fullPath, [...parentSegments, entry.name]);
|
|
241
244
|
} else if (entry.isFile()) {
|
|
242
|
-
const ext =
|
|
243
|
-
if (![".ts", ".js"].includes(ext))
|
|
244
|
-
const baseName =
|
|
245
|
+
const ext = path4.extname(entry.name);
|
|
246
|
+
if (![".ts", ".js"].includes(ext)) return [];
|
|
247
|
+
const baseName = path4.basename(entry.name, ext);
|
|
245
248
|
const apiSegments = baseName === "route" || baseName === "index" ? parentSegments : [...parentSegments, baseName];
|
|
246
249
|
const apiPath = "/api/" + apiSegments.join("/");
|
|
247
|
-
|
|
250
|
+
return [{
|
|
248
251
|
type: RouteType.API,
|
|
249
252
|
path: apiPath.replace(/\/+/g, "/") || "/api",
|
|
250
253
|
filePath: fullPath,
|
|
251
254
|
pattern: createRoutePattern(apiPath),
|
|
252
255
|
segments: ["api", ...apiSegments].filter(Boolean)
|
|
253
|
-
}
|
|
256
|
+
}];
|
|
254
257
|
}
|
|
255
|
-
|
|
256
|
-
}
|
|
257
|
-
function createRoutePattern(routePath) {
|
|
258
|
-
let pattern = routePath.replace(/\*[^/]*/g, "(.*)").replace(/:[^/]+/g, "([^/]+)").replace(/\//g, "\\/");
|
|
259
|
-
return new RegExp(`^${pattern}$`);
|
|
260
|
-
}
|
|
261
|
-
function matchRoute(urlPath, routes) {
|
|
262
|
-
const normalizedPath = urlPath === "" ? "/" : urlPath.split("?")[0];
|
|
263
|
-
for (const route of routes) {
|
|
264
|
-
const match = normalizedPath.match(route.pattern);
|
|
265
|
-
if (match) {
|
|
266
|
-
const params = extractParams(route.path, match);
|
|
267
|
-
return { ...route, params };
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
return null;
|
|
271
|
-
}
|
|
272
|
-
function extractParams(routePath, match) {
|
|
273
|
-
const params = {};
|
|
274
|
-
const paramNames = [];
|
|
275
|
-
const paramRegex = /:([^/]+)|\*([^/]*)/g;
|
|
276
|
-
let paramMatch;
|
|
277
|
-
while ((paramMatch = paramRegex.exec(routePath)) !== null) {
|
|
278
|
-
paramNames.push(paramMatch[1] || paramMatch[2] || "splat");
|
|
279
|
-
}
|
|
280
|
-
paramNames.forEach((name, index) => {
|
|
281
|
-
params[name] = match[index + 1];
|
|
258
|
+
return [];
|
|
282
259
|
});
|
|
283
|
-
|
|
260
|
+
const subRoutes = await Promise.all(promises);
|
|
261
|
+
return routes.concat(...subRoutes);
|
|
284
262
|
}
|
|
263
|
+
|
|
264
|
+
// src/router/tree-builder.ts
|
|
285
265
|
function findRouteLayouts(route, layoutsMap) {
|
|
286
266
|
const layouts = [];
|
|
287
267
|
for (const segment of route.segments) {
|
|
@@ -311,6 +291,44 @@ function buildTree(routes) {
|
|
|
311
291
|
}
|
|
312
292
|
return tree;
|
|
313
293
|
}
|
|
294
|
+
|
|
295
|
+
// src/router/matcher.ts
|
|
296
|
+
function matchRoute(urlPath, routes) {
|
|
297
|
+
const normalizedPath = urlPath === "" ? "/" : urlPath.split("?")[0];
|
|
298
|
+
for (const route of routes) {
|
|
299
|
+
const match = normalizedPath.match(route.pattern);
|
|
300
|
+
if (match) {
|
|
301
|
+
const params = extractParams(route.path, match);
|
|
302
|
+
return { ...route, params };
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
return null;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// src/router/index.ts
|
|
309
|
+
async function buildRouteTree(appDir) {
|
|
310
|
+
const projectRoot = path4.dirname(appDir);
|
|
311
|
+
const routes = {
|
|
312
|
+
pages: [],
|
|
313
|
+
api: [],
|
|
314
|
+
layouts: /* @__PURE__ */ new Map(),
|
|
315
|
+
tree: { children: {}, routes: [] },
|
|
316
|
+
appRoutes: []
|
|
317
|
+
};
|
|
318
|
+
if (fs.existsSync(appDir)) {
|
|
319
|
+
routes.appRoutes = await scanAppDirectory(appDir, appDir);
|
|
320
|
+
}
|
|
321
|
+
const serverApiDir = path4.join(projectRoot, "server", "api");
|
|
322
|
+
if (fs.existsSync(serverApiDir)) {
|
|
323
|
+
routes.api = await scanApiDirectory(serverApiDir, serverApiDir);
|
|
324
|
+
}
|
|
325
|
+
const rootLayoutTsx = path4.join(appDir, "layout.tsx");
|
|
326
|
+
const rootLayoutJsx = path4.join(appDir, "layout.jsx");
|
|
327
|
+
if (fs.existsSync(rootLayoutTsx)) routes.rootLayout = rootLayoutTsx;
|
|
328
|
+
else if (fs.existsSync(rootLayoutJsx)) routes.rootLayout = rootLayoutJsx;
|
|
329
|
+
routes.tree = buildTree(routes.appRoutes);
|
|
330
|
+
return routes;
|
|
331
|
+
}
|
|
314
332
|
var AppConfigSchema = z.object({
|
|
315
333
|
name: z.string().default("Velix App"),
|
|
316
334
|
url: z.string().url().optional()
|
|
@@ -374,10 +392,10 @@ function defineConfig(config) {
|
|
|
374
392
|
return config;
|
|
375
393
|
}
|
|
376
394
|
async function loadConfig(projectRoot) {
|
|
377
|
-
const configPathTs =
|
|
378
|
-
const configPathJs =
|
|
379
|
-
const configPathLegacyTs =
|
|
380
|
-
const configPathLegacyJs =
|
|
395
|
+
const configPathTs = path4.join(projectRoot, "velix.config.ts");
|
|
396
|
+
const configPathJs = path4.join(projectRoot, "velix.config.js");
|
|
397
|
+
const configPathLegacyTs = path4.join(projectRoot, "flexireact.config.ts");
|
|
398
|
+
const configPathLegacyJs = path4.join(projectRoot, "flexireact.config.js");
|
|
381
399
|
let configPath = null;
|
|
382
400
|
if (fs.existsSync(configPathTs)) configPath = configPathTs;
|
|
383
401
|
else if (fs.existsSync(configPathJs)) configPath = configPathJs;
|
|
@@ -410,9 +428,9 @@ async function loadConfig(projectRoot) {
|
|
|
410
428
|
function resolvePaths(config, projectRoot) {
|
|
411
429
|
return {
|
|
412
430
|
...config,
|
|
413
|
-
resolvedAppDir:
|
|
414
|
-
resolvedPublicDir:
|
|
415
|
-
resolvedOutDir:
|
|
431
|
+
resolvedAppDir: path4.resolve(projectRoot, config.appDir),
|
|
432
|
+
resolvedPublicDir: path4.resolve(projectRoot, config.publicDir),
|
|
433
|
+
resolvedOutDir: path4.resolve(projectRoot, config.build.outDir)
|
|
416
434
|
};
|
|
417
435
|
}
|
|
418
436
|
function deepMerge(target, source) {
|
|
@@ -428,7 +446,7 @@ function deepMerge(target, source) {
|
|
|
428
446
|
}
|
|
429
447
|
|
|
430
448
|
// src/version.ts
|
|
431
|
-
var VERSION = "5.
|
|
449
|
+
var VERSION = "5.3.0";
|
|
432
450
|
|
|
433
451
|
// src/logger.ts
|
|
434
452
|
var colors = {
|
|
@@ -476,14 +494,14 @@ var logger = {
|
|
|
476
494
|
if (pagesDir) console.log(` ${c.bold}App:${c.reset} ${c.dim}${pagesDir}${c.reset}`);
|
|
477
495
|
console.log("");
|
|
478
496
|
},
|
|
479
|
-
request(method,
|
|
497
|
+
request(method, path9, status, time, extra = {}) {
|
|
480
498
|
const statusColor = getStatusColor(status);
|
|
481
499
|
const timeStr = fmtTime(time);
|
|
482
500
|
let badge = `${c.dim}\u25CB${c.reset}`;
|
|
483
501
|
if (extra.type === "dynamic" || extra.type === "ssr") badge = `${c.white}\u0192${c.reset}`;
|
|
484
502
|
else if (extra.type === "api") badge = `${c.cyan}\u03BB${c.reset}`;
|
|
485
503
|
const statusStr = `${statusColor}${status}${c.reset}`;
|
|
486
|
-
console.log(` ${badge} ${c.white}${method}${c.reset} ${
|
|
504
|
+
console.log(` ${badge} ${c.white}${method}${c.reset} ${path9} ${statusStr} ${c.dim}${timeStr}${c.reset}`);
|
|
487
505
|
},
|
|
488
506
|
info(msg) {
|
|
489
507
|
console.log(` ${c.cyan}\u2139${c.reset} ${msg}`);
|
|
@@ -511,10 +529,10 @@ var logger = {
|
|
|
511
529
|
plugin(name) {
|
|
512
530
|
console.log(` ${c.cyan}\u25C6${c.reset} Plugin ${c.dim}${name}${c.reset}`);
|
|
513
531
|
},
|
|
514
|
-
route(
|
|
532
|
+
route(path9, type) {
|
|
515
533
|
const typeLabel = type === "api" ? "\u03BB" : type === "dynamic" ? "\u0192" : "\u25CB";
|
|
516
534
|
const color = type === "api" ? c.cyan : type === "dynamic" ? c.white : c.dim;
|
|
517
|
-
console.log(` ${color}${typeLabel}${c.reset} ${
|
|
535
|
+
console.log(` ${color}${typeLabel}${c.reset} ${path9}`);
|
|
518
536
|
},
|
|
519
537
|
divider() {
|
|
520
538
|
console.log(`${c.dim} \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500${c.reset}`);
|
|
@@ -805,7 +823,7 @@ async function loadMiddleware(projectRoot) {
|
|
|
805
823
|
const fns = [];
|
|
806
824
|
const possibleFiles = ["proxy.ts", "proxy.js"];
|
|
807
825
|
for (const file of possibleFiles) {
|
|
808
|
-
const filePath =
|
|
826
|
+
const filePath = path4.join(projectRoot, file);
|
|
809
827
|
if (!fs.existsSync(filePath)) continue;
|
|
810
828
|
try {
|
|
811
829
|
const { pathToFileURL: pathToFileURL4 } = await import('url');
|
|
@@ -980,9 +998,9 @@ async function loadPlugins(projectRoot, config) {
|
|
|
980
998
|
for (const entry of pluginEntries) {
|
|
981
999
|
try {
|
|
982
1000
|
if (typeof entry === "string") {
|
|
983
|
-
const localPath =
|
|
984
|
-
const localPathJs =
|
|
985
|
-
const localPathDir =
|
|
1001
|
+
const localPath = path4.join(projectRoot, "plugins", entry + ".ts");
|
|
1002
|
+
const localPathJs = path4.join(projectRoot, "plugins", entry + ".js");
|
|
1003
|
+
const localPathDir = path4.join(projectRoot, "plugins", entry, "index.ts");
|
|
986
1004
|
let pluginPath = null;
|
|
987
1005
|
if (fs.existsSync(localPath)) pluginPath = localPath;
|
|
988
1006
|
else if (fs.existsSync(localPathJs)) pluginPath = localPathJs;
|
|
@@ -1523,189 +1541,113 @@ function createAIAction(config) {
|
|
|
1523
1541
|
return response.text;
|
|
1524
1542
|
};
|
|
1525
1543
|
}
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
var VelixCache = class {
|
|
1529
|
-
maxSize;
|
|
1530
|
-
defaultTtl;
|
|
1531
|
-
defaultSwr;
|
|
1532
|
-
store = /* @__PURE__ */ new Map();
|
|
1544
|
+
var MemoryCacheAdapter = class {
|
|
1545
|
+
lru;
|
|
1533
1546
|
tagIndex = /* @__PURE__ */ new Map();
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
tail;
|
|
1537
|
-
constructor(config = {}) {
|
|
1538
|
-
this.maxSize = config.maxSize ?? 1e3;
|
|
1539
|
-
this.defaultTtl = config.defaultTtl ?? 6e4;
|
|
1540
|
-
this.defaultSwr = config.defaultSwr ?? 1e4;
|
|
1541
|
-
this.head = this.createSentinel("__HEAD__");
|
|
1542
|
-
this.tail = this.createSentinel("__TAIL__");
|
|
1543
|
-
this.head.next = this.tail;
|
|
1544
|
-
this.tail.prev = this.head;
|
|
1545
|
-
}
|
|
1546
|
-
/** Current number of entries */
|
|
1547
|
-
get size() {
|
|
1548
|
-
return this.store.size;
|
|
1547
|
+
constructor(options = {}) {
|
|
1548
|
+
this.lru = new LRUCache({ max: options.maxSize ?? 500 });
|
|
1549
1549
|
}
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1550
|
+
async get(key) {
|
|
1551
|
+
const entry = this.lru.get(key);
|
|
1552
|
+
if (!entry) return null;
|
|
1553
|
+
if (entry.expiresAt && Date.now() > entry.expiresAt) {
|
|
1554
|
+
this.lru.delete(key);
|
|
1555
|
+
return null;
|
|
1556
1556
|
}
|
|
1557
|
+
return entry.value;
|
|
1558
|
+
}
|
|
1559
|
+
async set(key, value, options = {}) {
|
|
1557
1560
|
const entry = {
|
|
1558
|
-
key,
|
|
1559
1561
|
value,
|
|
1560
|
-
tags:
|
|
1561
|
-
|
|
1562
|
-
ttl: options?.ttl ?? this.defaultTtl,
|
|
1563
|
-
swr: options?.swr ?? this.defaultSwr,
|
|
1564
|
-
prev: null,
|
|
1565
|
-
next: null
|
|
1562
|
+
tags: options.tags ?? [],
|
|
1563
|
+
expiresAt: options.ttl ? Date.now() + options.ttl : null
|
|
1566
1564
|
};
|
|
1567
|
-
this.
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
if (!this.tagIndex.has(tag)) {
|
|
1571
|
-
this.tagIndex.set(tag, /* @__PURE__ */ new Set());
|
|
1572
|
-
}
|
|
1565
|
+
this.lru.set(key, entry);
|
|
1566
|
+
options.tags?.forEach((tag) => {
|
|
1567
|
+
if (!this.tagIndex.has(tag)) this.tagIndex.set(tag, /* @__PURE__ */ new Set());
|
|
1573
1568
|
this.tagIndex.get(tag).add(key);
|
|
1574
|
-
}
|
|
1575
|
-
while (this.store.size > this.maxSize) {
|
|
1576
|
-
this.evictLRU();
|
|
1577
|
-
}
|
|
1569
|
+
});
|
|
1578
1570
|
}
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
*
|
|
1582
|
-
* @returns `{ value, stale }` — `stale` is true when the entry is past TTL but within SWR window.
|
|
1583
|
-
*/
|
|
1584
|
-
get(key) {
|
|
1585
|
-
const entry = this.store.get(key);
|
|
1586
|
-
if (!entry) return null;
|
|
1587
|
-
const now = Date.now();
|
|
1588
|
-
const age = now - entry.createdAt;
|
|
1589
|
-
if (age > entry.ttl + entry.swr) {
|
|
1590
|
-
this.removeEntry(entry);
|
|
1591
|
-
return null;
|
|
1592
|
-
}
|
|
1593
|
-
this.moveToHead(entry);
|
|
1594
|
-
const stale = age > entry.ttl;
|
|
1595
|
-
return { value: entry.value, stale };
|
|
1571
|
+
async delete(key) {
|
|
1572
|
+
this.lru.delete(key);
|
|
1596
1573
|
}
|
|
1597
|
-
|
|
1598
|
-
* Simple get that returns only the value (ignoring staleness).
|
|
1599
|
-
* Returns `null` if not found or expired.
|
|
1600
|
-
*/
|
|
1601
|
-
getValue(key) {
|
|
1602
|
-
const result = this.get(key);
|
|
1603
|
-
return result ? result.value : null;
|
|
1604
|
-
}
|
|
1605
|
-
/** Check whether a key exists and is not expired */
|
|
1606
|
-
has(key) {
|
|
1607
|
-
return this.get(key) !== null;
|
|
1608
|
-
}
|
|
1609
|
-
/** Remove a specific key */
|
|
1610
|
-
delete(key) {
|
|
1611
|
-
const entry = this.store.get(key);
|
|
1612
|
-
if (!entry) return false;
|
|
1613
|
-
this.removeEntry(entry);
|
|
1614
|
-
return true;
|
|
1615
|
-
}
|
|
1616
|
-
/** Invalidate a specific path/key */
|
|
1617
|
-
revalidatePath(path8) {
|
|
1618
|
-
this.delete(path8);
|
|
1619
|
-
}
|
|
1620
|
-
/** Invalidate all entries associated with a tag */
|
|
1621
|
-
revalidateTag(tag) {
|
|
1574
|
+
async deleteByTag(tag) {
|
|
1622
1575
|
const keys = this.tagIndex.get(tag);
|
|
1623
1576
|
if (!keys) return;
|
|
1624
|
-
|
|
1625
|
-
const entry = this.store.get(key);
|
|
1626
|
-
if (entry) {
|
|
1627
|
-
this.removeEntry(entry);
|
|
1628
|
-
}
|
|
1629
|
-
}
|
|
1577
|
+
keys.forEach((key) => this.lru.delete(key));
|
|
1630
1578
|
this.tagIndex.delete(tag);
|
|
1631
1579
|
}
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
this.head.next = this.tail;
|
|
1637
|
-
this.tail.prev = this.head;
|
|
1580
|
+
async deleteByPrefix(prefix) {
|
|
1581
|
+
for (const key of this.lru.keys()) {
|
|
1582
|
+
if (key.startsWith(prefix)) this.lru.delete(key);
|
|
1583
|
+
}
|
|
1638
1584
|
}
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
size: this.store.size,
|
|
1643
|
-
maxSize: this.maxSize,
|
|
1644
|
-
tags: this.tagIndex.size
|
|
1645
|
-
};
|
|
1585
|
+
async clear() {
|
|
1586
|
+
this.lru.clear();
|
|
1587
|
+
this.tagIndex.clear();
|
|
1646
1588
|
}
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
return {
|
|
1650
|
-
key,
|
|
1651
|
-
value: null,
|
|
1652
|
-
tags: /* @__PURE__ */ new Set(),
|
|
1653
|
-
createdAt: 0,
|
|
1654
|
-
ttl: 0,
|
|
1655
|
-
swr: 0,
|
|
1656
|
-
prev: null,
|
|
1657
|
-
next: null
|
|
1658
|
-
};
|
|
1589
|
+
async has(key) {
|
|
1590
|
+
return this.lru.has(key);
|
|
1659
1591
|
}
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
if (entry.next) entry.next.prev = entry.prev;
|
|
1669
|
-
entry.prev = null;
|
|
1670
|
-
entry.next = null;
|
|
1671
|
-
}
|
|
1672
|
-
moveToHead(entry) {
|
|
1673
|
-
this.removeFromList(entry);
|
|
1674
|
-
this.addToHead(entry);
|
|
1675
|
-
}
|
|
1676
|
-
evictLRU() {
|
|
1677
|
-
const lru = this.tail.prev;
|
|
1678
|
-
if (lru && lru !== this.head) {
|
|
1679
|
-
this.removeEntry(lru);
|
|
1592
|
+
};
|
|
1593
|
+
|
|
1594
|
+
// src/cache/deduplicator.ts
|
|
1595
|
+
var RequestDeduplicator = class {
|
|
1596
|
+
inFlight = /* @__PURE__ */ new Map();
|
|
1597
|
+
async dedupe(key, fn) {
|
|
1598
|
+
if (this.inFlight.has(key)) {
|
|
1599
|
+
return this.inFlight.get(key);
|
|
1680
1600
|
}
|
|
1601
|
+
const promise = fn().finally(() => {
|
|
1602
|
+
this.inFlight.delete(key);
|
|
1603
|
+
});
|
|
1604
|
+
this.inFlight.set(key, promise);
|
|
1605
|
+
return promise;
|
|
1681
1606
|
}
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
}
|
|
1607
|
+
};
|
|
1608
|
+
|
|
1609
|
+
// src/cache/index.ts
|
|
1610
|
+
var VelixCache = class {
|
|
1611
|
+
adapter;
|
|
1612
|
+
deduplicator;
|
|
1613
|
+
constructor(adapter = new MemoryCacheAdapter()) {
|
|
1614
|
+
this.adapter = adapter;
|
|
1615
|
+
this.deduplicator = new RequestDeduplicator();
|
|
1616
|
+
}
|
|
1617
|
+
async revalidatePath(path9) {
|
|
1618
|
+
await this.adapter.deleteByPrefix(`route:${path9}`);
|
|
1619
|
+
}
|
|
1620
|
+
async revalidateTag(tag) {
|
|
1621
|
+
await this.adapter.deleteByTag(tag);
|
|
1622
|
+
}
|
|
1623
|
+
// unstable_cache — avec déduplication
|
|
1624
|
+
unstable_cache(fn, keys, options) {
|
|
1625
|
+
const key = keys.join(":");
|
|
1626
|
+
return async () => {
|
|
1627
|
+
const cached = await this.adapter.get(key);
|
|
1628
|
+
if (cached !== null) return cached;
|
|
1629
|
+
const value = await this.deduplicator.dedupe(key, fn);
|
|
1630
|
+
await this.adapter.set(key, value, {
|
|
1631
|
+
ttl: options?.revalidate ? options.revalidate * 1e3 : void 0,
|
|
1632
|
+
tags: options?.tags
|
|
1633
|
+
});
|
|
1634
|
+
return value;
|
|
1635
|
+
};
|
|
1694
1636
|
}
|
|
1695
1637
|
};
|
|
1696
1638
|
var cacheManager = new VelixCache();
|
|
1697
|
-
function revalidatePath(
|
|
1698
|
-
cacheManager.revalidatePath(
|
|
1639
|
+
async function revalidatePath(path9, _type = "path") {
|
|
1640
|
+
await cacheManager.revalidatePath(path9);
|
|
1699
1641
|
if (typeof global !== "undefined" && global.__VELIX_HMR_SERVER__) {
|
|
1700
1642
|
global.__VELIX_HMR_SERVER__.broadcast(JSON.stringify({
|
|
1701
1643
|
type: "revalidate",
|
|
1702
|
-
path:
|
|
1644
|
+
path: path9,
|
|
1703
1645
|
revalidationType: _type
|
|
1704
1646
|
}));
|
|
1705
1647
|
}
|
|
1706
1648
|
}
|
|
1707
|
-
function revalidateTag(tag) {
|
|
1708
|
-
cacheManager.revalidateTag(tag);
|
|
1649
|
+
async function revalidateTag(tag) {
|
|
1650
|
+
await cacheManager.revalidateTag(tag);
|
|
1709
1651
|
if (typeof global !== "undefined" && global.__VELIX_HMR_SERVER__) {
|
|
1710
1652
|
global.__VELIX_HMR_SERVER__.broadcast(JSON.stringify({
|
|
1711
1653
|
type: "revalidate",
|
|
@@ -1714,19 +1656,7 @@ function revalidateTag(tag) {
|
|
|
1714
1656
|
}
|
|
1715
1657
|
}
|
|
1716
1658
|
function unstable_cache(fn, keys, options) {
|
|
1717
|
-
return
|
|
1718
|
-
const cacheKey = keys.join(":");
|
|
1719
|
-
const cached = cacheManager.get(cacheKey);
|
|
1720
|
-
if (cached && !cached.stale) {
|
|
1721
|
-
return cached.value;
|
|
1722
|
-
}
|
|
1723
|
-
const result = await fn();
|
|
1724
|
-
cacheManager.set(cacheKey, result, {
|
|
1725
|
-
tags: options?.tags,
|
|
1726
|
-
ttl: options?.revalidate ? options.revalidate * 1e3 : void 0
|
|
1727
|
-
});
|
|
1728
|
-
return result;
|
|
1729
|
-
};
|
|
1659
|
+
return cacheManager.unstable_cache(fn, keys, options);
|
|
1730
1660
|
}
|
|
1731
1661
|
var VelixHttpError = class extends Error {
|
|
1732
1662
|
status;
|
|
@@ -1783,7 +1713,7 @@ async function buildApiManifest(serverDir) {
|
|
|
1783
1713
|
return files.map((file) => filePathToRoute(file, serverDir));
|
|
1784
1714
|
}
|
|
1785
1715
|
function filePathToRoute(filePath, serverDir) {
|
|
1786
|
-
const relative =
|
|
1716
|
+
const relative = path4.relative(path4.join(serverDir, "api"), filePath);
|
|
1787
1717
|
const normalizedRelative = relative.replace(/\\/g, "/");
|
|
1788
1718
|
const withoutExt = normalizedRelative.replace(/\.ts$/, "");
|
|
1789
1719
|
const params = [];
|
|
@@ -1873,7 +1803,7 @@ function serverAction(options) {
|
|
|
1873
1803
|
}
|
|
1874
1804
|
};
|
|
1875
1805
|
}
|
|
1876
|
-
function createHMRServer(httpServer, projectRoot) {
|
|
1806
|
+
function createHMRServer(httpServer, projectRoot, options) {
|
|
1877
1807
|
const wss = new WebSocketServer({ server: httpServer, path: "/__velix_hmr" });
|
|
1878
1808
|
const clients = /* @__PURE__ */ new Set();
|
|
1879
1809
|
wss.on("connection", (ws) => {
|
|
@@ -1886,19 +1816,26 @@ function createHMRServer(httpServer, projectRoot) {
|
|
|
1886
1816
|
persistent: true,
|
|
1887
1817
|
ignoreInitial: true
|
|
1888
1818
|
});
|
|
1889
|
-
function
|
|
1819
|
+
function broadcastLocal(event) {
|
|
1890
1820
|
const msg = JSON.stringify(event);
|
|
1891
1821
|
clients.forEach((ws) => {
|
|
1892
1822
|
if (ws.readyState === ws.OPEN) ws.send(msg);
|
|
1893
1823
|
});
|
|
1894
1824
|
}
|
|
1825
|
+
function broadcast(event) {
|
|
1826
|
+
broadcastLocal(event);
|
|
1827
|
+
options?.pubsub?.publish(event);
|
|
1828
|
+
}
|
|
1829
|
+
options?.pubsub?.onEvent((event) => {
|
|
1830
|
+
broadcastLocal(event);
|
|
1831
|
+
});
|
|
1895
1832
|
watcher.on("change", (file) => {
|
|
1896
1833
|
broadcast({ type: "file-changed", file, timestamp: Date.now() });
|
|
1897
1834
|
});
|
|
1898
1835
|
watcher.on("add", (file) => {
|
|
1899
1836
|
broadcast({ type: "file-added", file, timestamp: Date.now() });
|
|
1900
1837
|
});
|
|
1901
|
-
return { broadcast, watcher, wss, clients };
|
|
1838
|
+
return { broadcast, broadcastLocal, watcher, wss, clients };
|
|
1902
1839
|
}
|
|
1903
1840
|
function getErrorBoundaryType(error) {
|
|
1904
1841
|
if (error instanceof NotFoundError) return "not-found";
|
|
@@ -1906,18 +1843,18 @@ function getErrorBoundaryType(error) {
|
|
|
1906
1843
|
return "error";
|
|
1907
1844
|
}
|
|
1908
1845
|
function resolveErrorBoundary(routeFilePath, appDir, type) {
|
|
1909
|
-
let currentDir =
|
|
1846
|
+
let currentDir = path4.dirname(routeFilePath);
|
|
1910
1847
|
while (currentDir.length >= appDir.length && currentDir.startsWith(appDir)) {
|
|
1911
|
-
const boundaryPath =
|
|
1848
|
+
const boundaryPath = path4.join(currentDir, `${type}.tsx`);
|
|
1912
1849
|
if (fs.existsSync(boundaryPath)) {
|
|
1913
|
-
const scope = currentDir === appDir ? "root" :
|
|
1850
|
+
const scope = currentDir === appDir ? "root" : path4.basename(currentDir);
|
|
1914
1851
|
return {
|
|
1915
1852
|
filePath: boundaryPath,
|
|
1916
1853
|
type,
|
|
1917
1854
|
scope
|
|
1918
1855
|
};
|
|
1919
1856
|
}
|
|
1920
|
-
const parentDir =
|
|
1857
|
+
const parentDir = path4.dirname(currentDir);
|
|
1921
1858
|
if (parentDir === currentDir) break;
|
|
1922
1859
|
currentDir = parentDir;
|
|
1923
1860
|
}
|
|
@@ -1932,6 +1869,6 @@ function defineNotFound(component) {
|
|
|
1932
1869
|
return component;
|
|
1933
1870
|
}
|
|
1934
1871
|
|
|
1935
|
-
export { ForbiddenError, NotFoundError, OllamaProvider, OpenAIProvider, PluginHooks, PluginManager, RouteType, UnauthorizedError, VelixCache, VelixConfigSchema, VelixHttpError, buildApiManifest, buildPrompt, buildRouteTree, builtinPlugins, cacheManager, cleanDir, composeMiddleware, copyDir, createAIAction, createAIClient, createDeferred, createHMRServer, createStreamDecoder, debounce, defaultConfig, defineConfig, defineError, defineLoader, defineNotFound, definePlugin, defineRoute, ensureDir, escapeHtml, estimateTokens, filePathToRoute, findFiles, findRouteLayouts, formatBytes, formatTime, generateHash, generateJsonLd, generateMetadataTags, generateRobotsTxt, generateSitemap, getErrorBoundaryType, handleApiRequest, isClientComponent, isIsland, isServerComponent, jsonLd, loadConfig, loadMiddleware, loadPlugins, logger, matchRoute, mergeMetadata, middlewares, parseSSE, pluginManager, resolveErrorBoundary, resolvePaths, retry, revalidatePath, revalidateTag, runMiddleware, safeJsonParse, sanitizeInput, serverAction, sleep, truncateToTokens, unstable_cache, useAI, validateApiKey, validateInput };
|
|
1872
|
+
export { ForbiddenError, NotFoundError, OllamaProvider, OpenAIProvider, PluginHooks, PluginManager, RouteType, UnauthorizedError, VelixCache, VelixConfigSchema, VelixHttpError, buildApiManifest, buildPrompt, buildRouteTree, builtinPlugins, cacheManager, cleanDir, composeMiddleware, copyDir, createAIAction, createAIClient, createDeferred, createHMRServer, createRoutePattern, createStreamDecoder, debounce, defaultConfig, defineConfig, defineError, defineLoader, defineNotFound, definePlugin, defineRoute, ensureDir, escapeHtml, estimateTokens, filePathToRoute, findFiles, findRouteLayouts, formatBytes, formatTime, generateHash, generateJsonLd, generateMetadataTags, generateRobotsTxt, generateSitemap, getErrorBoundaryType, handleApiRequest, isClientComponent, isIsland, isServerComponent, jsonLd, loadConfig, loadMiddleware, loadPlugins, logger, matchRoute, mergeMetadata, middlewares, parseSSE, pluginManager, resolveErrorBoundary, resolvePaths, retry, revalidatePath, revalidateTag, runMiddleware, safeJsonParse, sanitizeInput, serverAction, sleep, truncateToTokens, unstable_cache, useAI, validateApiKey, validateInput };
|
|
1936
1873
|
//# sourceMappingURL=index.js.map
|
|
1937
1874
|
//# sourceMappingURL=index.js.map
|