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,576 @@
1
+ /**
2
+ * TypeScript type generation for the API
3
+ * Core logic - no Vite dependencies
4
+ */
5
+ import * as fs from 'fs';
6
+ import * as path from 'path';
7
+ import { capitalize } from '../../shared/utils.js';
8
+ /**
9
+ * Generates the content of the generated types file
10
+ */
11
+ export function generateTypesContent(routes, apiBasePath) {
12
+ const imports = `// Auto-generated by Vitek - DO NOT EDIT
13
+ // This file is automatically generated from your API routes
14
+
15
+ export type VitekParams = Record<string, string>;
16
+ export type VitekQuery = Record<string, string | string[]>;
17
+ `;
18
+ // Generate unique names for all types (Params, Body, Query)
19
+ const typeNameMaps = generateUniqueTypeNames(routes);
20
+ const paramTypeNameMap = typeNameMaps.params;
21
+ const bodyTypeNameMap = typeNameMaps.body;
22
+ const queryTypeNameMap = typeNameMaps.query;
23
+ // Generate body types for routes that have body type declared
24
+ const bodyTypes = routes
25
+ .filter(route => route.bodyType)
26
+ .map(route => {
27
+ const routeKey = `${route.method}:${route.pattern}`;
28
+ const bodyTypeName = bodyTypeNameMap.get(routeKey);
29
+ // route.bodyType was already checked in the filter above, so it's not undefined
30
+ const bodyType = route.bodyType;
31
+ // If bodyType already starts with '{', it means it's an extracted interface
32
+ // If not, it's a type alias, so use it directly
33
+ const bodyTypeDef = bodyType.trim().startsWith('{')
34
+ ? bodyType
35
+ : bodyType;
36
+ return `export type ${bodyTypeName} = ${bodyTypeDef};`;
37
+ })
38
+ .filter((type, index, arr) => {
39
+ // Remove duplicates based on type name
40
+ const typeName = type.match(/^export type (\w+)/)?.[1];
41
+ return typeName && arr.findIndex(t => t.match(/^export type (\w+)/)?.[1] === typeName) === index;
42
+ })
43
+ .join('\n\n');
44
+ // Generate query types for routes that have query type declared
45
+ const queryTypes = routes
46
+ .filter(route => route.queryType)
47
+ .map(route => {
48
+ const routeKey = `${route.method}:${route.pattern}`;
49
+ const queryTypeName = queryTypeNameMap.get(routeKey);
50
+ // route.queryType was already checked in the filter above, so it's not undefined
51
+ const queryType = route.queryType;
52
+ const queryTypeDef = queryType.trim().startsWith('{')
53
+ ? queryType
54
+ : queryType;
55
+ return `export type ${queryTypeName} = ${queryTypeDef};`;
56
+ })
57
+ .filter((type, index, arr) => {
58
+ // Remove duplicates based on type name
59
+ const typeName = type.match(/^export type (\w+)/)?.[1];
60
+ return typeName && arr.findIndex(t => t.match(/^export type (\w+)/)?.[1] === typeName) === index;
61
+ })
62
+ .join('\n\n');
63
+ // Generate types for each route (params)
64
+ const routeTypes = routes.map(route => {
65
+ const routeKey = `${route.method}:${route.pattern}`;
66
+ const typeName = paramTypeNameMap.get(routeKey);
67
+ const paramsType = generateParamsType(route.params);
68
+ return `export interface ${typeName} extends VitekParams {
69
+ ${paramsType}
70
+ }`;
71
+ })
72
+ .filter((type, index, arr) => {
73
+ // Remove duplicates based on type name
74
+ const typeName = type.match(/^export interface (\w+)/)?.[1];
75
+ return typeName && arr.findIndex(t => t.match(/^export interface (\w+)/)?.[1] === typeName) === index;
76
+ })
77
+ .join('\n\n');
78
+ // Generate a type union with all routes
79
+ const routeDefinitions = routes.map(route => {
80
+ const routeKey = `${route.method}:${route.pattern}`;
81
+ const typeName = paramTypeNameMap.get(routeKey);
82
+ return ` | {
83
+ pattern: '${route.pattern}';
84
+ method: '${route.method}';
85
+ params: ${typeName};
86
+ }`;
87
+ }).join('\n');
88
+ const unionType = `export type VitekRoute =${routeDefinitions.length > 0 ? '\n' + routeDefinitions : ' never'};`;
89
+ return `${imports}
90
+ ${bodyTypes ? '\n' + bodyTypes + '\n' : ''}${queryTypes ? '\n' + queryTypes + '\n' : ''}
91
+ ${routeTypes}
92
+
93
+ ${unionType}
94
+
95
+ export const API_BASE_PATH = '${apiBasePath}' as const;
96
+ `;
97
+ }
98
+ /**
99
+ * Generates unique names for all types (Params, Body, Query)
100
+ * Ensures there are no collisions within and between categories
101
+ */
102
+ function generateUniqueTypeNames(routes) {
103
+ const paramNameMap = new Map();
104
+ const bodyNameMap = new Map();
105
+ const queryNameMap = new Map();
106
+ // All names already used (to ensure uniqueness between categories as well)
107
+ const allUsedNames = new Set();
108
+ // Process Params types
109
+ const paramGroups = new Map();
110
+ routes.forEach(route => {
111
+ const baseName = generateTypeNameBase(route, 'Params');
112
+ if (!paramGroups.has(baseName)) {
113
+ paramGroups.set(baseName, []);
114
+ }
115
+ paramGroups.get(baseName).push(route);
116
+ });
117
+ routes.forEach(route => {
118
+ const routeKey = `${route.method}:${route.pattern}`;
119
+ const baseName = generateTypeNameBase(route, 'Params');
120
+ const conflictingRoutes = paramGroups.get(baseName);
121
+ let uniqueName;
122
+ if (conflictingRoutes.length === 1) {
123
+ uniqueName = baseName;
124
+ }
125
+ else {
126
+ uniqueName = generateUniqueTypeName(route, baseName, conflictingRoutes, allUsedNames, 'Params');
127
+ }
128
+ paramNameMap.set(routeKey, uniqueName);
129
+ allUsedNames.add(uniqueName);
130
+ });
131
+ // Process Body types
132
+ const bodyGroups = new Map();
133
+ routes.filter(r => r.bodyType).forEach(route => {
134
+ const baseName = generateTypeNameBase(route, 'Body');
135
+ if (!bodyGroups.has(baseName)) {
136
+ bodyGroups.set(baseName, []);
137
+ }
138
+ bodyGroups.get(baseName).push(route);
139
+ });
140
+ routes.filter(r => r.bodyType).forEach(route => {
141
+ const routeKey = `${route.method}:${route.pattern}`;
142
+ const baseName = generateTypeNameBase(route, 'Body');
143
+ const conflictingRoutes = bodyGroups.get(baseName);
144
+ let uniqueName;
145
+ if (conflictingRoutes.length === 1) {
146
+ uniqueName = baseName;
147
+ }
148
+ else {
149
+ uniqueName = generateUniqueTypeName(route, baseName, conflictingRoutes, allUsedNames, 'Body');
150
+ }
151
+ bodyNameMap.set(routeKey, uniqueName);
152
+ allUsedNames.add(uniqueName);
153
+ });
154
+ // Process Query types
155
+ const queryGroups = new Map();
156
+ routes.filter(r => r.queryType).forEach(route => {
157
+ const baseName = generateTypeNameBase(route, 'Query');
158
+ if (!queryGroups.has(baseName)) {
159
+ queryGroups.set(baseName, []);
160
+ }
161
+ queryGroups.get(baseName).push(route);
162
+ });
163
+ routes.filter(r => r.queryType).forEach(route => {
164
+ const routeKey = `${route.method}:${route.pattern}`;
165
+ const baseName = generateTypeNameBase(route, 'Query');
166
+ const conflictingRoutes = queryGroups.get(baseName);
167
+ let uniqueName;
168
+ if (conflictingRoutes.length === 1) {
169
+ uniqueName = baseName;
170
+ }
171
+ else {
172
+ uniqueName = generateUniqueTypeName(route, baseName, conflictingRoutes, allUsedNames, 'Query');
173
+ }
174
+ queryNameMap.set(routeKey, uniqueName);
175
+ allUsedNames.add(uniqueName);
176
+ });
177
+ return {
178
+ params: paramNameMap,
179
+ body: bodyNameMap,
180
+ query: queryNameMap,
181
+ };
182
+ }
183
+ /**
184
+ * Generates base type name from a route
185
+ * Example: "users/:id" + "get" + "Params" -> "UsersIdGetParams"
186
+ */
187
+ function generateTypeNameBase(route, suffix) {
188
+ const parts = route.pattern
189
+ .split('/')
190
+ .filter(Boolean)
191
+ .map(part => {
192
+ // Remove : or * from the beginning
193
+ const clean = part.replace(/^[:*]/, '');
194
+ // Remove "index" from the end
195
+ const withoutIndex = clean === 'index' ? '' : clean;
196
+ // Capitalize each part
197
+ return withoutIndex
198
+ .split('-')
199
+ .map(word => capitalize(word))
200
+ .join('');
201
+ });
202
+ const patternPart = parts.filter(Boolean).join('');
203
+ return patternPart + capitalize(route.method) + suffix;
204
+ }
205
+ /**
206
+ * Generates a unique type name when there's a collision
207
+ */
208
+ function generateUniqueTypeName(route, baseName, conflictingRoutes, allUsedNames, suffix) {
209
+ // Get all parts of the pattern (including parameters for context)
210
+ const patternParts = route.pattern.split('/').filter(Boolean);
211
+ // Remove "index" but keep all other parts (including params)
212
+ const allParts = patternParts
213
+ .filter(part => part !== 'index')
214
+ .map(part => {
215
+ // Include params in name to differentiate better
216
+ const clean = part.replace(/^[:*]/, '');
217
+ return capitalize(clean);
218
+ });
219
+ // Use all parts of the path to ensure uniqueness
220
+ if (allParts.length > 0) {
221
+ const fullPathName = allParts.join('');
222
+ const candidate = fullPathName + capitalize(route.method) + suffix;
223
+ if (!allUsedNames.has(candidate)) {
224
+ return candidate;
225
+ }
226
+ }
227
+ // If it still collides, try adding suffix with the last parts
228
+ for (let i = allParts.length; i > 0; i--) {
229
+ const suffixParts = allParts.slice(-i).join('');
230
+ if (suffixParts) {
231
+ const candidate = `${baseName}${suffixParts}`;
232
+ if (!allUsedNames.has(candidate)) {
233
+ return candidate;
234
+ }
235
+ }
236
+ }
237
+ // Last resort: add numeric suffix
238
+ let counter = 1;
239
+ let uniqueName = `${baseName}${counter}`;
240
+ while (allUsedNames.has(uniqueName)) {
241
+ counter++;
242
+ uniqueName = `${baseName}${counter}`;
243
+ }
244
+ return uniqueName;
245
+ }
246
+ /**
247
+ * Generates the params definition for a type
248
+ */
249
+ function generateParamsType(params) {
250
+ if (params.length === 0) {
251
+ return ' // No dynamic params';
252
+ }
253
+ return params.map(param => ` ${param}: string;`).join('\n');
254
+ }
255
+ /**
256
+ * Writes the generated types file
257
+ */
258
+ export async function generateTypesFile(outputPath, routes, apiBasePath) {
259
+ const content = generateTypesContent(routes, apiBasePath);
260
+ // Create directory if it doesn't exist
261
+ const dir = path.dirname(outputPath);
262
+ if (!fs.existsSync(dir)) {
263
+ fs.mkdirSync(dir, { recursive: true });
264
+ }
265
+ fs.writeFileSync(outputPath, content, 'utf-8');
266
+ }
267
+ /**
268
+ * Generates the content of the generated services file
269
+ */
270
+ export function generateServicesContent(routes, apiBasePath, isTypeScript = true) {
271
+ // Generate unique names for all types (even in JS, we use them for parameter names)
272
+ const uniqueTypeNames = generateUniqueTypeNames(routes);
273
+ const paramTypeNameMap = uniqueTypeNames.params;
274
+ const bodyTypeNameMap = uniqueTypeNames.body;
275
+ const queryTypeNameMap = uniqueTypeNames.query;
276
+ // Imports and constants
277
+ let imports = '';
278
+ let apiBasePathConstant = '';
279
+ if (isTypeScript) {
280
+ // Collect all types needed to import (using unique names)
281
+ const paramTypeNames = Array.from(new Set(routes
282
+ .filter(route => route.params.length > 0)
283
+ .map(route => {
284
+ const routeKey = `${route.method}:${route.pattern}`;
285
+ return paramTypeNameMap.get(routeKey);
286
+ })));
287
+ const bodyTypeNames = Array.from(new Set(routes
288
+ .filter(route => route.bodyType)
289
+ .map(route => {
290
+ const routeKey = `${route.method}:${route.pattern}`;
291
+ return bodyTypeNameMap.get(routeKey);
292
+ })));
293
+ const queryTypeNames = Array.from(new Set(routes
294
+ .filter(route => route.queryType)
295
+ .map(route => {
296
+ const routeKey = `${route.method}:${route.pattern}`;
297
+ return queryTypeNameMap.get(routeKey);
298
+ })));
299
+ const allTypeNames = [...paramTypeNames, ...bodyTypeNames, ...queryTypeNames];
300
+ const typeImports = allTypeNames.length > 0
301
+ ? `import type { ${allTypeNames.join(', ')} } from './api.types.js';\n`
302
+ : '';
303
+ imports = `// Auto-generated by Vitek - DO NOT EDIT
304
+ // This file is automatically generated from your API routes
305
+
306
+ import { API_BASE_PATH } from './api.types.js';
307
+ ${typeImports}
308
+ `;
309
+ }
310
+ else {
311
+ // JavaScript: no type imports, just constant
312
+ imports = `// Auto-generated by Vitek - DO NOT EDIT
313
+ // This file is automatically generated from your API routes
314
+
315
+ const API_BASE_PATH = '${apiBasePath}';
316
+ `;
317
+ }
318
+ // Generate function for each route with unique names
319
+ const functionNameMap = generateUniqueFunctionNames(routes);
320
+ const functions = routes.map(route => {
321
+ const routeKey = `${route.method}:${route.pattern}`;
322
+ const functionName = functionNameMap.get(routeKey);
323
+ const typeName = paramTypeNameMap.get(routeKey);
324
+ const bodyTypeName = route.bodyType ? bodyTypeNameMap.get(routeKey) : undefined;
325
+ const queryTypeName = route.queryType ? queryTypeNameMap.get(routeKey) : undefined;
326
+ const urlPath = generateUrlPath(route.pattern, route.params);
327
+ // Determine function parameters - only add if they actually exist
328
+ const hasParams = route.params.length > 0;
329
+ const hasBody = !!route.bodyType; // Only if bodyType is declared
330
+ const hasQuery = !!route.queryType; // Only if queryType is declared
331
+ const methodLower = route.method.toLowerCase();
332
+ let params = [];
333
+ let returnType = isTypeScript ? ': Promise<any>' : '';
334
+ // Add params only if they exist
335
+ if (hasParams) {
336
+ if (isTypeScript) {
337
+ params.push(`params: ${typeName}`);
338
+ }
339
+ else {
340
+ params.push(`params`);
341
+ }
342
+ }
343
+ // Add body only if bodyType is declared
344
+ if (hasBody) {
345
+ if (isTypeScript) {
346
+ params.push(`body: ${bodyTypeName}`);
347
+ }
348
+ else {
349
+ params.push(`body`);
350
+ }
351
+ }
352
+ // Add query only if queryType is declared
353
+ if (hasQuery) {
354
+ if (isTypeScript) {
355
+ params.push(`query: ${queryTypeName}`);
356
+ }
357
+ else {
358
+ params.push(`query`);
359
+ }
360
+ }
361
+ // Always add options
362
+ if (isTypeScript) {
363
+ params.push(`options?: RequestInit`);
364
+ }
365
+ else {
366
+ params.push(`options`);
367
+ }
368
+ // Build fetch code
369
+ let fetchCode = '';
370
+ if (hasBody && hasQuery) {
371
+ // POST, PUT, PATCH with body and query string
372
+ fetchCode = ` const url = new URL(\`\${API_BASE_PATH}${urlPath}\`, window.location.origin);
373
+ Object.entries(query).forEach(([key, value]) => {
374
+ url.searchParams.append(key, String(value));
375
+ });
376
+ return fetch(url.pathname + url.search, {
377
+ method: '${route.method.toUpperCase()}',
378
+ headers: {
379
+ 'Content-Type': 'application/json',
380
+ ...options?.headers,
381
+ },
382
+ body: JSON.stringify(body),
383
+ ...options,
384
+ }).then(res => res.json());`;
385
+ }
386
+ else if (hasBody) {
387
+ // POST, PUT, PATCH with body (no query)
388
+ fetchCode = ` return fetch(\`\${API_BASE_PATH}${urlPath}\`, {
389
+ method: '${route.method.toUpperCase()}',
390
+ headers: {
391
+ 'Content-Type': 'application/json',
392
+ ...options?.headers,
393
+ },
394
+ body: JSON.stringify(body),
395
+ ...options,
396
+ }).then(res => res.json());`;
397
+ }
398
+ else if (hasQuery) {
399
+ // GET (or other method) with typed query string (no body)
400
+ fetchCode = ` const url = new URL(\`\${API_BASE_PATH}${urlPath}\`, window.location.origin);
401
+ Object.entries(query).forEach(([key, value]) => {
402
+ url.searchParams.append(key, String(value));
403
+ });
404
+ return fetch(url.pathname + url.search, {
405
+ method: '${route.method.toUpperCase()}',
406
+ ...options,
407
+ }).then(res => res.json());`;
408
+ }
409
+ else {
410
+ // GET/DELETE/etc without body or query
411
+ fetchCode = ` return fetch(\`\${API_BASE_PATH}${urlPath}\`, {
412
+ method: '${route.method.toUpperCase()}',
413
+ ...options,
414
+ }).then(res => res.json());`;
415
+ }
416
+ return `export async function ${functionName}(${params.join(', ')})${returnType} {
417
+ ${fetchCode}
418
+ }`;
419
+ }).join('\n\n');
420
+ return `${imports}${functions}
421
+ `;
422
+ }
423
+ /**
424
+ * Generates unique function names for all routes, ensuring there are no collisions
425
+ */
426
+ function generateUniqueFunctionNames(routes) {
427
+ const nameMap = new Map(); // routeKey -> functionName
428
+ const nameToRoutes = new Map(); // functionName -> routes[]
429
+ // First pass: group routes by base name
430
+ routes.forEach(route => {
431
+ const baseName = generateFunctionNameBase(route);
432
+ if (!nameToRoutes.has(baseName)) {
433
+ nameToRoutes.set(baseName, []);
434
+ }
435
+ nameToRoutes.get(baseName).push(route);
436
+ });
437
+ // Second pass: resolve collisions
438
+ routes.forEach(route => {
439
+ const routeKey = `${route.method}:${route.pattern}`;
440
+ const baseName = generateFunctionNameBase(route);
441
+ const routesWithSameName = nameToRoutes.get(baseName);
442
+ if (routesWithSameName.length === 1) {
443
+ // Unique name, use directly
444
+ nameMap.set(routeKey, baseName);
445
+ }
446
+ else {
447
+ // There's a collision, we need to make it unique by including more context
448
+ const uniqueName = generateUniqueFunctionName(route, baseName, routesWithSameName, nameMap);
449
+ nameMap.set(routeKey, uniqueName);
450
+ }
451
+ });
452
+ return nameMap;
453
+ }
454
+ /**
455
+ * Generates base function name from a route
456
+ * Example: "users/:id" + "get" -> "getUsersById"
457
+ * Example: "posts/index" + "get" -> "getPosts" (index is removed)
458
+ * Example: "" + "get" -> "get" (root route)
459
+ */
460
+ function generateFunctionNameBase(route) {
461
+ const method = route.method.toLowerCase();
462
+ if (route.pattern === '') {
463
+ // Rota raiz
464
+ return method;
465
+ }
466
+ const parts = route.pattern
467
+ .split('/')
468
+ .filter(Boolean)
469
+ .filter(part => part !== 'index') // Remove "index" from function name
470
+ .map(part => {
471
+ // Remove : or * from the beginning
472
+ const clean = part.replace(/^[:*]/, '');
473
+ // Capitalize
474
+ return capitalize(clean);
475
+ });
476
+ const pathName = parts.join('');
477
+ return `${method}${pathName}`;
478
+ }
479
+ /**
480
+ * Generates a unique function name when there's a collision
481
+ * Includes more context from the pattern to differentiate
482
+ */
483
+ function generateUniqueFunctionName(route, baseName, conflictingRoutes, existingNames) {
484
+ // Get all parts of the pattern (including parameters as context)
485
+ const patternParts = route.pattern.split('/').filter(Boolean);
486
+ // Remove "index" but keep other parts (including params for context)
487
+ const allParts = patternParts
488
+ .filter(part => part !== 'index')
489
+ .map(part => {
490
+ // For parameters, include the name as context to differentiate
491
+ // Example: ":id" becomes "Id", "*ids" becomes "Ids"
492
+ const clean = part.replace(/^[:*]/, '');
493
+ return capitalize(clean);
494
+ });
495
+ // Use all parts of the path to ensure uniqueness from the start
496
+ if (allParts.length > 0) {
497
+ const fullPathName = allParts.join('');
498
+ const candidate = `${route.method.toLowerCase()}${fullPathName}`;
499
+ // Check if this name has already been assigned to another route
500
+ const usedNames = new Set(existingNames.values());
501
+ if (!usedNames.has(candidate)) {
502
+ return candidate;
503
+ }
504
+ }
505
+ // If it still collides, try adding suffix with the last different parts
506
+ // Compare with other conflicting routes to find differences
507
+ const otherPatterns = conflictingRoutes
508
+ .filter(r => r.pattern !== route.pattern)
509
+ .map(r => r.pattern.split('/').filter(Boolean));
510
+ // Try using more parts from the end of the path
511
+ for (let i = allParts.length; i > 0; i--) {
512
+ const suffix = allParts.slice(-i).join('');
513
+ if (suffix) {
514
+ const candidate = `${baseName}${suffix}`;
515
+ const usedNames = new Set(existingNames.values());
516
+ if (!usedNames.has(candidate)) {
517
+ return candidate;
518
+ }
519
+ }
520
+ }
521
+ // Last resort: add sequential numeric suffix
522
+ let counter = 1;
523
+ let uniqueName = `${baseName}${counter}`;
524
+ const usedNames = new Set(existingNames.values());
525
+ while (usedNames.has(uniqueName)) {
526
+ counter++;
527
+ uniqueName = `${baseName}${counter}`;
528
+ }
529
+ return uniqueName;
530
+ }
531
+ /**
532
+ * Generates the URL path by replacing params with template literals
533
+ * Example: "users/:id" -> "/users/${params.id}"
534
+ * Example: "posts/index" -> "/posts" (index is removed)
535
+ * Example: "" -> "/"
536
+ */
537
+ function generateUrlPath(pattern, params) {
538
+ if (pattern === '') {
539
+ return '/';
540
+ }
541
+ // Remove "index" from the end of pattern if it exists
542
+ let cleanPattern = pattern.replace(/\/index$/, '').replace(/^index\//, '');
543
+ if (cleanPattern === 'index') {
544
+ cleanPattern = '';
545
+ }
546
+ if (cleanPattern === '') {
547
+ return '/';
548
+ }
549
+ let urlPath = '/' + cleanPattern;
550
+ // Substitui :param por ${params.param}
551
+ // Substitui *param por ${params.param} (catch-all)
552
+ params.forEach(param => {
553
+ // Para catch-all (*param), substitui o *param
554
+ const catchAllPattern = `*${param}`;
555
+ if (urlPath.includes(catchAllPattern)) {
556
+ urlPath = urlPath.replace(catchAllPattern, `\${params.${param}}`);
557
+ }
558
+ else {
559
+ // Para parâmetro normal (:param)
560
+ urlPath = urlPath.replace(`:${param}`, `\${params.${param}}`);
561
+ }
562
+ });
563
+ return urlPath;
564
+ }
565
+ /**
566
+ * Writes the generated services file
567
+ */
568
+ export async function generateServicesFile(outputPath, routes, apiBasePath, isTypeScript = true) {
569
+ const content = generateServicesContent(routes, apiBasePath, isTypeScript);
570
+ // Create directory if it doesn't exist
571
+ const dir = path.dirname(outputPath);
572
+ if (!fs.existsSync(dir)) {
573
+ fs.mkdirSync(dir, { recursive: true });
574
+ }
575
+ fs.writeFileSync(outputPath, content, 'utf-8');
576
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Schema for type generation
3
+ * Core logic - no dependencies
4
+ */
5
+ import type { Route } from '../routing/route-types.js';
6
+ /**
7
+ * Schema of a route for type generation
8
+ */
9
+ export interface RouteSchema {
10
+ pattern: string;
11
+ method: string;
12
+ params: string[];
13
+ file: string;
14
+ bodyType?: string;
15
+ queryType?: string;
16
+ }
17
+ /**
18
+ * Converts routes to schema
19
+ */
20
+ export declare function routesToSchema(routes: Route[]): RouteSchema[];
21
+ //# sourceMappingURL=schema.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../../src/core/types/schema.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,2BAA2B,CAAC;AAEvD;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,KAAK,EAAE,GAAG,WAAW,EAAE,CAS7D"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Schema for type generation
3
+ * Core logic - no dependencies
4
+ */
5
+ /**
6
+ * Converts routes to schema
7
+ */
8
+ export function routesToSchema(routes) {
9
+ return routes.map(route => ({
10
+ pattern: route.pattern,
11
+ method: route.method,
12
+ params: route.params,
13
+ file: route.file,
14
+ bodyType: route.bodyType,
15
+ queryType: route.queryType,
16
+ }));
17
+ }
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Validation types
3
+ * Core logic - runtime agnostic
4
+ */
5
+ /**
6
+ * Validation rule for a field
7
+ */
8
+ export interface ValidationRule {
9
+ type: 'string' | 'number' | 'boolean' | 'object' | 'array';
10
+ required?: boolean;
11
+ min?: number;
12
+ max?: number;
13
+ pattern?: string | RegExp;
14
+ custom?: (value: any) => boolean | string;
15
+ }
16
+ /**
17
+ * Validation schema for body or query parameters
18
+ */
19
+ export type ValidationSchema = Record<string, ValidationRule>;
20
+ /**
21
+ * Validation result
22
+ */
23
+ export interface ValidationResult {
24
+ valid: boolean;
25
+ errors?: Record<string, string[]>;
26
+ }
27
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/core/validation/types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,QAAQ,GAAG,QAAQ,GAAG,SAAS,GAAG,QAAQ,GAAG,OAAO,CAAC;IAC3D,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC1B,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,KAAK,OAAO,GAAG,MAAM,CAAC;CAC3C;AAED;;GAEG;AACH,MAAM,MAAM,gBAAgB,GAAG,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;AAE9D;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;CACnC"}
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Validation types
3
+ * Core logic - runtime agnostic
4
+ */
5
+ export {};
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Validation logic
3
+ * Core logic - runtime agnostic
4
+ */
5
+ import type { ValidationSchema, ValidationResult } from './types.js';
6
+ /**
7
+ * Validates an object against a validation schema
8
+ */
9
+ export declare function validate(data: any, schema: ValidationSchema): ValidationResult;
10
+ /**
11
+ * Validates and throws ValidationError if invalid
12
+ */
13
+ export declare function validateOrThrow(data: any, schema: ValidationSchema): void;
14
+ /**
15
+ * Validates body parameters
16
+ */
17
+ export declare function validateBody(body: any, schema: ValidationSchema): any;
18
+ /**
19
+ * Validates query parameters
20
+ */
21
+ export declare function validateQuery(query: any, schema: ValidationSchema): any;
22
+ //# sourceMappingURL=validator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validator.d.ts","sourceRoot":"","sources":["../../../src/core/validation/validator.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,KAAK,EAAE,gBAAgB,EAAE,gBAAgB,EAAkB,MAAM,YAAY,CAAC;AAiGrF;;GAEG;AACH,wBAAgB,QAAQ,CACtB,IAAI,EAAE,GAAG,EACT,MAAM,EAAE,gBAAgB,GACvB,gBAAgB,CAmBlB;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,GAAG,EAAE,MAAM,EAAE,gBAAgB,GAAG,IAAI,CAKzE;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,GAAG,EAAE,MAAM,EAAE,gBAAgB,GAAG,GAAG,CAGrE;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,GAAG,EAAE,MAAM,EAAE,gBAAgB,GAAG,GAAG,CAGvE"}