@ereo/router 0.1.6

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.js ADDED
@@ -0,0 +1,1507 @@
1
+ // @bun
2
+ var __require = import.meta.require;
3
+
4
+ // src/file-router.ts
5
+ import { readdir, stat } from "fs/promises";
6
+ import { join, extname } from "path";
7
+
8
+ // src/types.ts
9
+ var ROUTE_SCORE = {
10
+ STATIC: 100,
11
+ INDEX: 90,
12
+ DYNAMIC: 50,
13
+ OPTIONAL: 30,
14
+ CATCH_ALL: 10
15
+ };
16
+ var SPECIAL_FILES = {
17
+ LAYOUT: "_layout",
18
+ ERROR: "_error",
19
+ LOADING: "_loading",
20
+ NOT_FOUND: "_404"
21
+ };
22
+ var ROUTE_GROUP_PATTERN = /^\((.+)\)$/;
23
+ var DYNAMIC_SEGMENT_PATTERN = /^\[([^\]]+)\]$/;
24
+ var CATCH_ALL_PATTERN = /^\[\.\.\.([^\]]+)\]$/;
25
+ var OPTIONAL_PATTERN = /^\[\[([^\]]+)\]\]$/;
26
+
27
+ // src/matcher.ts
28
+ function parsePathSegments(path) {
29
+ const segments = [];
30
+ const parts = path.split("/").filter(Boolean);
31
+ for (const part of parts) {
32
+ const catchAllMatch = part.match(CATCH_ALL_PATTERN);
33
+ if (catchAllMatch) {
34
+ segments.push({
35
+ raw: part,
36
+ type: "catchAll",
37
+ paramName: catchAllMatch[1]
38
+ });
39
+ continue;
40
+ }
41
+ const optionalMatch = part.match(OPTIONAL_PATTERN);
42
+ if (optionalMatch) {
43
+ segments.push({
44
+ raw: part,
45
+ type: "optional",
46
+ paramName: optionalMatch[1]
47
+ });
48
+ continue;
49
+ }
50
+ const dynamicMatch = part.match(DYNAMIC_SEGMENT_PATTERN);
51
+ if (dynamicMatch) {
52
+ segments.push({
53
+ raw: part,
54
+ type: "dynamic",
55
+ paramName: dynamicMatch[1]
56
+ });
57
+ continue;
58
+ }
59
+ segments.push({
60
+ raw: part,
61
+ type: "static"
62
+ });
63
+ }
64
+ return segments;
65
+ }
66
+ function calculateRouteScore(segments) {
67
+ let score = 0;
68
+ for (let i = 0;i < segments.length; i++) {
69
+ const segment = segments[i];
70
+ const positionMultiplier = 1000 / (i + 1);
71
+ switch (segment.type) {
72
+ case "static":
73
+ score += ROUTE_SCORE.STATIC * positionMultiplier;
74
+ break;
75
+ case "dynamic":
76
+ score += ROUTE_SCORE.DYNAMIC * positionMultiplier;
77
+ break;
78
+ case "optional":
79
+ score += ROUTE_SCORE.OPTIONAL * positionMultiplier;
80
+ break;
81
+ case "catchAll":
82
+ score += ROUTE_SCORE.CATCH_ALL * positionMultiplier;
83
+ break;
84
+ }
85
+ }
86
+ return score;
87
+ }
88
+ function patternToRegex(segments) {
89
+ if (segments.length === 0) {
90
+ return /^\/$/;
91
+ }
92
+ let pattern = "^";
93
+ for (let i = 0;i < segments.length; i++) {
94
+ const segment = segments[i];
95
+ switch (segment.type) {
96
+ case "static":
97
+ pattern += `\\/${escapeRegex(segment.raw)}`;
98
+ break;
99
+ case "dynamic":
100
+ pattern += "\\/([^/]+)";
101
+ break;
102
+ case "optional":
103
+ pattern += "(?:\\/([^/]+))?";
104
+ break;
105
+ case "catchAll":
106
+ pattern += "(?:\\/(.+))?";
107
+ break;
108
+ }
109
+ }
110
+ pattern += "\\/?$";
111
+ return new RegExp(pattern);
112
+ }
113
+ function escapeRegex(str) {
114
+ return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
115
+ }
116
+ function matchRoute(pathname, route, segments) {
117
+ const regex = patternToRegex(segments);
118
+ const match = pathname.match(regex);
119
+ if (!match) {
120
+ return null;
121
+ }
122
+ const params = {};
123
+ let paramIndex = 1;
124
+ for (const segment of segments) {
125
+ if (segment.paramName) {
126
+ const value = match[paramIndex];
127
+ if (segment.type === "catchAll" && value) {
128
+ params[segment.paramName] = value.split("/");
129
+ } else if (value !== undefined) {
130
+ params[segment.paramName] = value;
131
+ }
132
+ paramIndex++;
133
+ }
134
+ }
135
+ return {
136
+ route,
137
+ params,
138
+ pathname
139
+ };
140
+ }
141
+
142
+ class RouteMatcher {
143
+ routes = [];
144
+ constructor(routes) {
145
+ this.compileRoutes(routes);
146
+ }
147
+ compileRoutes(routes) {
148
+ const flatRoutes = this.flattenRoutes(routes);
149
+ this.routes = flatRoutes.map((route) => {
150
+ const segments = parsePathSegments(route.path);
151
+ return {
152
+ route,
153
+ segments,
154
+ regex: patternToRegex(segments),
155
+ score: calculateRouteScore(segments)
156
+ };
157
+ }).sort((a, b) => b.score - a.score);
158
+ }
159
+ flattenRoutes(routes, parent) {
160
+ const result = [];
161
+ for (const route of routes) {
162
+ if (!route.layout || route.index) {
163
+ result.push(route);
164
+ }
165
+ if (route.children) {
166
+ result.push(...this.flattenRoutes(route.children, route));
167
+ }
168
+ }
169
+ return result;
170
+ }
171
+ match(pathname) {
172
+ const normalizedPath = pathname === "" ? "/" : pathname;
173
+ for (const { route, segments, regex } of this.routes) {
174
+ const match = normalizedPath.match(regex);
175
+ if (match) {
176
+ const params = {};
177
+ let paramIndex = 1;
178
+ for (const segment of segments) {
179
+ if (segment.paramName) {
180
+ const value = match[paramIndex];
181
+ if (segment.type === "catchAll" && value) {
182
+ params[segment.paramName] = value.split("/");
183
+ } else if (value !== undefined) {
184
+ params[segment.paramName] = value;
185
+ }
186
+ paramIndex++;
187
+ }
188
+ }
189
+ return {
190
+ route,
191
+ params,
192
+ pathname: normalizedPath
193
+ };
194
+ }
195
+ }
196
+ return null;
197
+ }
198
+ getRoutes() {
199
+ return this.routes.map((r) => r.route);
200
+ }
201
+ addRoute(route) {
202
+ const segments = parsePathSegments(route.path);
203
+ const entry = {
204
+ route,
205
+ segments,
206
+ regex: patternToRegex(segments),
207
+ score: calculateRouteScore(segments)
208
+ };
209
+ const insertIndex = this.routes.findIndex((r) => r.score < entry.score);
210
+ if (insertIndex === -1) {
211
+ this.routes.push(entry);
212
+ } else {
213
+ this.routes.splice(insertIndex, 0, entry);
214
+ }
215
+ }
216
+ removeRoute(routeId) {
217
+ const index = this.routes.findIndex((r) => r.route.id === routeId);
218
+ if (index !== -1) {
219
+ this.routes.splice(index, 1);
220
+ return true;
221
+ }
222
+ return false;
223
+ }
224
+ }
225
+ function createMatcher(routes) {
226
+ return new RouteMatcher(routes);
227
+ }
228
+ function matchWithLayouts(pathname, routes) {
229
+ const matcher = new RouteMatcher(routes);
230
+ const match = matcher.match(pathname);
231
+ if (!match) {
232
+ return null;
233
+ }
234
+ const layouts = [];
235
+ const collectLayouts = (routeList, currentPath) => {
236
+ for (const route of routeList) {
237
+ if (route.layout) {
238
+ const layoutPath = route.path === "/" ? "" : route.path;
239
+ const matchPath = match.pathname === "/" ? "" : match.pathname;
240
+ if (matchPath === layoutPath || matchPath.startsWith(layoutPath + "/") || layoutPath === "") {
241
+ layouts.push(route);
242
+ }
243
+ }
244
+ if (route.children) {
245
+ collectLayouts(route.children, currentPath + route.path);
246
+ }
247
+ }
248
+ };
249
+ collectLayouts(routes, "");
250
+ layouts.sort((a, b) => a.path.length - b.path.length);
251
+ return {
252
+ ...match,
253
+ layouts
254
+ };
255
+ }
256
+
257
+ // src/route-tree.ts
258
+ class RouteTree {
259
+ root;
260
+ constructor() {
261
+ this.root = this.createNode("/", "/", "", false, false);
262
+ }
263
+ createNode(id, path, file, index, layout) {
264
+ const segments = parsePathSegments(path);
265
+ return {
266
+ id,
267
+ path,
268
+ segments,
269
+ file,
270
+ index,
271
+ layout,
272
+ children: [],
273
+ score: calculateRouteScore(segments)
274
+ };
275
+ }
276
+ addRoute(id, path, file, options = {}) {
277
+ const { index = false, layout = false } = options;
278
+ const node = this.createNode(id, path, file, index, layout);
279
+ const parent = this.findParent(path);
280
+ node.parent = parent;
281
+ parent.children.push(node);
282
+ parent.children.sort((a, b) => b.score - a.score);
283
+ return node;
284
+ }
285
+ findParent(path) {
286
+ if (path === "/") {
287
+ return this.root;
288
+ }
289
+ const segments = path.split("/").filter(Boolean);
290
+ segments.pop();
291
+ if (segments.length === 0) {
292
+ return this.root;
293
+ }
294
+ let current = this.root;
295
+ let currentPath = "";
296
+ for (const segment of segments) {
297
+ currentPath += "/" + segment;
298
+ const child = current.children.find((c) => c.path === currentPath || c.layout);
299
+ if (child) {
300
+ current = child;
301
+ }
302
+ }
303
+ return current;
304
+ }
305
+ getRoot() {
306
+ return this.root;
307
+ }
308
+ toRoutes() {
309
+ return this.nodeToRoute(this.root).children || [];
310
+ }
311
+ nodeToRoute(node) {
312
+ const route = {
313
+ id: node.id,
314
+ path: node.path,
315
+ file: node.file,
316
+ index: node.index,
317
+ layout: node.layout
318
+ };
319
+ if (node.children.length > 0) {
320
+ route.children = node.children.map((child) => this.nodeToRoute(child));
321
+ }
322
+ if (node.module) {
323
+ route.module = node.module;
324
+ }
325
+ return route;
326
+ }
327
+ findByPath(path) {
328
+ const search = (node) => {
329
+ if (node.path === path) {
330
+ return node;
331
+ }
332
+ for (const child of node.children) {
333
+ const found = search(child);
334
+ if (found)
335
+ return found;
336
+ }
337
+ return null;
338
+ };
339
+ return search(this.root);
340
+ }
341
+ findById(id) {
342
+ const search = (node) => {
343
+ if (node.id === id) {
344
+ return node;
345
+ }
346
+ for (const child of node.children) {
347
+ const found = search(child);
348
+ if (found)
349
+ return found;
350
+ }
351
+ return null;
352
+ };
353
+ return search(this.root);
354
+ }
355
+ removeById(id) {
356
+ const remove = (parent) => {
357
+ const index = parent.children.findIndex((c) => c.id === id);
358
+ if (index !== -1) {
359
+ parent.children.splice(index, 1);
360
+ return true;
361
+ }
362
+ for (const child of parent.children) {
363
+ if (remove(child))
364
+ return true;
365
+ }
366
+ return false;
367
+ };
368
+ return remove(this.root);
369
+ }
370
+ flatten() {
371
+ const result = [];
372
+ const collect = (node) => {
373
+ if (node !== this.root) {
374
+ result.push(node);
375
+ }
376
+ for (const child of node.children) {
377
+ collect(child);
378
+ }
379
+ };
380
+ collect(this.root);
381
+ return result;
382
+ }
383
+ getLayouts() {
384
+ return this.flatten().filter((n) => n.layout);
385
+ }
386
+ getLayoutChain(routeId) {
387
+ const node = this.findById(routeId);
388
+ if (!node)
389
+ return [];
390
+ const layouts = [];
391
+ let current = node.parent;
392
+ while (current) {
393
+ if (current.layout) {
394
+ layouts.unshift(current);
395
+ }
396
+ current = current.parent;
397
+ }
398
+ return layouts;
399
+ }
400
+ }
401
+ function filePathToUrlPath(filePath, routesDir) {
402
+ let path = filePath.replace(routesDir, "").replace(/\.(tsx?|jsx?)$/, "");
403
+ const fileName = path.split("/").pop() || "";
404
+ const isLayout = fileName === SPECIAL_FILES.LAYOUT;
405
+ const isIndex = fileName === "index";
406
+ if (isLayout || isIndex) {
407
+ path = path.replace(/\/?(index|_layout)$/, "");
408
+ }
409
+ path = path.replace(/\/\([^)]+\)/g, "");
410
+ if (!path.startsWith("/")) {
411
+ path = "/" + path;
412
+ }
413
+ if (path !== "/" && path.endsWith("/")) {
414
+ path = path.slice(0, -1);
415
+ }
416
+ return {
417
+ path: path || "/",
418
+ index: isIndex,
419
+ layout: isLayout
420
+ };
421
+ }
422
+ function buildRouteTree(files, routesDir) {
423
+ const tree = new RouteTree;
424
+ const sortedFiles = [...files].sort((a, b) => {
425
+ const aIsLayout = a.relativePath.includes(SPECIAL_FILES.LAYOUT);
426
+ const bIsLayout = b.relativePath.includes(SPECIAL_FILES.LAYOUT);
427
+ if (aIsLayout && !bIsLayout)
428
+ return -1;
429
+ if (!aIsLayout && bIsLayout)
430
+ return 1;
431
+ return a.relativePath.localeCompare(b.relativePath);
432
+ });
433
+ for (const file of sortedFiles) {
434
+ const { path, index, layout } = filePathToUrlPath(file.relativePath, routesDir);
435
+ const id = file.relativePath.replace(/\.(tsx?|jsx?)$/, "");
436
+ tree.addRoute(id, path, file.absolutePath, { index, layout });
437
+ }
438
+ return tree;
439
+ }
440
+ function createRouteTree() {
441
+ return new RouteTree;
442
+ }
443
+
444
+ // src/route-config.ts
445
+ var defaultRenderConfig = {
446
+ mode: "ssr",
447
+ streaming: { enabled: true }
448
+ };
449
+ var defaultIslandsConfig = {
450
+ defaultStrategy: "load",
451
+ disabled: false
452
+ };
453
+ var defaultProgressiveConfig = {
454
+ forms: { fallback: "server", redirect: "follow" },
455
+ prefetch: { trigger: "hover", data: true, ttl: 60000 }
456
+ };
457
+ function parseMiddleware(middleware) {
458
+ if (!middleware)
459
+ return;
460
+ if (!Array.isArray(middleware)) {
461
+ throw new Error("Middleware must be an array");
462
+ }
463
+ return middleware.map((item) => {
464
+ if (typeof item === "string")
465
+ return item;
466
+ if (typeof item === "function")
467
+ return item;
468
+ throw new Error(`Invalid middleware item: ${item}`);
469
+ });
470
+ }
471
+ function parseRenderConfig(config) {
472
+ if (!config || typeof config !== "object") {
473
+ return defaultRenderConfig;
474
+ }
475
+ const c = config;
476
+ const mode = c.mode || "ssr";
477
+ if (!["ssg", "ssr", "csr", "json", "xml"].includes(mode)) {
478
+ throw new Error(`Invalid render mode: ${mode}`);
479
+ }
480
+ return {
481
+ mode,
482
+ prerender: parsePrerenderConfig(c.prerender),
483
+ streaming: parseStreamingConfig(c.streaming),
484
+ csr: parseCSRConfig(c.csr)
485
+ };
486
+ }
487
+ function parsePrerenderConfig(config) {
488
+ if (!config || typeof config !== "object")
489
+ return;
490
+ const c = config;
491
+ return {
492
+ enabled: Boolean(c.enabled),
493
+ paths: parsePaths(c.paths),
494
+ revalidate: typeof c.revalidate === "number" ? c.revalidate : undefined,
495
+ tags: parseTags(c.tags),
496
+ fallback: c.fallback || "blocking"
497
+ };
498
+ }
499
+ function parsePaths(paths) {
500
+ if (!paths)
501
+ return;
502
+ if (Array.isArray(paths))
503
+ return paths;
504
+ if (typeof paths === "function")
505
+ return paths;
506
+ throw new Error("Invalid prerender paths");
507
+ }
508
+ function parseTags(tags) {
509
+ if (!tags)
510
+ return;
511
+ if (Array.isArray(tags))
512
+ return tags;
513
+ if (typeof tags === "function")
514
+ return tags;
515
+ throw new Error("Invalid cache tags");
516
+ }
517
+ function parseStreamingConfig(config) {
518
+ if (!config || typeof config !== "object")
519
+ return;
520
+ const c = config;
521
+ return {
522
+ enabled: Boolean(c.enabled),
523
+ suspenseBoundaries: Array.isArray(c.suspenseBoundaries) ? c.suspenseBoundaries : undefined
524
+ };
525
+ }
526
+ function parseCSRConfig(config) {
527
+ if (!config || typeof config !== "object")
528
+ return;
529
+ const c = config;
530
+ return {
531
+ enabled: Boolean(c.enabled),
532
+ clientLoader: typeof c.clientLoader === "function" ? c.clientLoader : undefined
533
+ };
534
+ }
535
+ function parseIslandsConfig(config) {
536
+ if (!config || typeof config !== "object") {
537
+ return defaultIslandsConfig;
538
+ }
539
+ const c = config;
540
+ const strategies = ["load", "idle", "visible", "media", "none"];
541
+ const defaultStrategy = c.defaultStrategy || "load";
542
+ if (!strategies.includes(defaultStrategy)) {
543
+ throw new Error(`Invalid hydration strategy: ${defaultStrategy}`);
544
+ }
545
+ return {
546
+ defaultStrategy,
547
+ components: Array.isArray(c.components) ? c.components : undefined,
548
+ disabled: Boolean(c.disabled)
549
+ };
550
+ }
551
+ function parseCacheConfig(config) {
552
+ if (!config || typeof config !== "object")
553
+ return;
554
+ const c = config;
555
+ return {
556
+ edge: parseEdgeCache(c.edge),
557
+ browser: parseBrowserCache(c.browser),
558
+ data: parseDataCache(c.data)
559
+ };
560
+ }
561
+ function parseEdgeCache(config) {
562
+ if (!config || typeof config !== "object")
563
+ return;
564
+ const c = config;
565
+ return {
566
+ maxAge: typeof c.maxAge === "number" ? c.maxAge : 0,
567
+ staleWhileRevalidate: typeof c.staleWhileRevalidate === "number" ? c.staleWhileRevalidate : undefined,
568
+ vary: Array.isArray(c.vary) ? c.vary : undefined,
569
+ keyGenerator: typeof c.keyGenerator === "function" ? c.keyGenerator : undefined
570
+ };
571
+ }
572
+ function parseBrowserCache(config) {
573
+ if (!config || typeof config !== "object")
574
+ return;
575
+ const c = config;
576
+ return {
577
+ maxAge: typeof c.maxAge === "number" ? c.maxAge : 0,
578
+ private: Boolean(c.private)
579
+ };
580
+ }
581
+ function parseDataCache(config) {
582
+ if (!config || typeof config !== "object")
583
+ return;
584
+ const c = config;
585
+ return {
586
+ key: typeof c.key === "string" ? c.key : typeof c.key === "function" ? c.key : undefined,
587
+ tags: parseTags(c.tags)
588
+ };
589
+ }
590
+ function parseProgressiveConfig(config) {
591
+ if (!config || typeof config !== "object") {
592
+ return defaultProgressiveConfig;
593
+ }
594
+ const c = config;
595
+ return {
596
+ forms: parseFormsConfig(c.forms),
597
+ prefetch: parsePrefetchConfig(c.prefetch)
598
+ };
599
+ }
600
+ function parseFormsConfig(config) {
601
+ if (!config || typeof config !== "object") {
602
+ return defaultProgressiveConfig.forms;
603
+ }
604
+ const c = config;
605
+ const fallback = c.fallback || "server";
606
+ if (!["server", "spa"].includes(fallback)) {
607
+ throw new Error(`Invalid form fallback: ${fallback}`);
608
+ }
609
+ return {
610
+ fallback,
611
+ redirect: c.redirect || "follow"
612
+ };
613
+ }
614
+ function parsePrefetchConfig(config) {
615
+ if (!config || typeof config !== "object") {
616
+ return defaultProgressiveConfig.prefetch;
617
+ }
618
+ const c = config;
619
+ const trigger = c.trigger || "hover";
620
+ if (!["hover", "visible", "intent", "never"].includes(trigger)) {
621
+ throw new Error(`Invalid prefetch trigger: ${trigger}`);
622
+ }
623
+ return {
624
+ trigger,
625
+ data: Boolean(c.data),
626
+ ttl: typeof c.ttl === "number" ? c.ttl : 60000
627
+ };
628
+ }
629
+ function parseAuthConfig(config) {
630
+ if (!config || typeof config !== "object")
631
+ return;
632
+ const c = config;
633
+ return {
634
+ required: Boolean(c.required),
635
+ roles: Array.isArray(c.roles) ? c.roles : undefined,
636
+ permissions: Array.isArray(c.permissions) ? c.permissions : undefined,
637
+ check: typeof c.check === "function" ? c.check : undefined,
638
+ redirect: typeof c.redirect === "string" ? c.redirect : undefined,
639
+ unauthorized: typeof c.unauthorized === "object" ? c.unauthorized : undefined
640
+ };
641
+ }
642
+ function parseDevConfig(config) {
643
+ if (!config || typeof config !== "object")
644
+ return;
645
+ const c = config;
646
+ return {
647
+ mock: parseMockConfig(c.mock),
648
+ latency: typeof c.latency === "number" ? c.latency : undefined,
649
+ errorRate: typeof c.errorRate === "number" ? c.errorRate : undefined
650
+ };
651
+ }
652
+ function parseMockConfig(config) {
653
+ if (!config || typeof config !== "object")
654
+ return;
655
+ const c = config;
656
+ return {
657
+ enabled: Boolean(c.enabled),
658
+ data: typeof c.data === "object" ? c.data : undefined
659
+ };
660
+ }
661
+ function parseVariants(config) {
662
+ if (config === undefined)
663
+ return;
664
+ if (!Array.isArray(config)) {
665
+ throw new Error("Variants must be an array");
666
+ }
667
+ return config.map((variant, index) => {
668
+ if (!variant || typeof variant !== "object") {
669
+ throw new Error(`Invalid variant at index ${index}`);
670
+ }
671
+ const v = variant;
672
+ if (!v.path || typeof v.path !== "string") {
673
+ throw new Error(`Variant at index ${index} missing path`);
674
+ }
675
+ return {
676
+ path: v.path,
677
+ params: typeof v.params === "object" ? v.params : undefined,
678
+ config: v.config ? parseRouteConfig(v.config) : undefined
679
+ };
680
+ });
681
+ }
682
+ function parseRouteConfig(config) {
683
+ if (!config || typeof config !== "object") {
684
+ return {};
685
+ }
686
+ const c = config;
687
+ return {
688
+ middleware: parseMiddleware(c.middleware),
689
+ render: parseRenderConfig(c.render),
690
+ islands: parseIslandsConfig(c.islands),
691
+ cache: parseCacheConfig(c.cache),
692
+ progressive: parseProgressiveConfig(c.progressive),
693
+ auth: parseAuthConfig(c.auth),
694
+ dev: parseDevConfig(c.dev),
695
+ variants: parseVariants(c.variants)
696
+ };
697
+ }
698
+ function mergeRouteConfigs(parent, child) {
699
+ if (!parent)
700
+ return child || {};
701
+ if (!child)
702
+ return parent;
703
+ return {
704
+ middleware: [...parent.middleware || [], ...child.middleware || []],
705
+ render: child.render || parent.render,
706
+ islands: child.islands || parent.islands,
707
+ cache: child.cache || parent.cache,
708
+ progressive: child.progressive || parent.progressive,
709
+ route: child.route || parent.route,
710
+ auth: child.auth || parent.auth,
711
+ dev: child.dev || parent.dev,
712
+ variants: child.variants || parent.variants
713
+ };
714
+ }
715
+
716
+ // src/file-router.ts
717
+ var defaultOptions = {
718
+ routesDir: "app/routes",
719
+ basePath: "",
720
+ extensions: [".tsx", ".ts", ".jsx", ".js"],
721
+ watch: false
722
+ };
723
+
724
+ class FileRouter {
725
+ options;
726
+ routesDir;
727
+ routes = [];
728
+ tree = null;
729
+ matcher = null;
730
+ watcher = null;
731
+ eventHandlers = {};
732
+ constructor(options = {}) {
733
+ this.options = { ...defaultOptions, ...options };
734
+ const routesDir = this.options.routesDir;
735
+ this.routesDir = routesDir.startsWith("/") ? routesDir : join(process.cwd(), routesDir);
736
+ }
737
+ async init() {
738
+ await this.discoverRoutes();
739
+ if (this.options.watch) {
740
+ this.startWatching();
741
+ }
742
+ }
743
+ async discoverRoutes() {
744
+ const files = await this.scanDirectory(this.routesDir);
745
+ this.tree = buildRouteTree(files.map((f) => ({
746
+ relativePath: f.relativePath,
747
+ absolutePath: f.absolutePath
748
+ })), "");
749
+ this.routes = this.tree.toRoutes();
750
+ this.matcher = createMatcher(this.routes);
751
+ this.emit("reload", this.routes);
752
+ return this.routes;
753
+ }
754
+ async scanDirectory(dir, base = "") {
755
+ const files = [];
756
+ try {
757
+ const entries = await readdir(dir, { withFileTypes: true });
758
+ for (const entry of entries) {
759
+ const fullPath = join(dir, entry.name);
760
+ const relativePath = join(base, entry.name);
761
+ if (entry.isDirectory()) {
762
+ const subFiles = await this.scanDirectory(fullPath, relativePath);
763
+ files.push(...subFiles);
764
+ } else if (entry.isFile()) {
765
+ const ext = extname(entry.name);
766
+ if (this.options.extensions.includes(ext)) {
767
+ files.push({
768
+ relativePath: "/" + relativePath,
769
+ absolutePath: fullPath,
770
+ extension: ext
771
+ });
772
+ }
773
+ }
774
+ }
775
+ } catch (error) {
776
+ if (error.code !== "ENOENT") {
777
+ throw error;
778
+ }
779
+ }
780
+ return files;
781
+ }
782
+ startWatching() {
783
+ this.watchWithNode();
784
+ }
785
+ watchWithNode() {
786
+ const { watch } = __require("fs");
787
+ try {
788
+ this.watcher = watch(this.routesDir, { recursive: true }, (event, filename) => {
789
+ if (!filename)
790
+ return;
791
+ const ext = extname(filename);
792
+ if (!this.options.extensions.includes(ext))
793
+ return;
794
+ this.handleFileChange(filename, event);
795
+ });
796
+ } catch (error) {
797
+ console.warn("File watching not available:", error);
798
+ }
799
+ }
800
+ debounceTimer = null;
801
+ handleFileChange(filename, event) {
802
+ if (this.debounceTimer) {
803
+ clearTimeout(this.debounceTimer);
804
+ }
805
+ this.debounceTimer = setTimeout(async () => {
806
+ const fullPath = join(this.routesDir, filename);
807
+ try {
808
+ const stats = await stat(fullPath);
809
+ if (stats.isFile()) {
810
+ if (event === "rename") {
811
+ await this.discoverRoutes();
812
+ } else {
813
+ const routeId = "/" + filename.replace(/\.(tsx?|jsx?)$/, "");
814
+ const node = this.tree?.findById(routeId);
815
+ if (node) {
816
+ delete node.module;
817
+ this.emit("change", this.nodeToRoute(node));
818
+ }
819
+ }
820
+ }
821
+ } catch (error) {
822
+ if (error.code === "ENOENT") {
823
+ const routeId = "/" + filename.replace(/\.(tsx?|jsx?)$/, "");
824
+ this.tree?.removeById(routeId);
825
+ this.routes = this.tree?.toRoutes() || [];
826
+ this.matcher = createMatcher(this.routes);
827
+ this.emit("remove", routeId);
828
+ }
829
+ }
830
+ }, 50);
831
+ }
832
+ nodeToRoute(node) {
833
+ return {
834
+ id: node.id,
835
+ path: node.path,
836
+ file: node.file,
837
+ index: node.index,
838
+ layout: node.layout,
839
+ module: node.module,
840
+ children: node.children?.map((c) => this.nodeToRoute(c))
841
+ };
842
+ }
843
+ stopWatching() {
844
+ if (this.watcher) {
845
+ this.watcher.close();
846
+ this.watcher = null;
847
+ }
848
+ }
849
+ getRoutes() {
850
+ return this.routes;
851
+ }
852
+ getTree() {
853
+ return this.tree;
854
+ }
855
+ getMatcher() {
856
+ return this.matcher;
857
+ }
858
+ match(pathname) {
859
+ return this.matcher?.match(pathname) ?? null;
860
+ }
861
+ on(event, handler) {
862
+ this.eventHandlers[event] = handler;
863
+ }
864
+ emit(event, ...args) {
865
+ const handler = this.eventHandlers[event];
866
+ if (handler) {
867
+ handler(...args);
868
+ }
869
+ }
870
+ async loadModule(route) {
871
+ if (route.module)
872
+ return;
873
+ try {
874
+ const mod = await import(route.file);
875
+ route.module = mod;
876
+ if (mod.config) {
877
+ route.config = parseRouteConfig(mod.config);
878
+ }
879
+ const parent = this.findParentRoute(route);
880
+ if (parent?.config) {
881
+ route.config = mergeRouteConfigs(parent.config, route.config);
882
+ }
883
+ } catch (error) {
884
+ console.error(`Failed to load route module: ${route.file}`, error);
885
+ throw error;
886
+ }
887
+ }
888
+ findParentRoute(route) {
889
+ const parentId = route.id.split("/").slice(0, -1).join("/") || "/";
890
+ return this.routes.find((r) => r.id === parentId);
891
+ }
892
+ async getRouteConfig(route) {
893
+ if (!route.config && !route.module) {
894
+ await this.loadModule(route);
895
+ }
896
+ return route.config;
897
+ }
898
+ async getRoutesWithConfig() {
899
+ const loadPromises = this.routes.map(async (route) => {
900
+ if (!route.config && !route.module) {
901
+ await this.loadModule(route);
902
+ }
903
+ });
904
+ await Promise.all(loadPromises);
905
+ return this.routes;
906
+ }
907
+ async findRoutesByRenderMode(mode) {
908
+ const routes = await this.getRoutesWithConfig();
909
+ return routes.filter((r) => r.config?.render?.mode === mode);
910
+ }
911
+ async findProtectedRoutes() {
912
+ const routes = await this.getRoutesWithConfig();
913
+ return routes.filter((r) => r.config?.auth?.required);
914
+ }
915
+ async getPrerenderPaths() {
916
+ const routes = await this.findRoutesByRenderMode("ssg");
917
+ const paths = [];
918
+ for (const route of routes) {
919
+ const prerender = route.config?.render?.prerender;
920
+ if (prerender?.enabled && prerender.paths) {
921
+ if (Array.isArray(prerender.paths)) {
922
+ paths.push(...prerender.paths);
923
+ } else if (typeof prerender.paths === "function") {
924
+ const result = await prerender.paths();
925
+ paths.push(...Array.isArray(result) ? result : []);
926
+ }
927
+ }
928
+ }
929
+ return paths;
930
+ }
931
+ async loadAllModules() {
932
+ const loadRecursive = async (routes) => {
933
+ for (const route of routes) {
934
+ await this.loadModule(route);
935
+ if (route.children) {
936
+ await loadRecursive(route.children);
937
+ }
938
+ }
939
+ };
940
+ await loadRecursive(this.routes);
941
+ }
942
+ }
943
+ function createFileRouter(options) {
944
+ return new FileRouter(options);
945
+ }
946
+ async function initFileRouter(options) {
947
+ const router = createFileRouter(options);
948
+ await router.init();
949
+ return router;
950
+ }
951
+ // src/middleware-chain.ts
952
+ function createMiddleware(config) {
953
+ return {
954
+ ...config,
955
+ register() {
956
+ registerMiddleware(config.name, config.handler);
957
+ }
958
+ };
959
+ }
960
+ function chainMiddleware(...middlewares) {
961
+ const combinedProvides = middlewares.flatMap((m) => m.provides || []);
962
+ const combinedRequires = middlewares.flatMap((m) => m.requires || []);
963
+ return {
964
+ name: middlewares.map((m) => m.name).join("+"),
965
+ provides: combinedProvides,
966
+ requires: combinedRequires,
967
+ handler: composeMiddleware(...middlewares.map((m) => m.handler))
968
+ };
969
+ }
970
+ var namedMiddlewareRegistry = new Map;
971
+ var typedMiddlewareRegistry = new Map;
972
+ function registerMiddleware(name, handler) {
973
+ namedMiddlewareRegistry.set(name, handler);
974
+ }
975
+ function registerTypedMiddleware(middleware) {
976
+ typedMiddlewareRegistry.set(middleware.name, middleware);
977
+ namedMiddlewareRegistry.set(middleware.name, middleware.handler);
978
+ }
979
+ function getTypedMiddleware(name) {
980
+ return typedMiddlewareRegistry.get(name);
981
+ }
982
+ function validateMiddlewareChain(names) {
983
+ const errors = [];
984
+ const providedKeys = new Set;
985
+ for (const name of names) {
986
+ const middleware = typedMiddlewareRegistry.get(name);
987
+ if (!middleware)
988
+ continue;
989
+ const requires = middleware.requires || [];
990
+ for (const key of requires) {
991
+ if (!providedKeys.has(key)) {
992
+ errors.push(`Middleware '${name}' requires '${String(key)}' but it's not provided by preceding middleware`);
993
+ }
994
+ }
995
+ const provides = middleware.provides || [];
996
+ for (const key of provides) {
997
+ providedKeys.add(key);
998
+ }
999
+ }
1000
+ return {
1001
+ valid: errors.length === 0,
1002
+ errors
1003
+ };
1004
+ }
1005
+ function getMiddleware(name) {
1006
+ return namedMiddlewareRegistry.get(name);
1007
+ }
1008
+ function hasMiddleware(name) {
1009
+ return namedMiddlewareRegistry.has(name);
1010
+ }
1011
+ function unregisterMiddleware(name) {
1012
+ return namedMiddlewareRegistry.delete(name);
1013
+ }
1014
+ function clearMiddlewareRegistry() {
1015
+ namedMiddlewareRegistry.clear();
1016
+ }
1017
+ function resolveMiddleware(reference) {
1018
+ if (typeof reference === "function") {
1019
+ return reference;
1020
+ }
1021
+ return namedMiddlewareRegistry.get(reference);
1022
+ }
1023
+ async function executeMiddlewareChain(middleware, options) {
1024
+ const { request, context, finalHandler, onError } = options;
1025
+ const handlers = [];
1026
+ for (const ref of middleware) {
1027
+ const handler = resolveMiddleware(ref);
1028
+ if (!handler) {
1029
+ if (typeof ref === "string") {
1030
+ throw new Error(`Named middleware not found: ${ref}`);
1031
+ }
1032
+ throw new Error("Invalid middleware reference");
1033
+ }
1034
+ handlers.push(handler);
1035
+ }
1036
+ let index = 0;
1037
+ const next = async () => {
1038
+ if (index >= handlers.length) {
1039
+ return finalHandler();
1040
+ }
1041
+ const handler = handlers[index++];
1042
+ try {
1043
+ return await handler(request, context, next);
1044
+ } catch (error) {
1045
+ if (onError) {
1046
+ return onError(error instanceof Error ? error : new Error(String(error)));
1047
+ }
1048
+ throw error;
1049
+ }
1050
+ };
1051
+ return next();
1052
+ }
1053
+ function createMiddlewareExecutor(config) {
1054
+ const middleware = config.middleware || [];
1055
+ return async (options) => {
1056
+ return executeMiddlewareChain(middleware, options);
1057
+ };
1058
+ }
1059
+ function composeMiddleware(...handlers) {
1060
+ return async (request, context, next) => {
1061
+ let index = 0;
1062
+ const composedNext = async () => {
1063
+ if (index >= handlers.length) {
1064
+ return next();
1065
+ }
1066
+ const handler = handlers[index++];
1067
+ return handler(request, context, composedNext);
1068
+ };
1069
+ return composedNext();
1070
+ };
1071
+ }
1072
+ function when(predicate, middleware) {
1073
+ return async (request, context, next) => {
1074
+ const shouldRun = await predicate(request, context);
1075
+ if (shouldRun) {
1076
+ return middleware(request, context, next);
1077
+ }
1078
+ return next();
1079
+ };
1080
+ }
1081
+ function method(methods, middleware) {
1082
+ const methodSet = new Set(Array.isArray(methods) ? methods : [methods]);
1083
+ return when((request) => methodSet.has(request.method), middleware);
1084
+ }
1085
+ function globToRegex(glob) {
1086
+ const escaped = glob.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*\*/g, "{{GLOBSTAR}}").replace(/\*/g, "[^/]*").replace(/\?/g, "[^/]").replace(/\{\{GLOBSTAR\}\}/g, ".*");
1087
+ return new RegExp(`^${escaped}$`);
1088
+ }
1089
+ function path(patterns, middleware) {
1090
+ const patternList = Array.isArray(patterns) ? patterns : [patterns];
1091
+ return when((request) => {
1092
+ const url = new URL(request.url);
1093
+ return patternList.some((pattern) => {
1094
+ if (typeof pattern === "string") {
1095
+ if (url.pathname === pattern)
1096
+ return true;
1097
+ if (pattern.endsWith("/*")) {
1098
+ const prefix = pattern.slice(0, -1);
1099
+ return url.pathname.startsWith(prefix);
1100
+ }
1101
+ return url.pathname.startsWith(pattern);
1102
+ }
1103
+ return pattern.test(url.pathname);
1104
+ });
1105
+ }, middleware);
1106
+ }
1107
+ function createLoggerMiddleware(options = {}) {
1108
+ return async (request, context, next) => {
1109
+ const startTime = Date.now();
1110
+ const url = new URL(request.url);
1111
+ const logData = {
1112
+ method: request.method,
1113
+ pathname: url.pathname,
1114
+ timestamp: new Date().toISOString()
1115
+ };
1116
+ if (options.includeHeaders) {
1117
+ logData.headers = Object.fromEntries(options.includeHeaders.map((h) => [h, request.headers.get(h)]));
1118
+ }
1119
+ try {
1120
+ const response = await next();
1121
+ const duration = Date.now() - startTime;
1122
+ console.log({
1123
+ ...logData,
1124
+ status: response.status,
1125
+ duration: `${duration}ms`
1126
+ });
1127
+ return response;
1128
+ } catch (error) {
1129
+ const duration = Date.now() - startTime;
1130
+ console.error({
1131
+ ...logData,
1132
+ error: error instanceof Error ? error.message : String(error),
1133
+ duration: `${duration}ms`
1134
+ });
1135
+ throw error;
1136
+ }
1137
+ };
1138
+ }
1139
+ function createCorsMiddleware(options = {}) {
1140
+ const {
1141
+ origin = "*",
1142
+ methods = ["GET", "HEAD", "PUT", "PATCH", "POST", "DELETE"],
1143
+ headers = [],
1144
+ credentials = false,
1145
+ maxAge = 86400
1146
+ } = options;
1147
+ return async (request, context, next) => {
1148
+ const response = await next();
1149
+ const corsHeaders = new Headers(response.headers);
1150
+ if (typeof origin === "string") {
1151
+ corsHeaders.set("Access-Control-Allow-Origin", origin);
1152
+ } else if (Array.isArray(origin)) {
1153
+ const requestOrigin = request.headers.get("Origin");
1154
+ if (requestOrigin && origin.includes(requestOrigin)) {
1155
+ corsHeaders.set("Access-Control-Allow-Origin", requestOrigin);
1156
+ }
1157
+ } else if (typeof origin === "function") {
1158
+ const requestOrigin = request.headers.get("Origin") || "";
1159
+ if (origin(requestOrigin)) {
1160
+ corsHeaders.set("Access-Control-Allow-Origin", requestOrigin);
1161
+ }
1162
+ }
1163
+ corsHeaders.set("Access-Control-Allow-Methods", methods.join(", "));
1164
+ if (headers.length > 0) {
1165
+ corsHeaders.set("Access-Control-Allow-Headers", headers.join(", "));
1166
+ }
1167
+ if (credentials) {
1168
+ corsHeaders.set("Access-Control-Allow-Credentials", "true");
1169
+ }
1170
+ corsHeaders.set("Access-Control-Max-Age", String(maxAge));
1171
+ if (request.method === "OPTIONS") {
1172
+ return new Response(null, {
1173
+ status: 204,
1174
+ headers: corsHeaders
1175
+ });
1176
+ }
1177
+ return new Response(response.body, {
1178
+ status: response.status,
1179
+ statusText: response.statusText,
1180
+ headers: corsHeaders
1181
+ });
1182
+ };
1183
+ }
1184
+ function createRateLimitMiddleware(options = {}) {
1185
+ const {
1186
+ windowMs = 60000,
1187
+ maxRequests = 100,
1188
+ keyGenerator = (req) => req.headers.get("X-Forwarded-For") || "unknown",
1189
+ skipSuccessfulRequests = false
1190
+ } = options;
1191
+ const store = new Map;
1192
+ return async (request, context, next) => {
1193
+ const key = keyGenerator(request);
1194
+ const now = Date.now();
1195
+ let record = store.get(key);
1196
+ if (!record || now > record.resetTime) {
1197
+ record = { count: 0, resetTime: now + windowMs };
1198
+ }
1199
+ record.count++;
1200
+ store.set(key, record);
1201
+ if (record.count > maxRequests) {
1202
+ return new Response("Rate limit exceeded", {
1203
+ status: 429,
1204
+ headers: {
1205
+ "Retry-After": String(Math.ceil((record.resetTime - now) / 1000))
1206
+ }
1207
+ });
1208
+ }
1209
+ const response = await next();
1210
+ const newResponse = new Response(response.body, {
1211
+ status: response.status,
1212
+ statusText: response.statusText,
1213
+ headers: response.headers
1214
+ });
1215
+ newResponse.headers.set("X-RateLimit-Limit", String(maxRequests));
1216
+ newResponse.headers.set("X-RateLimit-Remaining", String(Math.max(0, maxRequests - record.count)));
1217
+ newResponse.headers.set("X-RateLimit-Reset", String(Math.ceil(record.resetTime / 1000)));
1218
+ if (skipSuccessfulRequests && response.status < 400) {
1219
+ store.delete(key);
1220
+ }
1221
+ return newResponse;
1222
+ };
1223
+ }
1224
+ // src/validation.ts
1225
+ class ParamValidationError extends Error {
1226
+ field;
1227
+ value;
1228
+ constructor(message, field, value) {
1229
+ super(message);
1230
+ this.field = field;
1231
+ this.value = value;
1232
+ this.name = "ParamValidationError";
1233
+ }
1234
+ }
1235
+ var validators = {
1236
+ string: (options = {}) => ({
1237
+ parse: (value) => {
1238
+ if (value === undefined) {
1239
+ throw new ParamValidationError("Value is required", "value", value);
1240
+ }
1241
+ const str = Array.isArray(value) ? value[0] : value;
1242
+ if (typeof str !== "string") {
1243
+ throw new ParamValidationError("Value must be a string", "value", value);
1244
+ }
1245
+ if (options.min !== undefined && str.length < options.min) {
1246
+ throw new ParamValidationError(`String too short (min ${options.min})`, "value", str);
1247
+ }
1248
+ if (options.max !== undefined && str.length > options.max) {
1249
+ throw new ParamValidationError(`String too long (max ${options.max})`, "value", str);
1250
+ }
1251
+ if (options.regex && !options.regex.test(str)) {
1252
+ throw new ParamValidationError("String does not match pattern", "value", str);
1253
+ }
1254
+ return str;
1255
+ }
1256
+ }),
1257
+ number: (options = {}) => ({
1258
+ parse: (value) => {
1259
+ if (value === undefined) {
1260
+ throw new ParamValidationError("Value is required", "value", value);
1261
+ }
1262
+ const str = Array.isArray(value) ? value[0] : value;
1263
+ const num = Number(str);
1264
+ if (isNaN(num)) {
1265
+ throw new ParamValidationError("Value must be a number", "value", value);
1266
+ }
1267
+ if (options.integer && !Number.isInteger(num)) {
1268
+ throw new ParamValidationError("Value must be an integer", "value", num);
1269
+ }
1270
+ if (options.min !== undefined && num < options.min) {
1271
+ throw new ParamValidationError(`Number too small (min ${options.min})`, "value", num);
1272
+ }
1273
+ if (options.max !== undefined && num > options.max) {
1274
+ throw new ParamValidationError(`Number too large (max ${options.max})`, "value", num);
1275
+ }
1276
+ return num;
1277
+ }
1278
+ }),
1279
+ int: (options = {}) => validators.number({ ...options, integer: true }),
1280
+ boolean: () => ({
1281
+ parse: (value) => {
1282
+ if (value === undefined) {
1283
+ throw new ParamValidationError("Value is required", "value", value);
1284
+ }
1285
+ const str = Array.isArray(value) ? value[0] : value;
1286
+ const lower = str.toLowerCase();
1287
+ if (lower === "true" || lower === "1" || lower === "yes")
1288
+ return true;
1289
+ if (lower === "false" || lower === "0" || lower === "no")
1290
+ return false;
1291
+ throw new ParamValidationError("Value must be a boolean", "value", value);
1292
+ }
1293
+ }),
1294
+ enum: (values) => ({
1295
+ parse: (value) => {
1296
+ if (value === undefined) {
1297
+ throw new ParamValidationError("Value is required", "value", value);
1298
+ }
1299
+ const str = Array.isArray(value) ? value[0] : value;
1300
+ if (!values.includes(str)) {
1301
+ throw new ParamValidationError(`Value must be one of: ${values.join(", ")}`, "value", str);
1302
+ }
1303
+ return str;
1304
+ }
1305
+ }),
1306
+ array: (itemValidator) => ({
1307
+ parse: (value) => {
1308
+ if (value === undefined) {
1309
+ return [];
1310
+ }
1311
+ const arr = Array.isArray(value) ? value : [value];
1312
+ return arr.map((item) => itemValidator.parse(item));
1313
+ }
1314
+ }),
1315
+ optional: (validator) => ({
1316
+ parse: (value) => {
1317
+ if (value === undefined)
1318
+ return;
1319
+ return validator.parse(value);
1320
+ }
1321
+ }),
1322
+ default: (validator, defaultValue) => ({
1323
+ parse: (value) => {
1324
+ if (value === undefined)
1325
+ return defaultValue;
1326
+ return validator.parse(value);
1327
+ }
1328
+ })
1329
+ };
1330
+ function validateParams(params, schema) {
1331
+ const result = {};
1332
+ const errors = [];
1333
+ for (const [key, validator] of Object.entries(schema)) {
1334
+ try {
1335
+ result[key] = validator.parse(params[key]);
1336
+ } catch (error) {
1337
+ if (error instanceof ParamValidationError) {
1338
+ errors.push(new ParamValidationError(error.message, key, params[key]));
1339
+ } else {
1340
+ errors.push(new ParamValidationError(String(error), key, params[key]));
1341
+ }
1342
+ }
1343
+ }
1344
+ if (errors.length > 0) {
1345
+ const firstError = errors[0];
1346
+ throw new ParamValidationError(`Parameter validation failed: ${firstError.message}`, firstError.field, firstError.value);
1347
+ }
1348
+ return result;
1349
+ }
1350
+ function safeValidateParams(params, schema) {
1351
+ try {
1352
+ const data = validateParams(params, schema);
1353
+ return { valid: true, data };
1354
+ } catch (error) {
1355
+ if (error instanceof ParamValidationError) {
1356
+ return { valid: false, errors: [error] };
1357
+ }
1358
+ return {
1359
+ valid: false,
1360
+ errors: [new ParamValidationError(String(error), "unknown", params)]
1361
+ };
1362
+ }
1363
+ }
1364
+ function validateSearchParams(searchParams, schema) {
1365
+ let params;
1366
+ if (typeof searchParams === "string") {
1367
+ const url = new URL(`http://localhost:3000?${searchParams}`);
1368
+ params = Object.fromEntries(url.searchParams.entries());
1369
+ } else if (searchParams instanceof URLSearchParams) {
1370
+ params = Object.fromEntries(searchParams.entries());
1371
+ } else {
1372
+ params = searchParams;
1373
+ }
1374
+ const result = {};
1375
+ for (const [key, validatorDef] of Object.entries(schema)) {
1376
+ const value = params[key];
1377
+ if (typeof validatorDef === "function" || "parse" in validatorDef) {
1378
+ const validator = validatorDef;
1379
+ result[key] = validator.parse(value);
1380
+ } else if ("default" in validatorDef) {
1381
+ const def = validatorDef;
1382
+ if (value === undefined) {
1383
+ result[key] = def.default;
1384
+ } else if (def.validator) {
1385
+ result[key] = def.validator.parse(value);
1386
+ } else {
1387
+ result[key] = value;
1388
+ }
1389
+ }
1390
+ }
1391
+ return result;
1392
+ }
1393
+ function createRouteValidator(options) {
1394
+ const validate = (routeParams, searchParams) => {
1395
+ const validatedParams = options.params ? validateParams(routeParams, options.params) : {};
1396
+ const validatedSearch = options.searchParams ? validateSearchParams(searchParams, options.searchParams) : {};
1397
+ return {
1398
+ params: validatedParams,
1399
+ searchParams: validatedSearch
1400
+ };
1401
+ };
1402
+ return {
1403
+ validate,
1404
+ safeValidate: (routeParams, searchParams) => {
1405
+ try {
1406
+ return {
1407
+ valid: true,
1408
+ data: validate(routeParams, searchParams)
1409
+ };
1410
+ } catch (error) {
1411
+ return {
1412
+ valid: false,
1413
+ error: error instanceof ParamValidationError ? error : new ParamValidationError(String(error), "unknown", null)
1414
+ };
1415
+ }
1416
+ }
1417
+ };
1418
+ }
1419
+ function matchParamPattern(pattern, value) {
1420
+ if (pattern.startsWith("[...")) {
1421
+ return true;
1422
+ }
1423
+ if (pattern.startsWith("[[") && pattern.endsWith("]]")) {
1424
+ return value === "" || /^[^/]+$/.test(value);
1425
+ }
1426
+ if (pattern.startsWith("[") && pattern.endsWith("]")) {
1427
+ return /^[^/]+$/.test(value);
1428
+ }
1429
+ return pattern === value;
1430
+ }
1431
+ function extractParamNames(path2) {
1432
+ const names = [];
1433
+ const segments = path2.split("/");
1434
+ for (const segment of segments) {
1435
+ if (segment.startsWith("[...") && segment.endsWith("]")) {
1436
+ names.push(segment.slice(4, -1));
1437
+ } else if (segment.startsWith("[[[") && segment.endsWith("]]")) {
1438
+ names.push(segment.slice(3, -2));
1439
+ } else if (segment.startsWith("[[") && segment.endsWith("]]")) {
1440
+ names.push(segment.slice(2, -2));
1441
+ } else if (segment.startsWith("[") && segment.endsWith("]")) {
1442
+ names.push(segment.slice(1, -1));
1443
+ }
1444
+ }
1445
+ return names;
1446
+ }
1447
+ export {
1448
+ when,
1449
+ validators,
1450
+ validateSearchParams,
1451
+ validateParams,
1452
+ validateMiddlewareChain,
1453
+ unregisterMiddleware,
1454
+ safeValidateParams,
1455
+ resolveMiddleware,
1456
+ registerTypedMiddleware,
1457
+ registerMiddleware,
1458
+ patternToRegex,
1459
+ path,
1460
+ parseVariants,
1461
+ parseRouteConfig,
1462
+ parseRenderConfig,
1463
+ parseProgressiveConfig,
1464
+ parsePathSegments,
1465
+ parseMiddleware,
1466
+ parseIslandsConfig,
1467
+ parseDevConfig,
1468
+ parseCacheConfig,
1469
+ parseAuthConfig,
1470
+ method,
1471
+ mergeRouteConfigs,
1472
+ matchWithLayouts,
1473
+ matchRoute,
1474
+ matchParamPattern,
1475
+ initFileRouter,
1476
+ hasMiddleware,
1477
+ globToRegex,
1478
+ getTypedMiddleware,
1479
+ getMiddleware,
1480
+ filePathToUrlPath,
1481
+ extractParamNames,
1482
+ executeMiddlewareChain,
1483
+ createRouteValidator,
1484
+ createRouteTree,
1485
+ createRateLimitMiddleware,
1486
+ createMiddlewareExecutor,
1487
+ createMiddleware,
1488
+ createMatcher,
1489
+ createLoggerMiddleware,
1490
+ createFileRouter,
1491
+ createCorsMiddleware,
1492
+ composeMiddleware,
1493
+ clearMiddlewareRegistry,
1494
+ chainMiddleware,
1495
+ calculateRouteScore,
1496
+ buildRouteTree,
1497
+ SPECIAL_FILES,
1498
+ RouteTree,
1499
+ RouteMatcher,
1500
+ ROUTE_SCORE,
1501
+ ROUTE_GROUP_PATTERN,
1502
+ ParamValidationError,
1503
+ OPTIONAL_PATTERN,
1504
+ FileRouter,
1505
+ DYNAMIC_SEGMENT_PATTERN,
1506
+ CATCH_ALL_PATTERN
1507
+ };