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,23 @@
1
+ /**
2
+ * Adapter for integration with Vite development server
3
+ * Thin layer that connects core → Vite
4
+ */
5
+ import type { Connect, ViteDevServer } from 'vite';
6
+ import type { IncomingMessage, ServerResponse } from 'http';
7
+ import type { VitekLogger } from './logger.js';
8
+ export interface ViteDevServerOptions {
9
+ root: string;
10
+ apiDir: string;
11
+ logger: VitekLogger;
12
+ viteServer: ViteDevServer;
13
+ enableValidation?: boolean;
14
+ }
15
+ /**
16
+ * Creates middleware for Vite development server
17
+ */
18
+ export declare function createViteDevServerMiddleware(options: ViteDevServerOptions): {
19
+ middleware: (req: IncomingMessage, res: ServerResponse, next: Connect.NextFunction) => Promise<void>;
20
+ cleanup: () => void;
21
+ reload: () => Promise<void>;
22
+ };
23
+ //# sourceMappingURL=dev-server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dev-server.d.ts","sourceRoot":"","sources":["../../../src/adapters/vite/dev-server.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,MAAM,CAAC;AACnD,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,MAAM,CAAC;AAsB5D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAG/C,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,WAAW,CAAC;IACpB,UAAU,EAAE,aAAa,CAAC;IAC1B,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B;AAoRD;;GAEG;AACH,wBAAgB,6BAA6B,CAAC,OAAO,EAAE,oBAAoB;sBAUhE,eAAe,OACf,cAAc,QACb,OAAO,CAAC,YAAY;;;EAoL/B"}
@@ -0,0 +1,428 @@
1
+ /**
2
+ * Adapter for integration with Vite development server
3
+ * Thin layer that connects core → Vite
4
+ */
5
+ import * as path from 'path';
6
+ import * as fs from 'fs';
7
+ import { scanApiDirectory } from '../../core/file-system/scan-api-dir.js';
8
+ /**
9
+ * Detects if the project uses TypeScript by checking if tsconfig.json exists
10
+ */
11
+ function isTypeScriptProject(root) {
12
+ const tsconfigPath = path.join(root, 'tsconfig.json');
13
+ return fs.existsSync(tsconfigPath);
14
+ }
15
+ import { watchApiDirectory } from '../../core/file-system/watch-api-dir.js';
16
+ import { createRoute } from '../../core/routing/route-parser.js';
17
+ import { matchRoute } from '../../core/routing/route-matcher.js';
18
+ import { compose } from '../../core/middleware/compose.js';
19
+ import { getApplicableMiddlewares } from '../../core/middleware/get-applicable-middlewares.js';
20
+ import { createContext, isVitekResponse } from '../../core/context/create-context.js';
21
+ import { routesToSchema } from '../../core/types/schema.js';
22
+ import { generateTypesFile, generateServicesFile } from '../../core/types/generate.js';
23
+ import { API_BASE_PATH, GENERATED_TYPES_FILE, GENERATED_SERVICES_FILE } from '../../shared/constants.js';
24
+ import { HttpError } from '../../shared/errors.js';
25
+ /**
26
+ * Development server state
27
+ */
28
+ class DevServerState {
29
+ options;
30
+ routes = [];
31
+ middlewares = []; // Loaded hierarchical middlewares
32
+ watcher = null;
33
+ constructor(options) {
34
+ this.options = options;
35
+ }
36
+ /**
37
+ * Initializes the server: scan, load routes and middleware
38
+ */
39
+ async initialize() {
40
+ await this.reload(false); // Don't show "Reloading" on initialization
41
+ this.setupWatcher();
42
+ }
43
+ /**
44
+ * Reloads routes and middleware
45
+ */
46
+ async reload(showReloadLog = true) {
47
+ if (showReloadLog) {
48
+ this.options.logger.info('Reloading API routes...');
49
+ }
50
+ // Scan directory
51
+ const scanResult = scanApiDirectory(this.options.apiDir);
52
+ // Load hierarchical middlewares
53
+ this.middlewares = [];
54
+ for (const middlewareInfo of scanResult.middlewares) {
55
+ try {
56
+ // Convert absolute path to relative path to Vite root (format /src/api/posts/middleware.ts)
57
+ const relativePath = path.relative(this.options.root, middlewareInfo.path);
58
+ const vitePath = `/${relativePath.replace(/\\/g, '/')}`;
59
+ // Use Vite's ssrLoadModule to process TypeScript
60
+ const middlewareModule = await this.options.viteServer.ssrLoadModule(vitePath);
61
+ const middleware = middlewareModule.default || middlewareModule.middleware;
62
+ let middlewareArray = [];
63
+ if (Array.isArray(middleware)) {
64
+ middlewareArray = middleware;
65
+ }
66
+ else if (typeof middleware === 'function') {
67
+ middlewareArray = [middleware];
68
+ }
69
+ if (middlewareArray.length > 0) {
70
+ this.middlewares.push({
71
+ middleware: middlewareArray,
72
+ basePattern: middlewareInfo.basePattern,
73
+ });
74
+ }
75
+ }
76
+ catch (error) {
77
+ this.options.logger.warn(`Failed to load middleware ${middlewareInfo.path}: ${error instanceof Error ? error.message : String(error)}`);
78
+ }
79
+ }
80
+ const totalMiddlewareCount = this.middlewares.reduce((sum, m) => sum + m.middleware.length, 0);
81
+ this.options.logger.middlewareLoaded(totalMiddlewareCount);
82
+ // Load routes
83
+ this.routes = [];
84
+ for (const parsedRoute of scanResult.routes) {
85
+ try {
86
+ // Convert absolute path to relative path to Vite root (format /src/api/users/[id].get.ts)
87
+ const relativePath = path.relative(this.options.root, parsedRoute.file);
88
+ const vitePath = `/${relativePath.replace(/\\/g, '/')}`;
89
+ // Use Vite's ssrLoadModule to process TypeScript
90
+ const handlerModule = await this.options.viteServer.ssrLoadModule(vitePath);
91
+ const handler = handlerModule.default || handlerModule.handler || handlerModule[parsedRoute.method];
92
+ if (typeof handler !== 'function') {
93
+ this.options.logger.warn(`Route file ${parsedRoute.file} does not export a handler function`);
94
+ continue;
95
+ }
96
+ // Extract bodyType from file (looking for export type Body or export interface Body)
97
+ const bodyType = extractBodyTypeFromFile(parsedRoute.file);
98
+ // Extract queryType from file (looking for export type Query or export interface Query)
99
+ const queryType = extractQueryTypeFromFile(parsedRoute.file);
100
+ const route = createRoute(parsedRoute, handler, bodyType, queryType);
101
+ this.routes.push(route);
102
+ }
103
+ catch (error) {
104
+ this.options.logger.error(`Failed to load route ${parsedRoute.file}: ${error instanceof Error ? error.message : String(error)}`);
105
+ }
106
+ }
107
+ // Log registered routes (consolidated)
108
+ const routesInfo = this.routes.map(r => ({
109
+ method: r.method,
110
+ pattern: r.pattern,
111
+ }));
112
+ this.options.logger.routesRegistered(routesInfo, API_BASE_PATH);
113
+ // Generate types
114
+ await this.generateTypes();
115
+ }
116
+ /**
117
+ * Sets up watcher to reload when files change
118
+ */
119
+ setupWatcher() {
120
+ if (this.watcher) {
121
+ this.watcher.close();
122
+ }
123
+ this.watcher = watchApiDirectory(this.options.apiDir, async (event, filePath) => {
124
+ this.options.logger.info(`API file ${event}: ${filePath}`);
125
+ await this.reload();
126
+ });
127
+ }
128
+ /**
129
+ * Generates types and services files
130
+ */
131
+ async generateTypes() {
132
+ try {
133
+ const schema = routesToSchema(this.routes);
134
+ const isTypeScript = isTypeScriptProject(this.options.root);
135
+ // Generate api.types.ts only if it's a TypeScript project
136
+ if (isTypeScript) {
137
+ const typesPath = path.join(this.options.root, 'src', GENERATED_TYPES_FILE);
138
+ await generateTypesFile(typesPath, schema, API_BASE_PATH);
139
+ const relativeTypesPath = path.relative(this.options.root, typesPath);
140
+ this.options.logger.typesGenerated(`./${relativeTypesPath.replace(/\\/g, '/')}`);
141
+ }
142
+ // Generate api.services.ts or api.services.js depending on the project
143
+ const servicesFileName = isTypeScript ? GENERATED_SERVICES_FILE : 'api.services.js';
144
+ const servicesPath = path.join(this.options.root, 'src', servicesFileName);
145
+ await generateServicesFile(servicesPath, schema, API_BASE_PATH, isTypeScript);
146
+ const relativeServicesPath = path.relative(this.options.root, servicesPath);
147
+ this.options.logger.servicesGenerated(`./${relativeServicesPath.replace(/\\/g, '/')}`);
148
+ }
149
+ catch (error) {
150
+ this.options.logger.error(`Failed to generate types: ${error instanceof Error ? error.message : String(error)}`);
151
+ }
152
+ }
153
+ /**
154
+ * Cleans up resources
155
+ */
156
+ cleanup() {
157
+ if (this.watcher) {
158
+ this.watcher.close();
159
+ this.watcher = null;
160
+ }
161
+ }
162
+ }
163
+ /**
164
+ * Extracts the body type from a route file
165
+ * Looks for export type Body = ... or export interface Body { ... }
166
+ * Returns the complete type definition as a string
167
+ */
168
+ function extractBodyTypeFromFile(filePath) {
169
+ return extractTypeFromFile(filePath, 'Body');
170
+ }
171
+ /**
172
+ * Extracts the query type from a route file
173
+ * Looks for export type Query = ... or export interface Query { ... }
174
+ * Returns the complete type definition as a string
175
+ */
176
+ function extractQueryTypeFromFile(filePath) {
177
+ return extractTypeFromFile(filePath, 'Query');
178
+ }
179
+ /**
180
+ * Helper function to extract a type from a file
181
+ * Uses regex-based extraction (synchronous)
182
+ *
183
+ * Note: AST-based extraction using ts-morph would be more robust but requires:
184
+ * 1. Making this function async
185
+ * 2. Adding ts-morph as optional dependency
186
+ * 3. Updating call sites to handle async
187
+ * This can be implemented in a future version when needed.
188
+ */
189
+ function extractTypeFromFile(filePath, typeName) {
190
+ try {
191
+ const content = fs.readFileSync(filePath, 'utf-8');
192
+ // Look for export type {TypeName} = ...
193
+ const typeStart = content.indexOf(`export type ${typeName}`);
194
+ if (typeStart !== -1) {
195
+ const afterStart = content.substring(typeStart);
196
+ const equalsIndex = afterStart.indexOf('=');
197
+ if (equalsIndex !== -1) {
198
+ const afterEquals = afterStart.substring(equalsIndex + 1).trimStart();
199
+ // If it starts with {, need to count braces to find the correct closing
200
+ if (afterEquals.startsWith('{')) {
201
+ let braceCount = 0;
202
+ let i = 0;
203
+ let foundClose = false;
204
+ for (; i < afterEquals.length; i++) {
205
+ if (afterEquals[i] === '{') {
206
+ braceCount++;
207
+ }
208
+ else if (afterEquals[i] === '}') {
209
+ braceCount--;
210
+ if (braceCount === 0) {
211
+ foundClose = true;
212
+ break;
213
+ }
214
+ }
215
+ }
216
+ if (foundClose) {
217
+ const typeBody = afterEquals.substring(0, i + 1).trim();
218
+ return typeBody;
219
+ }
220
+ }
221
+ else {
222
+ // If it doesn't start with {, it's a simple type alias (e.g., string, number, etc)
223
+ // Get until the first ; (but may have line breaks)
224
+ const semicolonIndex = afterEquals.indexOf(';');
225
+ if (semicolonIndex !== -1) {
226
+ return afterEquals.substring(0, semicolonIndex).trim();
227
+ }
228
+ }
229
+ }
230
+ }
231
+ // Look for export interface {TypeName} { ... }
232
+ const interfaceStart = content.indexOf(`export interface ${typeName}`);
233
+ if (interfaceStart !== -1) {
234
+ const afterStart = content.substring(interfaceStart);
235
+ const openBrace = afterStart.indexOf('{');
236
+ if (openBrace !== -1) {
237
+ let braceCount = 0;
238
+ let i = openBrace;
239
+ let foundClose = false;
240
+ for (; i < afterStart.length; i++) {
241
+ if (afterStart[i] === '{') {
242
+ braceCount++;
243
+ }
244
+ else if (afterStart[i] === '}') {
245
+ braceCount--;
246
+ if (braceCount === 0) {
247
+ foundClose = true;
248
+ break;
249
+ }
250
+ }
251
+ }
252
+ if (foundClose) {
253
+ const interfaceBody = afterStart.substring(openBrace + 1, i).trim();
254
+ return `{ ${interfaceBody} }`;
255
+ }
256
+ }
257
+ }
258
+ return undefined;
259
+ }
260
+ catch (error) {
261
+ // If unable to read the file, return undefined
262
+ return undefined;
263
+ }
264
+ }
265
+ /**
266
+ * Creates middleware for Vite development server
267
+ */
268
+ export function createViteDevServerMiddleware(options) {
269
+ const state = new DevServerState(options);
270
+ // Initialize when middleware is created
271
+ state.initialize().catch(error => {
272
+ options.logger.error(`Failed to initialize Vitek: ${error instanceof Error ? error.message : String(error)}`);
273
+ });
274
+ return {
275
+ middleware: async (req, res, next) => {
276
+ // Only process requests for /api/*
277
+ if (!req.url?.startsWith(API_BASE_PATH)) {
278
+ return next();
279
+ }
280
+ const startTime = Date.now();
281
+ const requestMethod = req.method?.toLowerCase() || 'get';
282
+ const requestPath = req.url.split('?')[0]; // Path without query string
283
+ try {
284
+ // Parse URL to separate path from query string
285
+ const url = new URL(req.url, `http://${req.headers.host || 'localhost'}`);
286
+ // Remove /api from path for matching
287
+ const routePath = url.pathname.replace(API_BASE_PATH, '') || '/';
288
+ const method = requestMethod;
289
+ // Try to match with a route
290
+ const match = matchRoute(state.routes, routePath, method);
291
+ if (!match) {
292
+ const duration = Date.now() - startTime;
293
+ res.statusCode = 404;
294
+ res.setHeader('Content-Type', 'application/json');
295
+ res.end(JSON.stringify({ error: 'Route not found' }));
296
+ options.logger.request(requestMethod, requestPath, 404, duration);
297
+ return;
298
+ }
299
+ // Log route match if enabled
300
+ options.logger.routeMatched(match.route.pattern, method);
301
+ // Parse query string (url was already created above)
302
+ const query = {};
303
+ url.searchParams.forEach((value, key) => {
304
+ if (query[key]) {
305
+ const existing = query[key];
306
+ query[key] = Array.isArray(existing) ? [...existing, value] : [existing, value];
307
+ }
308
+ else {
309
+ query[key] = value;
310
+ }
311
+ });
312
+ // Parse body if present
313
+ let body;
314
+ if (['post', 'put', 'patch'].includes(method)) {
315
+ body = await new Promise((resolve) => {
316
+ const chunks = [];
317
+ req.on('data', (chunk) => {
318
+ chunks.push(chunk);
319
+ });
320
+ req.on('end', () => {
321
+ const rawBody = Buffer.concat(chunks).toString();
322
+ if (!rawBody) {
323
+ resolve(undefined);
324
+ return;
325
+ }
326
+ try {
327
+ resolve(JSON.parse(rawBody));
328
+ }
329
+ catch {
330
+ resolve(rawBody);
331
+ }
332
+ });
333
+ });
334
+ }
335
+ // Create context
336
+ const context = createContext({
337
+ url: req.url,
338
+ method,
339
+ headers: req.headers,
340
+ body,
341
+ }, match.params, query);
342
+ // Note: Validation is opt-in and can be done manually in handlers using validateBody/validateQuery
343
+ // Automatic validation based on TypeScript types would require AST parsing (Phase 2.2)
344
+ // For now, validation is available as helper functions that handlers can use
345
+ // Get applicable middlewares for this route
346
+ const applicableMiddlewares = getApplicableMiddlewares(state.middlewares, match.route.pattern);
347
+ // Compose middlewares + handler
348
+ const composed = compose(applicableMiddlewares);
349
+ const handler = async () => {
350
+ const result = await match.route.handler(context);
351
+ // Handle VitekResponse format (with status and headers)
352
+ if (isVitekResponse(result)) {
353
+ const response = result;
354
+ const statusCode = response.status || 200;
355
+ // Set headers
356
+ if (response.headers) {
357
+ for (const [key, value] of Object.entries(response.headers)) {
358
+ res.setHeader(key, value);
359
+ }
360
+ }
361
+ // Set default Content-Type if not specified and body exists
362
+ if (!response.headers || !response.headers['Content-Type']) {
363
+ if (response.body !== undefined) {
364
+ res.setHeader('Content-Type', 'application/json');
365
+ }
366
+ }
367
+ res.statusCode = statusCode;
368
+ // Handle different response types
369
+ if (response.body === undefined) {
370
+ res.end();
371
+ }
372
+ else if (typeof response.body === 'string') {
373
+ res.end(response.body);
374
+ }
375
+ else {
376
+ res.end(JSON.stringify(response.body));
377
+ }
378
+ // Log request/response
379
+ const duration = Date.now() - startTime;
380
+ options.logger.request(requestMethod, requestPath, statusCode, duration);
381
+ }
382
+ else {
383
+ // Backward compatibility: plain object/primitive → JSON response with status 200
384
+ res.setHeader('Content-Type', 'application/json');
385
+ res.statusCode = 200;
386
+ res.end(JSON.stringify(result));
387
+ // Log request/response
388
+ const duration = Date.now() - startTime;
389
+ options.logger.request(requestMethod, requestPath, 200, duration);
390
+ }
391
+ };
392
+ await composed(context, handler);
393
+ }
394
+ catch (error) {
395
+ const duration = Date.now() - startTime;
396
+ // Handle HTTP errors with proper status codes
397
+ if (error instanceof HttpError) {
398
+ const httpError = error;
399
+ options.logger.warn(`HTTP Error ${httpError.statusCode}: ${httpError.message}`);
400
+ res.statusCode = httpError.statusCode;
401
+ res.setHeader('Content-Type', 'application/json');
402
+ res.end(JSON.stringify({
403
+ error: httpError.name,
404
+ message: httpError.message,
405
+ code: httpError.code,
406
+ }));
407
+ // Log request/response
408
+ options.logger.request(requestMethod, requestPath, httpError.statusCode, duration);
409
+ }
410
+ else {
411
+ // Generic error handling (backward compatible)
412
+ const errorMessage = error instanceof Error ? error.message : String(error);
413
+ options.logger.error(`Error handling request: ${errorMessage}`);
414
+ res.statusCode = 500;
415
+ res.setHeader('Content-Type', 'application/json');
416
+ res.end(JSON.stringify({
417
+ error: 'Internal server error',
418
+ message: errorMessage,
419
+ }));
420
+ // Log request/response
421
+ options.logger.request(requestMethod, requestPath, 500, duration);
422
+ }
423
+ }
424
+ },
425
+ cleanup: () => state.cleanup(),
426
+ reload: () => state.reload(),
427
+ };
428
+ }
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Logger adapter for Vite
3
+ * Translates core events into Vite logs
4
+ */
5
+ import type { Logger } from 'vite';
6
+ export type LogLevel = 'debug' | 'info' | 'warn' | 'error';
7
+ export interface LoggingOptions {
8
+ level?: LogLevel;
9
+ enableRequestLogging?: boolean;
10
+ enableRouteLogging?: boolean;
11
+ }
12
+ export interface VitekLogger {
13
+ debug(message: string, data?: Record<string, any>): void;
14
+ info(message: string, data?: Record<string, any>): void;
15
+ warn(message: string, data?: Record<string, any>): void;
16
+ error(message: string, data?: Record<string, any>): void;
17
+ routesRegistered(routes: Array<{
18
+ method: string;
19
+ pattern: string;
20
+ }>, apiBasePath: string): void;
21
+ routeMatched(pattern: string, method: string): void;
22
+ middlewareLoaded(count: number): void;
23
+ typesGenerated(outputPath: string): void;
24
+ servicesGenerated(outputPath: string): void;
25
+ request(method: string, path: string, statusCode: number, duration?: number): void;
26
+ response(method: string, path: string, statusCode: number, duration?: number): void;
27
+ getOptions(): LoggingOptions;
28
+ }
29
+ /**
30
+ * Creates a logger that uses Vite's logger
31
+ */
32
+ export declare function createViteLogger(viteLogger: Logger, options?: LoggingOptions): VitekLogger;
33
+ //# sourceMappingURL=logger.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../../src/adapters/vite/logger.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AAEnC,MAAM,MAAM,QAAQ,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;AAE3D,MAAM,WAAW,cAAc;IAC7B,KAAK,CAAC,EAAE,QAAQ,CAAC;IACjB,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,kBAAkB,CAAC,EAAE,OAAO,CAAC;CAC9B;AAoBD,MAAM,WAAW,WAAW;IAC1B,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,IAAI,CAAC;IACzD,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,IAAI,CAAC;IACxD,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,IAAI,CAAC;IACxD,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,IAAI,CAAC;IACzD,gBAAgB,CAAC,MAAM,EAAE,KAAK,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,EAAE,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAChG,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACpD,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACtC,cAAc,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IACzC,iBAAiB,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5C,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACnF,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACpF,UAAU,IAAI,cAAc,CAAC;CAC9B;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,cAAc,GAAG,WAAW,CAyH1F"}
@@ -0,0 +1,112 @@
1
+ /**
2
+ * Logger adapter for Vite
3
+ * Translates core events into Vite logs
4
+ */
5
+ /**
6
+ * Formats the [vitek] tag with green color and bold
7
+ * Uses ANSI codes: \x1b[1m = bold, \x1b[32m = green, \x1b[0m = reset
8
+ */
9
+ function formatTag(text) {
10
+ return `\x1b[1m\x1b[32m${text}\x1b[0m`;
11
+ }
12
+ /**
13
+ * Checks if a log level should be logged based on the configured level
14
+ */
15
+ function shouldLog(level, configuredLevel) {
16
+ const levels = ['debug', 'info', 'warn', 'error'];
17
+ const levelIndex = levels.indexOf(level);
18
+ const configuredIndex = levels.indexOf(configuredLevel);
19
+ return levelIndex >= configuredIndex;
20
+ }
21
+ /**
22
+ * Creates a logger that uses Vite's logger
23
+ */
24
+ export function createViteLogger(viteLogger, options) {
25
+ const tag = formatTag('[vitek]');
26
+ const logLevel = options?.level || 'info';
27
+ const enableRequestLogging = options?.enableRequestLogging || false;
28
+ const enableRouteLogging = options?.enableRouteLogging !== false; // Default true
29
+ const formatData = (data) => {
30
+ if (!data || Object.keys(data).length === 0) {
31
+ return '';
32
+ }
33
+ return ' ' + JSON.stringify(data);
34
+ };
35
+ return {
36
+ debug(message, data) {
37
+ if (shouldLog('debug', logLevel)) {
38
+ viteLogger.info(`${tag} [DEBUG] ${message}${formatData(data)}`, { timestamp: true });
39
+ }
40
+ },
41
+ info(message, data) {
42
+ if (shouldLog('info', logLevel)) {
43
+ viteLogger.info(`${tag} ${message}${formatData(data)}`, { timestamp: true });
44
+ }
45
+ },
46
+ warn(message, data) {
47
+ if (shouldLog('warn', logLevel)) {
48
+ viteLogger.warn(`${tag} ${message}${formatData(data)}`, { timestamp: true });
49
+ }
50
+ },
51
+ error(message, data) {
52
+ if (shouldLog('error', logLevel)) {
53
+ viteLogger.error(`${tag} ${message}${formatData(data)}`, { timestamp: true });
54
+ }
55
+ },
56
+ routesRegistered(routes, apiBasePath) {
57
+ if (routes.length === 0) {
58
+ if (shouldLog('info', logLevel)) {
59
+ viteLogger.info(`${tag} No routes registered`, { timestamp: true });
60
+ }
61
+ return;
62
+ }
63
+ if (shouldLog('info', logLevel)) {
64
+ const routesList = routes
65
+ .map(r => {
66
+ const pattern = r.pattern === '' ? '/' : `/${r.pattern}`;
67
+ return ` ${r.method.toUpperCase()} ${apiBasePath}${pattern}`;
68
+ })
69
+ .join('\n');
70
+ viteLogger.info(`${tag} Registered routes:\n${routesList}`, { timestamp: true });
71
+ }
72
+ },
73
+ routeMatched(pattern, method) {
74
+ if (enableRouteLogging && shouldLog('debug', logLevel)) {
75
+ viteLogger.info(`${tag} [ROUTE] ${method.toUpperCase()} ${pattern}`, { timestamp: true });
76
+ }
77
+ },
78
+ middlewareLoaded(count) {
79
+ if (shouldLog('info', logLevel)) {
80
+ viteLogger.info(`${tag} Loaded ${count} global middleware(s)`, { timestamp: true });
81
+ }
82
+ },
83
+ typesGenerated(outputPath) {
84
+ if (shouldLog('info', logLevel)) {
85
+ viteLogger.info(`${tag} Generated types: ${outputPath}`, { timestamp: true });
86
+ }
87
+ },
88
+ servicesGenerated(outputPath) {
89
+ if (shouldLog('info', logLevel)) {
90
+ viteLogger.info(`${tag} Generated services: ${outputPath}`, { timestamp: true });
91
+ }
92
+ },
93
+ request(method, path, statusCode, duration) {
94
+ if (enableRequestLogging && shouldLog('info', logLevel)) {
95
+ const durationStr = duration !== undefined ? ` (${duration}ms)` : '';
96
+ const statusColor = statusCode >= 500 ? '\x1b[31m' : statusCode >= 400 ? '\x1b[33m' : '\x1b[32m';
97
+ viteLogger.info(`${tag} [REQUEST] ${method.toUpperCase()} ${path} ${statusColor}${statusCode}\x1b[0m${durationStr}`, { timestamp: true });
98
+ }
99
+ },
100
+ response(method, path, statusCode, duration) {
101
+ // Alias for request (for consistency)
102
+ this.request(method, path, statusCode, duration);
103
+ },
104
+ getOptions() {
105
+ return {
106
+ level: logLevel,
107
+ enableRequestLogging,
108
+ enableRouteLogging,
109
+ };
110
+ },
111
+ };
112
+ }
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Context creation for route handlers
3
+ * Core logic - runtime agnostic
4
+ */
5
+ export interface VitekContext {
6
+ url: string;
7
+ method: string;
8
+ path: string;
9
+ query: Record<string, string | string[]>;
10
+ params: Record<string, string>;
11
+ headers: Record<string, string>;
12
+ body?: any;
13
+ }
14
+ export interface VitekRequest {
15
+ url: string;
16
+ method: string;
17
+ headers: Record<string, string>;
18
+ body?: any;
19
+ }
20
+ /**
21
+ * Response object that allows control over status code and headers
22
+ * If a handler returns a plain object, it will be treated as status 200 with JSON content-type
23
+ */
24
+ export interface VitekResponse {
25
+ status?: number;
26
+ headers?: Record<string, string>;
27
+ body?: any;
28
+ }
29
+ /**
30
+ * Type guard to check if a value is a VitekResponse
31
+ * A VitekResponse is identified by having 'status' (number) or 'headers' (object) properties
32
+ * Plain objects without these properties are treated as regular JSON responses (backward compatibility)
33
+ */
34
+ export declare function isVitekResponse(value: any): value is VitekResponse;
35
+ /**
36
+ * Creates a context from a request and extracted parameters
37
+ */
38
+ export declare function createContext(request: VitekRequest, params?: Record<string, string>, query?: Record<string, string | string[]>): VitekContext;
39
+ //# sourceMappingURL=create-context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"create-context.d.ts","sourceRoot":"","sources":["../../../src/core/context/create-context.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,WAAW,YAAY;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,CAAC;IACzC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,IAAI,CAAC,EAAE,GAAG,CAAC;CACZ;AAED,MAAM,WAAW,YAAY;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,IAAI,CAAC,EAAE,GAAG,CAAC;CACZ;AAED;;;GAGG;AACH,MAAM,WAAW,aAAa;IAC5B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,IAAI,CAAC,EAAE,GAAG,CAAC;CACZ;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,GAAG,GAAG,KAAK,IAAI,aAAa,CAWlE;AAED;;GAEG;AACH,wBAAgB,aAAa,CAC3B,OAAO,EAAE,YAAY,EACrB,MAAM,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM,EACnC,KAAK,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAM,GAC5C,YAAY,CAYd"}