vitek-plugin 0.1.0-beta

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +908 -0
  3. package/dist/adapters/vite/dev-server.d.ts +23 -0
  4. package/dist/adapters/vite/dev-server.d.ts.map +1 -0
  5. package/dist/adapters/vite/dev-server.js +428 -0
  6. package/dist/adapters/vite/logger.d.ts +33 -0
  7. package/dist/adapters/vite/logger.d.ts.map +1 -0
  8. package/dist/adapters/vite/logger.js +112 -0
  9. package/dist/core/context/create-context.d.ts +39 -0
  10. package/dist/core/context/create-context.d.ts.map +1 -0
  11. package/dist/core/context/create-context.js +34 -0
  12. package/dist/core/file-system/scan-api-dir.d.ts +26 -0
  13. package/dist/core/file-system/scan-api-dir.d.ts.map +1 -0
  14. package/dist/core/file-system/scan-api-dir.js +83 -0
  15. package/dist/core/file-system/watch-api-dir.d.ts +18 -0
  16. package/dist/core/file-system/watch-api-dir.d.ts.map +1 -0
  17. package/dist/core/file-system/watch-api-dir.js +40 -0
  18. package/dist/core/middleware/compose.d.ts +12 -0
  19. package/dist/core/middleware/compose.d.ts.map +1 -0
  20. package/dist/core/middleware/compose.js +27 -0
  21. package/dist/core/middleware/get-applicable-middlewares.d.ts +17 -0
  22. package/dist/core/middleware/get-applicable-middlewares.d.ts.map +1 -0
  23. package/dist/core/middleware/get-applicable-middlewares.js +47 -0
  24. package/dist/core/middleware/load-global.d.ts +13 -0
  25. package/dist/core/middleware/load-global.d.ts.map +1 -0
  26. package/dist/core/middleware/load-global.js +37 -0
  27. package/dist/core/normalize/normalize-path.d.ts +25 -0
  28. package/dist/core/normalize/normalize-path.d.ts.map +1 -0
  29. package/dist/core/normalize/normalize-path.js +76 -0
  30. package/dist/core/routing/route-matcher.d.ts +10 -0
  31. package/dist/core/routing/route-matcher.d.ts.map +1 -0
  32. package/dist/core/routing/route-matcher.js +32 -0
  33. package/dist/core/routing/route-parser.d.ts +28 -0
  34. package/dist/core/routing/route-parser.d.ts.map +1 -0
  35. package/dist/core/routing/route-parser.js +52 -0
  36. package/dist/core/routing/route-types.d.ts +43 -0
  37. package/dist/core/routing/route-types.d.ts.map +1 -0
  38. package/dist/core/routing/route-types.js +5 -0
  39. package/dist/core/types/extract-ast.d.ts +18 -0
  40. package/dist/core/types/extract-ast.d.ts.map +1 -0
  41. package/dist/core/types/extract-ast.js +26 -0
  42. package/dist/core/types/generate.d.ts +22 -0
  43. package/dist/core/types/generate.d.ts.map +1 -0
  44. package/dist/core/types/generate.js +576 -0
  45. package/dist/core/types/schema.d.ts +21 -0
  46. package/dist/core/types/schema.d.ts.map +1 -0
  47. package/dist/core/types/schema.js +17 -0
  48. package/dist/core/validation/types.d.ts +27 -0
  49. package/dist/core/validation/types.d.ts.map +1 -0
  50. package/dist/core/validation/types.js +5 -0
  51. package/dist/core/validation/validator.d.ts +22 -0
  52. package/dist/core/validation/validator.d.ts.map +1 -0
  53. package/dist/core/validation/validator.js +131 -0
  54. package/dist/index.d.ts +8 -0
  55. package/dist/index.d.ts.map +1 -0
  56. package/dist/index.js +4 -0
  57. package/dist/plugin.d.ts +27 -0
  58. package/dist/plugin.d.ts.map +1 -0
  59. package/dist/plugin.js +54 -0
  60. package/dist/shared/constants.d.ts +13 -0
  61. package/dist/shared/constants.d.ts.map +1 -0
  62. package/dist/shared/constants.js +12 -0
  63. package/dist/shared/errors.d.ts +75 -0
  64. package/dist/shared/errors.d.ts.map +1 -0
  65. package/dist/shared/errors.js +118 -0
  66. package/dist/shared/response-helpers.d.ts +61 -0
  67. package/dist/shared/response-helpers.d.ts.map +1 -0
  68. package/dist/shared/response-helpers.js +100 -0
  69. package/dist/shared/utils.d.ts +17 -0
  70. package/dist/shared/utils.d.ts.map +1 -0
  71. package/dist/shared/utils.js +27 -0
  72. package/package.json +48 -0
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Context creation for route handlers
3
+ * Core logic - runtime agnostic
4
+ */
5
+ /**
6
+ * Type guard to check if a value is a VitekResponse
7
+ * A VitekResponse is identified by having 'status' (number) or 'headers' (object) properties
8
+ * Plain objects without these properties are treated as regular JSON responses (backward compatibility)
9
+ */
10
+ export function isVitekResponse(value) {
11
+ if (typeof value !== 'object' || value === null || Array.isArray(value)) {
12
+ return false;
13
+ }
14
+ // It's a VitekResponse if it has 'status' (number) or 'headers' (object) properties
15
+ // These are clear indicators of a response object, not a data object
16
+ const hasStatus = 'status' in value && typeof value.status === 'number';
17
+ const hasHeaders = 'headers' in value && typeof value.headers === 'object' && value.headers !== null && !Array.isArray(value.headers);
18
+ return hasStatus || hasHeaders;
19
+ }
20
+ /**
21
+ * Creates a context from a request and extracted parameters
22
+ */
23
+ export function createContext(request, params = {}, query = {}) {
24
+ const url = new URL(request.url, 'http://localhost');
25
+ return {
26
+ url: request.url,
27
+ method: request.method.toLowerCase(),
28
+ path: url.pathname,
29
+ query,
30
+ params,
31
+ headers: request.headers,
32
+ body: request.body,
33
+ };
34
+ }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * API directory scanner
3
+ * Core logic - uses Node.js fs but abstracted to be testable
4
+ */
5
+ import type { ParsedRoute } from '../routing/route-parser.js';
6
+ /**
7
+ * Information about a found middleware
8
+ */
9
+ export interface MiddlewareInfo {
10
+ /** Absolute path of the middleware file */
11
+ path: string;
12
+ /** Base pattern calculated from the path (e.g., "posts", "posts/:id", "" for global) */
13
+ basePattern: string;
14
+ }
15
+ /**
16
+ * Result of API directory scan
17
+ */
18
+ export interface ScanResult {
19
+ routes: ParsedRoute[];
20
+ middlewares: MiddlewareInfo[];
21
+ }
22
+ /**
23
+ * Recursively scans a directory looking for route files and middlewares
24
+ */
25
+ export declare function scanApiDirectory(apiDir: string): ScanResult;
26
+ //# sourceMappingURL=scan-api-dir.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scan-api-dir.d.ts","sourceRoot":"","sources":["../../../src/core/file-system/scan-api-dir.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAKH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AAG9D;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,2CAA2C;IAC3C,IAAI,EAAE,MAAM,CAAC;IACb,wFAAwF;IACxF,WAAW,EAAE,MAAM,CAAC;CACrB;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,WAAW,EAAE,CAAC;IACtB,WAAW,EAAE,cAAc,EAAE,CAAC;CAC/B;AAsCD;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,UAAU,CAiD3D"}
@@ -0,0 +1,83 @@
1
+ /**
2
+ * API directory scanner
3
+ * Core logic - uses Node.js fs but abstracted to be testable
4
+ */
5
+ import * as fs from 'fs';
6
+ import * as path from 'path';
7
+ import { ROUTE_FILE_PATTERN, MIDDLEWARE_FILE_PATTERN, PARAM_PATTERN } from '../../shared/constants.js';
8
+ import { parseRouteFile } from '../routing/route-parser.js';
9
+ /**
10
+ * Calculates the base pattern of a middleware based on the path relative to apiDir
11
+ * Example:
12
+ * - src/api/middleware.ts -> "" (global)
13
+ * - src/api/posts/middleware.ts -> "posts"
14
+ * - src/api/posts/[id]/middleware.ts -> "posts/:id"
15
+ */
16
+ function calculateMiddlewareBasePattern(middlewarePath, apiDir) {
17
+ // Get the directory where the middleware is located
18
+ const middlewareDir = path.dirname(middlewarePath);
19
+ // Calculate relative path from middleware directory to apiDir
20
+ const relativePath = path.relative(apiDir, middlewareDir);
21
+ // If it's at the root (apiDir), it's a global middleware
22
+ if (!relativePath || relativePath === '.' || relativePath === '') {
23
+ return '';
24
+ }
25
+ // Normalize separators
26
+ let pattern = relativePath.replace(/\\/g, '/');
27
+ // Remove leading/trailing slashes
28
+ pattern = pattern.replace(/^\/+|\/+$/g, '');
29
+ // Convert [id] to :id and [...ids] to *ids
30
+ pattern = pattern.replace(PARAM_PATTERN, (match, isCatchAll, paramName) => {
31
+ if (isCatchAll) {
32
+ return `*${paramName}`;
33
+ }
34
+ return `:${paramName}`;
35
+ });
36
+ return pattern;
37
+ }
38
+ /**
39
+ * Recursively scans a directory looking for route files and middlewares
40
+ */
41
+ export function scanApiDirectory(apiDir) {
42
+ const routes = [];
43
+ const middlewares = [];
44
+ if (!fs.existsSync(apiDir)) {
45
+ return { routes, middlewares };
46
+ }
47
+ function scanDir(currentDir) {
48
+ const entries = fs.readdirSync(currentDir, { withFileTypes: true });
49
+ for (const entry of entries) {
50
+ const fullPath = path.join(currentDir, entry.name);
51
+ if (entry.isDirectory()) {
52
+ scanDir(fullPath);
53
+ }
54
+ else if (entry.isFile()) {
55
+ // Check if it's a middleware file
56
+ if (MIDDLEWARE_FILE_PATTERN.test(entry.name)) {
57
+ const basePattern = calculateMiddlewareBasePattern(fullPath, apiDir);
58
+ middlewares.push({
59
+ path: fullPath,
60
+ basePattern,
61
+ });
62
+ continue;
63
+ }
64
+ // Check if it's a route file
65
+ if (ROUTE_FILE_PATTERN.test(entry.name)) {
66
+ const parsed = parseRouteFile(fullPath, apiDir);
67
+ if (parsed) {
68
+ routes.push(parsed);
69
+ }
70
+ }
71
+ }
72
+ }
73
+ }
74
+ scanDir(apiDir);
75
+ // Sort middlewares by depth (most generic first, most specific last)
76
+ // This ensures that when composing, global middlewares come before specific ones
77
+ middlewares.sort((a, b) => {
78
+ const aDepth = a.basePattern ? a.basePattern.split('/').length : 0;
79
+ const bDepth = b.basePattern ? b.basePattern.split('/').length : 0;
80
+ return aDepth - bDepth;
81
+ });
82
+ return { routes, middlewares };
83
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * API directory watcher
3
+ * Core logic - abstracted to be runtime agnostic
4
+ */
5
+ export type FileChangeEvent = 'add' | 'change' | 'unlink';
6
+ export type FileChangeCallback = (event: FileChangeEvent, filePath: string) => void;
7
+ /**
8
+ * Interface for watcher (allows different implementations)
9
+ */
10
+ export interface ApiWatcher {
11
+ close(): void;
12
+ }
13
+ /**
14
+ * Creates a watcher for the API directory using Node.js fs.watch
15
+ * Returns a function to stop watching
16
+ */
17
+ export declare function watchApiDirectory(apiDir: string, callback: FileChangeCallback): ApiWatcher;
18
+ //# sourceMappingURL=watch-api-dir.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"watch-api-dir.d.ts","sourceRoot":"","sources":["../../../src/core/file-system/watch-api-dir.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAMH,MAAM,MAAM,eAAe,GAAG,KAAK,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAC1D,MAAM,MAAM,kBAAkB,GAAG,CAAC,KAAK,EAAE,eAAe,EAAE,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;AAEpF;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,KAAK,IAAI,IAAI,CAAC;CACf;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,kBAAkB,GAC3B,UAAU,CAiCZ"}
@@ -0,0 +1,40 @@
1
+ /**
2
+ * API directory watcher
3
+ * Core logic - abstracted to be runtime agnostic
4
+ */
5
+ import * as fs from 'fs';
6
+ import * as path from 'path';
7
+ import { ROUTE_FILE_PATTERN, MIDDLEWARE_FILE_PATTERN } from '../../shared/constants.js';
8
+ /**
9
+ * Creates a watcher for the API directory using Node.js fs.watch
10
+ * Returns a function to stop watching
11
+ */
12
+ export function watchApiDirectory(apiDir, callback) {
13
+ if (!fs.existsSync(apiDir)) {
14
+ return { close: () => { } };
15
+ }
16
+ const watcher = fs.watch(apiDir, { recursive: true }, (eventType, filename) => {
17
+ if (!filename)
18
+ return;
19
+ const filePath = path.join(apiDir, filename);
20
+ // Ignore if it's not a route file or relevant middleware
21
+ const isRouteFile = ROUTE_FILE_PATTERN.test(filename);
22
+ const isMiddlewareFile = MIDDLEWARE_FILE_PATTERN.test(filename);
23
+ if (!isRouteFile && !isMiddlewareFile) {
24
+ return;
25
+ }
26
+ // Normalize event type
27
+ let normalizedEvent;
28
+ if (eventType === 'rename') {
29
+ // rename can be add or unlink, check if it exists
30
+ normalizedEvent = fs.existsSync(filePath) ? 'add' : 'unlink';
31
+ }
32
+ else {
33
+ normalizedEvent = eventType;
34
+ }
35
+ callback(normalizedEvent, filePath);
36
+ });
37
+ return {
38
+ close: () => watcher.close(),
39
+ };
40
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Middleware composer (Koa-style)
3
+ * Core logic - no dependencies
4
+ */
5
+ import type { Middleware } from '../routing/route-types.js';
6
+ import type { VitekContext } from '../context/create-context.js';
7
+ /**
8
+ * Composes middlewares into a single function
9
+ * Executes in order and allows each middleware to call next() to continue
10
+ */
11
+ export declare function compose(middlewares: Middleware[]): (context: VitekContext, handler: () => Promise<any>) => Promise<any>;
12
+ //# sourceMappingURL=compose.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compose.d.ts","sourceRoot":"","sources":["../../../src/core/middleware/compose.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAC5D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AAEjE;;;GAGG;AACH,wBAAgB,OAAO,CAAC,WAAW,EAAE,UAAU,EAAE,IACxB,SAAS,YAAY,EAAE,SAAS,MAAM,OAAO,CAAC,GAAG,CAAC,KAAG,OAAO,CAAC,GAAG,CAAC,CAuBzF"}
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Middleware composer (Koa-style)
3
+ * Core logic - no dependencies
4
+ */
5
+ /**
6
+ * Composes middlewares into a single function
7
+ * Executes in order and allows each middleware to call next() to continue
8
+ */
9
+ export function compose(middlewares) {
10
+ return async function (context, handler) {
11
+ let index = -1;
12
+ async function dispatch(i) {
13
+ if (i <= index) {
14
+ throw new Error('next() called multiple times');
15
+ }
16
+ index = i;
17
+ // If all middlewares have been executed, call the final handler
18
+ if (i === middlewares.length) {
19
+ return handler();
20
+ }
21
+ const middleware = middlewares[i];
22
+ // Execute the current middleware
23
+ await middleware(context, () => dispatch(i + 1));
24
+ }
25
+ return dispatch(0);
26
+ };
27
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Determines which middlewares are applicable to a route based on the pattern
3
+ * Core logic - no dependencies
4
+ */
5
+ import type { Middleware } from '../routing/route-types.js';
6
+ /**
7
+ * Information about a loaded middleware (with the middleware function already imported)
8
+ */
9
+ export interface LoadedMiddleware {
10
+ middleware: Middleware[];
11
+ basePattern: string;
12
+ }
13
+ /**
14
+ * Returns the middlewares applicable to a route, ordered from most generic to most specific
15
+ */
16
+ export declare function getApplicableMiddlewares(middlewares: LoadedMiddleware[], routePattern: string): Middleware[];
17
+ //# sourceMappingURL=get-applicable-middlewares.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"get-applicable-middlewares.d.ts","sourceRoot":"","sources":["../../../src/core/middleware/get-applicable-middlewares.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAG5D;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,UAAU,EAAE,UAAU,EAAE,CAAC;IACzB,WAAW,EAAE,MAAM,CAAC;CACrB;AAuCD;;GAEG;AACH,wBAAgB,wBAAwB,CACtC,WAAW,EAAE,gBAAgB,EAAE,EAC/B,YAAY,EAAE,MAAM,GACnB,UAAU,EAAE,CAUd"}
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Determines which middlewares are applicable to a route based on the pattern
3
+ * Core logic - no dependencies
4
+ */
5
+ /**
6
+ * Checks if a middleware base pattern applies to a route pattern
7
+ * A middleware applies if its basePattern is a prefix of the routePattern
8
+ *
9
+ * Examples:
10
+ * - basePattern: "" → applies to all routes (global)
11
+ * - basePattern: "posts" → applies to "posts", "posts/:id", "posts/:id/comments", etc
12
+ * - basePattern: "posts/:id" → applies to "posts/:id", "posts/:id/comments", but not to "posts"
13
+ */
14
+ function isMiddlewareApplicable(basePattern, routePattern) {
15
+ // Global middleware (empty basePattern) applies to all routes
16
+ if (basePattern === '') {
17
+ return true;
18
+ }
19
+ // If route is empty (root), only global middleware applies
20
+ if (routePattern === '') {
21
+ return false;
22
+ }
23
+ // Normalize patterns for comparison (remove leading/trailing slashes)
24
+ const normalizedBase = basePattern.replace(/^\/+|\/+$/g, '');
25
+ const normalizedRoute = routePattern.replace(/^\/+|\/+$/g, '');
26
+ // Check if basePattern is an exact prefix of routePattern
27
+ // Or if route starts with basePattern followed by /
28
+ if (normalizedRoute === normalizedBase) {
29
+ return true;
30
+ }
31
+ if (normalizedRoute.startsWith(normalizedBase + '/')) {
32
+ return true;
33
+ }
34
+ return false;
35
+ }
36
+ /**
37
+ * Returns the middlewares applicable to a route, ordered from most generic to most specific
38
+ */
39
+ export function getApplicableMiddlewares(middlewares, routePattern) {
40
+ const applicable = [];
41
+ for (const loadedMiddleware of middlewares) {
42
+ if (isMiddlewareApplicable(loadedMiddleware.basePattern, routePattern)) {
43
+ applicable.push(...loadedMiddleware.middleware);
44
+ }
45
+ }
46
+ return applicable;
47
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Global middleware loading
3
+ * Core logic - runtime agnostic
4
+ */
5
+ import type { Middleware } from '../routing/route-types.js';
6
+ /**
7
+ * Loads global middleware from a module
8
+ * The module must export a default or named export 'middleware'
9
+ *
10
+ * @param middlewarePath - Absolute path to the middleware file
11
+ */
12
+ export declare function loadGlobalMiddleware(middlewarePath: string): Promise<Middleware[]>;
13
+ //# sourceMappingURL=load-global.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"load-global.d.ts","sourceRoot":"","sources":["../../../src/core/middleware/load-global.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAE5D;;;;;GAKG;AACH,wBAAsB,oBAAoB,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC,CA8BxF"}
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Global middleware loading
3
+ * Core logic - runtime agnostic
4
+ */
5
+ /**
6
+ * Loads global middleware from a module
7
+ * The module must export a default or named export 'middleware'
8
+ *
9
+ * @param middlewarePath - Absolute path to the middleware file
10
+ */
11
+ export async function loadGlobalMiddleware(middlewarePath) {
12
+ try {
13
+ // Dynamic import - converts to URL for Vite/Node compatibility
14
+ const { pathToFileURL } = await import('node:url');
15
+ const moduleUrl = pathToFileURL(middlewarePath).href;
16
+ const module = await import(moduleUrl);
17
+ // Supports both default export and named export
18
+ const middleware = module.default || module.middleware;
19
+ if (!middleware) {
20
+ return [];
21
+ }
22
+ // If it's an array, return directly
23
+ if (Array.isArray(middleware)) {
24
+ return middleware;
25
+ }
26
+ // If it's a single function, return as array
27
+ if (typeof middleware === 'function') {
28
+ return [middleware];
29
+ }
30
+ return [];
31
+ }
32
+ catch (error) {
33
+ // If file doesn't exist or has no middleware, return empty array
34
+ // This allows middleware to be optional
35
+ return [];
36
+ }
37
+ }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Path normalization for API routes
3
+ * Core logic - no Vite dependencies
4
+ */
5
+ /**
6
+ * Converts a file path to a normalized HTTP route
7
+ *
8
+ * Examples:
9
+ * - users/[id].get.ts -> users/:id
10
+ * - posts/[...ids].get.ts -> posts/*ids
11
+ * - health.get.ts -> health
12
+ */
13
+ export declare function normalizeRoutePath(filePath: string): string;
14
+ /**
15
+ * Extracts parameters from a path pattern
16
+ * Example: "users/:id/posts/:postId" -> ["id", "postId"]
17
+ */
18
+ export declare function extractParamsFromPattern(pattern: string): string[];
19
+ /**
20
+ * Converts a path pattern to regex for matching
21
+ * Example: "users/:id" -> /^\/users\/([^/]+)$/
22
+ * Example: "posts/*ids" -> /^\/posts\/(.*)$/
23
+ */
24
+ export declare function patternToRegex(pattern: string): RegExp;
25
+ //# sourceMappingURL=normalize-path.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"normalize-path.d.ts","sourceRoot":"","sources":["../../../src/core/normalize/normalize-path.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAKH;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CA2B3D;AAED;;;GAGG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,CAUlE;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CA0BtD"}
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Path normalization for API routes
3
+ * Core logic - no Vite dependencies
4
+ */
5
+ import { PARAM_PATTERN } from '../../shared/constants.js';
6
+ /**
7
+ * Converts a file path to a normalized HTTP route
8
+ *
9
+ * Examples:
10
+ * - users/[id].get.ts -> users/:id
11
+ * - posts/[...ids].get.ts -> posts/*ids
12
+ * - health.get.ts -> health
13
+ */
14
+ export function normalizeRoutePath(filePath) {
15
+ // Remove extensions (.ts, .js) and HTTP method
16
+ let path = filePath
17
+ .replace(/\.(ts|js)$/, '')
18
+ .replace(/\.(get|post|put|patch|delete|head|options)$/, '');
19
+ // Normalize separators
20
+ path = path.replace(/\\/g, '/');
21
+ // Remove leading/trailing slashes (but preserve empty string for root route)
22
+ path = path.replace(/^\/+|\/+$/g, '');
23
+ // If path is empty, it means root route (e.g., health.get.ts)
24
+ if (!path) {
25
+ return '';
26
+ }
27
+ // Convert [id] to :id and [...ids] to *ids (catch-all)
28
+ path = path.replace(PARAM_PATTERN, (match, isCatchAll, paramName) => {
29
+ if (isCatchAll) {
30
+ return `*${paramName}`;
31
+ }
32
+ return `:${paramName}`;
33
+ });
34
+ // Return without normalizing (without adding /), as it will be used in patternToRegex
35
+ return path;
36
+ }
37
+ /**
38
+ * Extracts parameters from a path pattern
39
+ * Example: "users/:id/posts/:postId" -> ["id", "postId"]
40
+ */
41
+ export function extractParamsFromPattern(pattern) {
42
+ const params = [];
43
+ const paramRegex = /[:*]([^/]+)/g;
44
+ let match;
45
+ while ((match = paramRegex.exec(pattern)) !== null) {
46
+ params.push(match[1]);
47
+ }
48
+ return params;
49
+ }
50
+ /**
51
+ * Converts a path pattern to regex for matching
52
+ * Example: "users/:id" -> /^\/users\/([^/]+)$/
53
+ * Example: "posts/*ids" -> /^\/posts\/(.*)$/
54
+ */
55
+ export function patternToRegex(pattern) {
56
+ // Normalize pattern: empty route becomes '/', others add / at the beginning
57
+ let normalizedPattern = pattern === '' ? '/' : (pattern.startsWith('/') ? pattern : `/${pattern}`);
58
+ // First replace placeholders (before escaping)
59
+ // Replace *param (catch-all) with temporary placeholder
60
+ normalizedPattern = normalizedPattern.replace(/\*(\w+)/g, '__CATCHALL_$1__');
61
+ // Replace :param with temporary placeholder
62
+ normalizedPattern = normalizedPattern.replace(/:(\w+)/g, '__PARAM_$1__');
63
+ // Escape special characters
64
+ let regexStr = normalizedPattern.replace(/[.+?^${}()|[\]\\]/g, '\\$&');
65
+ // Restore placeholders as capture groups
66
+ regexStr = regexStr.replace(/__PARAM_\w+__/g, '([^/]+)');
67
+ regexStr = regexStr.replace(/__CATCHALL_(\w+)__/g, '(.*)');
68
+ // Ensure it starts and ends correctly
69
+ if (!regexStr.startsWith('^')) {
70
+ regexStr = '^' + regexStr;
71
+ }
72
+ if (!regexStr.endsWith('$')) {
73
+ regexStr = regexStr + '$';
74
+ }
75
+ return new RegExp(regexStr);
76
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Route matching against requests
3
+ * Core logic - no Vite dependencies
4
+ */
5
+ import type { Route, RouteMatch } from './route-types.js';
6
+ /**
7
+ * Finds the route that matches a path and method
8
+ */
9
+ export declare function matchRoute(routes: Route[], path: string, method: string): RouteMatch | null;
10
+ //# sourceMappingURL=route-matcher.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"route-matcher.d.ts","sourceRoot":"","sources":["../../../src/core/routing/route-matcher.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAG1D;;GAEG;AACH,wBAAgB,UAAU,CACxB,MAAM,EAAE,KAAK,EAAE,EACf,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,GACb,UAAU,GAAG,IAAI,CA4BnB"}
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Route matching against requests
3
+ * Core logic - no Vite dependencies
4
+ */
5
+ /**
6
+ * Finds the route that matches a path and method
7
+ */
8
+ export function matchRoute(routes, path, method) {
9
+ // Normalize path ensuring it starts with /
10
+ const normalizedPath = path.startsWith('/') ? path : `/${path}`;
11
+ const normalizedMethod = method.toLowerCase();
12
+ // Filter routes by method
13
+ const methodRoutes = routes.filter(r => r.method === normalizedMethod);
14
+ // Try to match in order
15
+ for (const route of methodRoutes) {
16
+ const match = normalizedPath.match(route.regex);
17
+ if (match) {
18
+ // Extract parameters from match
19
+ const params = {};
20
+ route.params.forEach((paramName, index) => {
21
+ // +1 because match[0] is the complete match
22
+ const value = match[index + 1];
23
+ params[paramName] = value !== undefined ? value : '';
24
+ });
25
+ return {
26
+ route,
27
+ params,
28
+ };
29
+ }
30
+ }
31
+ return null;
32
+ }
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Route file parser
3
+ * Core logic - no Vite dependencies
4
+ */
5
+ import type { Route, RouteHandler } from './route-types.js';
6
+ import type { HttpMethod } from '../../shared/constants.js';
7
+ /**
8
+ * Result of parsing a route file
9
+ */
10
+ export interface ParsedRoute {
11
+ method: HttpMethod;
12
+ pattern: string;
13
+ params: string[];
14
+ file: string;
15
+ }
16
+ /**
17
+ * Extracts route information from a file path
18
+ *
19
+ * Examples:
20
+ * - src/api/users/[id].get.ts -> { method: 'get', pattern: 'users/:id', params: ['id'] }
21
+ * - src/api/posts/[...ids].get.ts -> { method: 'get', pattern: 'posts/*ids', params: ['ids'] }
22
+ */
23
+ export declare function parseRouteFile(filePath: string, baseDir: string): ParsedRoute | null;
24
+ /**
25
+ * Creates a route definition from a parsed route and handler
26
+ */
27
+ export declare function createRoute(parsed: ParsedRoute, handler: RouteHandler, bodyType?: string, queryType?: string): Route;
28
+ //# sourceMappingURL=route-parser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"route-parser.d.ts","sourceRoot":"","sources":["../../../src/core/routing/route-parser.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAKH,OAAO,KAAK,EAAE,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAC5D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAE5D;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,UAAU,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI,CA2BpF;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,WAAW,EAAE,OAAO,EAAE,YAAY,EAAE,QAAQ,CAAC,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,KAAK,CAWpH"}
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Route file parser
3
+ * Core logic - no Vite dependencies
4
+ */
5
+ import { ROUTE_FILE_PATTERN } from '../../shared/constants.js';
6
+ import { isHttpMethod } from '../../shared/utils.js';
7
+ import { normalizeRoutePath, extractParamsFromPattern, patternToRegex } from '../normalize/normalize-path.js';
8
+ /**
9
+ * Extracts route information from a file path
10
+ *
11
+ * Examples:
12
+ * - src/api/users/[id].get.ts -> { method: 'get', pattern: 'users/:id', params: ['id'] }
13
+ * - src/api/posts/[...ids].get.ts -> { method: 'get', pattern: 'posts/*ids', params: ['ids'] }
14
+ */
15
+ export function parseRouteFile(filePath, baseDir) {
16
+ // Remove base directory from path
17
+ const relativePath = filePath.replace(baseDir, '').replace(/^[/\\]/, '');
18
+ // Try to match route file pattern
19
+ const match = relativePath.match(ROUTE_FILE_PATTERN);
20
+ if (!match) {
21
+ return null;
22
+ }
23
+ const [, pathPart, methodPart] = match;
24
+ const method = methodPart.toLowerCase();
25
+ if (!isHttpMethod(method)) {
26
+ return null;
27
+ }
28
+ // Normalize path and extract parameters
29
+ const pattern = normalizeRoutePath(pathPart);
30
+ const params = extractParamsFromPattern(pattern);
31
+ return {
32
+ method,
33
+ pattern,
34
+ params,
35
+ file: filePath,
36
+ };
37
+ }
38
+ /**
39
+ * Creates a route definition from a parsed route and handler
40
+ */
41
+ export function createRoute(parsed, handler, bodyType, queryType) {
42
+ return {
43
+ pattern: parsed.pattern,
44
+ method: parsed.method,
45
+ handler,
46
+ params: parsed.params,
47
+ file: parsed.file,
48
+ regex: patternToRegex(parsed.pattern),
49
+ bodyType,
50
+ queryType,
51
+ };
52
+ }