vitek-plugin 0.1.2-beta.6 → 0.2.1-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 (166) hide show
  1. package/README.md +35 -4
  2. package/dist/adapters/vite/dev-server-middleware.d.ts +8 -0
  3. package/dist/adapters/vite/dev-server-middleware.d.ts.map +1 -0
  4. package/dist/adapters/vite/dev-server-middleware.js +30 -0
  5. package/dist/adapters/vite/dev-server-state.d.ts +41 -0
  6. package/dist/adapters/vite/dev-server-state.d.ts.map +1 -0
  7. package/dist/adapters/vite/dev-server-state.js +191 -0
  8. package/dist/adapters/vite/dev-server.d.ts +2 -21
  9. package/dist/adapters/vite/dev-server.d.ts.map +1 -1
  10. package/dist/adapters/vite/dev-server.js +7 -216
  11. package/dist/adapters/vite/path-utils.d.ts +20 -0
  12. package/dist/adapters/vite/path-utils.d.ts.map +1 -0
  13. package/dist/adapters/vite/path-utils.js +46 -0
  14. package/dist/adapters/vite/path-utils.test.d.ts +2 -0
  15. package/dist/adapters/vite/path-utils.test.d.ts.map +1 -0
  16. package/dist/adapters/vite/path-utils.test.js +79 -0
  17. package/dist/build/build-api-bundle.d.ts +1 -0
  18. package/dist/build/build-api-bundle.d.ts.map +1 -1
  19. package/dist/build/build-api-bundle.js +38 -3
  20. package/dist/build/build-api-bundle.test.d.ts +2 -0
  21. package/dist/build/build-api-bundle.test.d.ts.map +1 -0
  22. package/dist/build/build-api-bundle.test.js +50 -0
  23. package/dist/build/build-sockets-bundle.test.d.ts +2 -0
  24. package/dist/build/build-sockets-bundle.test.d.ts.map +1 -0
  25. package/dist/build/build-sockets-bundle.test.js +49 -0
  26. package/dist/cli/cli.d.ts +8 -0
  27. package/dist/cli/cli.d.ts.map +1 -0
  28. package/dist/cli/cli.js +25 -0
  29. package/dist/cli/fixtures/serve-config/vitek.config.d.mts +6 -0
  30. package/dist/cli/fixtures/serve-config/vitek.config.d.mts.map +1 -0
  31. package/dist/cli/fixtures/serve-config/vitek.config.mjs +19 -0
  32. package/dist/cli/init.d.ts +15 -0
  33. package/dist/cli/init.d.ts.map +1 -0
  34. package/dist/cli/init.js +99 -0
  35. package/dist/cli/init.test.d.ts +2 -0
  36. package/dist/cli/init.test.d.ts.map +1 -0
  37. package/dist/cli/init.test.js +117 -0
  38. package/dist/cli/mcp-project-config.d.ts +8 -0
  39. package/dist/cli/mcp-project-config.d.ts.map +1 -0
  40. package/dist/cli/mcp-project-config.js +26 -0
  41. package/dist/cli/mcp-project.d.ts +2 -0
  42. package/dist/cli/mcp-project.d.ts.map +1 -0
  43. package/dist/cli/mcp-project.js +101 -0
  44. package/dist/cli/serve.d.ts +27 -1
  45. package/dist/cli/serve.d.ts.map +1 -1
  46. package/dist/cli/serve.js +85 -10
  47. package/dist/cli/serve.test.d.ts +2 -0
  48. package/dist/cli/serve.test.d.ts.map +1 -0
  49. package/dist/cli/serve.test.js +108 -0
  50. package/dist/core/asyncapi/generate.test.d.ts +2 -0
  51. package/dist/core/asyncapi/generate.test.d.ts.map +1 -0
  52. package/dist/core/asyncapi/generate.test.js +120 -0
  53. package/dist/core/context/create-context.d.ts +2 -0
  54. package/dist/core/context/create-context.d.ts.map +1 -1
  55. package/dist/core/file-system/watch-api-dir.d.ts +4 -1
  56. package/dist/core/file-system/watch-api-dir.d.ts.map +1 -1
  57. package/dist/core/file-system/watch-api-dir.js +31 -6
  58. package/dist/core/file-system/watch-api-dir.test.d.ts +2 -0
  59. package/dist/core/file-system/watch-api-dir.test.d.ts.map +1 -0
  60. package/dist/core/file-system/watch-api-dir.test.js +38 -0
  61. package/dist/core/generation/run-file-generation.d.ts +2 -0
  62. package/dist/core/generation/run-file-generation.d.ts.map +1 -1
  63. package/dist/core/generation/run-file-generation.js +4 -1
  64. package/dist/core/introspection/manifest.d.ts +24 -0
  65. package/dist/core/introspection/manifest.d.ts.map +1 -0
  66. package/dist/core/introspection/manifest.js +41 -0
  67. package/dist/core/introspection/manifest.test.d.ts +2 -0
  68. package/dist/core/introspection/manifest.test.d.ts.map +1 -0
  69. package/dist/core/introspection/manifest.test.js +62 -0
  70. package/dist/core/middleware/get-applicable-middlewares.d.ts +7 -0
  71. package/dist/core/middleware/get-applicable-middlewares.d.ts.map +1 -1
  72. package/dist/core/middleware/get-applicable-middlewares.js +23 -15
  73. package/dist/core/middleware/get-applicable-middlewares.test.js +36 -1
  74. package/dist/core/openapi/generate.d.ts +3 -79
  75. package/dist/core/openapi/generate.d.ts.map +1 -1
  76. package/dist/core/openapi/generate.js +4 -419
  77. package/dist/core/openapi/generate.test.d.ts +2 -0
  78. package/dist/core/openapi/generate.test.d.ts.map +1 -0
  79. package/dist/core/openapi/generate.test.js +184 -0
  80. package/dist/core/openapi/jsdoc.d.ts +3 -0
  81. package/dist/core/openapi/jsdoc.d.ts.map +1 -0
  82. package/dist/core/openapi/jsdoc.js +68 -0
  83. package/dist/core/openapi/jsdoc.test.d.ts +2 -0
  84. package/dist/core/openapi/jsdoc.test.d.ts.map +1 -0
  85. package/dist/core/openapi/jsdoc.test.js +111 -0
  86. package/dist/core/openapi/spec-builder.d.ts +4 -0
  87. package/dist/core/openapi/spec-builder.d.ts.map +1 -0
  88. package/dist/core/openapi/spec-builder.js +257 -0
  89. package/dist/core/openapi/spec-builder.test.d.ts +2 -0
  90. package/dist/core/openapi/spec-builder.test.d.ts.map +1 -0
  91. package/dist/core/openapi/spec-builder.test.js +93 -0
  92. package/dist/core/openapi/types.d.ts +42 -0
  93. package/dist/core/openapi/types.d.ts.map +1 -0
  94. package/dist/core/openapi/types.js +5 -0
  95. package/dist/core/server/cors.d.ts +29 -0
  96. package/dist/core/server/cors.d.ts.map +1 -0
  97. package/dist/core/server/cors.js +55 -0
  98. package/dist/core/server/cors.test.d.ts +2 -0
  99. package/dist/core/server/cors.test.d.ts.map +1 -0
  100. package/dist/core/server/cors.test.js +49 -0
  101. package/dist/core/server/proxy.d.ts +16 -0
  102. package/dist/core/server/proxy.d.ts.map +1 -0
  103. package/dist/core/server/proxy.js +20 -0
  104. package/dist/core/server/proxy.test.d.ts +2 -0
  105. package/dist/core/server/proxy.test.d.ts.map +1 -0
  106. package/dist/core/server/proxy.test.js +53 -0
  107. package/dist/core/server/request-handler.d.ts +17 -3
  108. package/dist/core/server/request-handler.d.ts.map +1 -1
  109. package/dist/core/server/request-handler.js +192 -84
  110. package/dist/core/server/request-handler.test.js +287 -22
  111. package/dist/core/socket/socket-handler.test.d.ts +2 -0
  112. package/dist/core/socket/socket-handler.test.d.ts.map +1 -0
  113. package/dist/core/socket/socket-handler.test.js +107 -0
  114. package/dist/core/types/schema.test.d.ts +2 -0
  115. package/dist/core/types/schema.test.d.ts.map +1 -0
  116. package/dist/core/types/schema.test.js +41 -0
  117. package/dist/core/validation/types.d.ts +2 -1
  118. package/dist/core/validation/types.d.ts.map +1 -1
  119. package/dist/core/validation/validator.d.ts +4 -16
  120. package/dist/core/validation/validator.d.ts.map +1 -1
  121. package/dist/core/validation/validator.js +4 -16
  122. package/dist/index.d.ts +6 -1
  123. package/dist/index.d.ts.map +1 -1
  124. package/dist/index.js +2 -1
  125. package/dist/plugin/context.d.ts +15 -0
  126. package/dist/plugin/context.d.ts.map +1 -0
  127. package/dist/plugin/context.js +12 -0
  128. package/dist/plugin/options.d.ts +46 -0
  129. package/dist/plugin/options.d.ts.map +1 -0
  130. package/dist/plugin/options.js +1 -0
  131. package/dist/plugin/plugin-api.d.ts +49 -0
  132. package/dist/plugin/plugin-api.d.ts.map +1 -0
  133. package/dist/plugin/plugin-api.js +5 -0
  134. package/dist/plugin/vitek-build.d.ts +7 -0
  135. package/dist/plugin/vitek-build.d.ts.map +1 -0
  136. package/dist/plugin/vitek-build.js +104 -0
  137. package/dist/plugin/vitek-config.d.ts +4 -0
  138. package/dist/plugin/vitek-config.d.ts.map +1 -0
  139. package/dist/plugin/vitek-config.js +51 -0
  140. package/dist/plugin/vitek-config.test.d.ts +2 -0
  141. package/dist/plugin/vitek-config.test.d.ts.map +1 -0
  142. package/dist/plugin/vitek-config.test.js +62 -0
  143. package/dist/plugin/vitek-dev.d.ts +7 -0
  144. package/dist/plugin/vitek-dev.d.ts.map +1 -0
  145. package/dist/plugin/vitek-dev.js +71 -0
  146. package/dist/plugin/vitek-preview.d.ts +7 -0
  147. package/dist/plugin/vitek-preview.d.ts.map +1 -0
  148. package/dist/plugin/vitek-preview.js +107 -0
  149. package/dist/plugin/vitek-resolve.d.ts +7 -0
  150. package/dist/plugin/vitek-resolve.d.ts.map +1 -0
  151. package/dist/plugin/vitek-resolve.js +26 -0
  152. package/dist/plugin/vitek-transform.d.ts +7 -0
  153. package/dist/plugin/vitek-transform.d.ts.map +1 -0
  154. package/dist/plugin/vitek-transform.js +80 -0
  155. package/dist/plugin/vitek.d.ts +10 -0
  156. package/dist/plugin/vitek.d.ts.map +1 -0
  157. package/dist/plugin/vitek.js +27 -0
  158. package/dist/plugin.d.ts +3 -32
  159. package/dist/plugin.d.ts.map +1 -1
  160. package/dist/plugin.js +2 -274
  161. package/dist/plugin.test.js +99 -29
  162. package/dist/shared/response-helpers.d.ts +21 -0
  163. package/dist/shared/response-helpers.d.ts.map +1 -1
  164. package/dist/shared/response-helpers.js +41 -0
  165. package/dist/shared/response-helpers.test.js +54 -1
  166. package/package.json +32 -15
@@ -9,32 +9,57 @@ import { ROUTE_FILE_PATTERN, MIDDLEWARE_FILE_PATTERN } from '../../shared/consta
9
9
  * Creates a watcher for the API directory using Node.js fs.watch
10
10
  * Returns a function to stop watching
11
11
  */
12
- export function watchApiDirectory(apiDir, callback) {
12
+ export function watchApiDirectory(apiDir, callback, options = {}) {
13
13
  if (!fs.existsSync(apiDir)) {
14
14
  return { close: () => { } };
15
15
  }
16
+ const debounceMs = options.debounceMs ?? 0;
17
+ let pending = [];
18
+ let timer = null;
19
+ const flush = () => {
20
+ if (timer != null) {
21
+ clearTimeout(timer);
22
+ timer = null;
23
+ }
24
+ const toEmit = pending;
25
+ pending = [];
26
+ for (const { event, filePath } of toEmit) {
27
+ callback(event, filePath);
28
+ }
29
+ };
30
+ const schedule = (event, filePath) => {
31
+ pending.push({ event, filePath });
32
+ if (debounceMs <= 0) {
33
+ flush();
34
+ return;
35
+ }
36
+ if (timer != null)
37
+ clearTimeout(timer);
38
+ timer = setTimeout(flush, debounceMs);
39
+ };
16
40
  const watcher = fs.watch(apiDir, { recursive: true }, (eventType, filename) => {
17
41
  if (!filename)
18
42
  return;
19
43
  const filePath = path.join(apiDir, filename);
20
- // Ignore if it's not a route file or relevant middleware
21
44
  const isRouteFile = ROUTE_FILE_PATTERN.test(filename);
22
45
  const isMiddlewareFile = MIDDLEWARE_FILE_PATTERN.test(filename);
23
46
  if (!isRouteFile && !isMiddlewareFile) {
24
47
  return;
25
48
  }
26
- // Normalize event type
27
49
  let normalizedEvent;
28
50
  if (eventType === 'rename') {
29
- // rename can be add or unlink, check if it exists
30
51
  normalizedEvent = fs.existsSync(filePath) ? 'add' : 'unlink';
31
52
  }
32
53
  else {
33
54
  normalizedEvent = eventType;
34
55
  }
35
- callback(normalizedEvent, filePath);
56
+ schedule(normalizedEvent, filePath);
36
57
  });
37
58
  return {
38
- close: () => watcher.close(),
59
+ close: () => {
60
+ if (timer != null)
61
+ clearTimeout(timer);
62
+ watcher.close();
63
+ },
39
64
  };
40
65
  }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=watch-api-dir.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"watch-api-dir.test.d.ts","sourceRoot":"","sources":["../../../src/core/file-system/watch-api-dir.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,38 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
+ import * as fs from 'fs';
3
+ import * as path from 'path';
4
+ import { watchApiDirectory } from './watch-api-dir.js';
5
+ describe('watch-api-dir', () => {
6
+ let tmpDir;
7
+ let apiDir;
8
+ beforeEach(() => {
9
+ tmpDir = fs.mkdtempSync(path.join(process.cwd(), 'watch-api-'));
10
+ apiDir = path.join(tmpDir, 'api');
11
+ fs.mkdirSync(apiDir, { recursive: true });
12
+ });
13
+ afterEach(() => {
14
+ try {
15
+ fs.rmSync(tmpDir, { recursive: true });
16
+ }
17
+ catch {
18
+ // ignore
19
+ }
20
+ });
21
+ it('returns watcher with no-op close when apiDir does not exist', () => {
22
+ const watcher = watchApiDirectory(path.join(tmpDir, 'nonexistent'), () => { });
23
+ expect(watcher).toBeDefined();
24
+ expect(typeof watcher.close).toBe('function');
25
+ watcher.close();
26
+ });
27
+ it('returns watcher with close when apiDir exists', () => {
28
+ const watcher = watchApiDirectory(apiDir, () => { });
29
+ expect(watcher).toBeDefined();
30
+ expect(typeof watcher.close).toBe('function');
31
+ watcher.close();
32
+ });
33
+ it('accepts debounceMs option', () => {
34
+ const watcher = watchApiDirectory(apiDir, () => { }, { debounceMs: 100 });
35
+ expect(watcher).toBeDefined();
36
+ watcher.close();
37
+ });
38
+ });
@@ -17,6 +17,8 @@ export interface RunFileGenerationOptions {
17
17
  info?: (message: string) => void;
18
18
  warn?: (message: string) => void;
19
19
  };
20
+ /** Callback when generation fails (e.g. OpenAPI). Does not run for types/services write errors. */
21
+ onGenerationError?: (error: Error) => void;
20
22
  }
21
23
  export declare function runFileGeneration(options: RunFileGenerationOptions): Promise<void>;
22
24
  //# sourceMappingURL=run-file-generation.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"run-file-generation.d.ts","sourceRoot":"","sources":["../../../src/core/generation/run-file-generation.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AAC9D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAChE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAKtD,OAAO,EAA4C,KAAK,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAgBvG,wBAAgB,oBAAoB,CAAC,YAAY,EAAE,WAAW,EAAE,GAAG,WAAW,EAAE,CAS/E;AAED,MAAM,WAAW,wBAAwB;IACvC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,WAAW,EAAE,CAAC;IACtB,OAAO,EAAE,YAAY,EAAE,CAAC;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,OAAO,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC;IACnC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE;QACP,cAAc,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;QACxC,iBAAiB,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;QAC3C,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;QACjC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;KAClC,CAAC;CACH;AAED,wBAAsB,iBAAiB,CAAC,OAAO,EAAE,wBAAwB,GAAG,OAAO,CAAC,IAAI,CAAC,CAsFxF"}
1
+ {"version":3,"file":"run-file-generation.d.ts","sourceRoot":"","sources":["../../../src/core/generation/run-file-generation.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AAC9D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAChE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAKtD,OAAO,EAA4C,KAAK,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAgBvG,wBAAgB,oBAAoB,CAAC,YAAY,EAAE,WAAW,EAAE,GAAG,WAAW,EAAE,CAS/E;AAED,MAAM,WAAW,wBAAwB;IACvC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,WAAW,EAAE,CAAC;IACtB,OAAO,EAAE,YAAY,EAAE,CAAC;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,OAAO,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC;IACnC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE;QACP,cAAc,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;QACxC,iBAAiB,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;QAC3C,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;QACjC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;KAClC,CAAC;IACF,mGAAmG;IACnG,iBAAiB,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;CAC5C;AAED,wBAAsB,iBAAiB,CAAC,OAAO,EAAE,wBAAwB,GAAG,OAAO,CAAC,IAAI,CAAC,CAuFxF"}
@@ -28,6 +28,7 @@ export async function runFileGeneration(options) {
28
28
  const logServices = logger?.servicesGenerated ?? (() => { });
29
29
  const logInfo = logger?.info ?? (() => { });
30
30
  const logWarn = logger?.warn ?? (() => { });
31
+ const onGenerationError = options.onGenerationError;
31
32
  const isTypeScript = isTypeScriptProject(root);
32
33
  const srcDir = path.join(root, 'src');
33
34
  if (schema.length > 0) {
@@ -81,7 +82,9 @@ export async function runFileGeneration(options) {
81
82
  logInfo(`API docs at: ./${path.relative(root, apiDocsPath).replace(/\\/g, '/')} → http://localhost:${serverPort}/api-docs.html`);
82
83
  }
83
84
  catch (error) {
84
- logWarn(`Failed to generate OpenAPI spec: ${error instanceof Error ? error.message : String(error)}`);
85
+ const err = error instanceof Error ? error : new Error(String(error));
86
+ logWarn(`Failed to generate OpenAPI spec: ${err.message}`);
87
+ onGenerationError?.(err);
85
88
  }
86
89
  }
87
90
  }
@@ -0,0 +1,24 @@
1
+ import type { ParsedRoute } from '../routing/route-parser.js';
2
+ import type { ParsedSocket } from '../routing/socket-parser.js';
3
+ export interface VitekManifest {
4
+ routes: Array<{
5
+ method: string;
6
+ pattern: string;
7
+ params: string[];
8
+ file: string;
9
+ }>;
10
+ middlewares: Array<{
11
+ basePattern: string;
12
+ path: string;
13
+ }>;
14
+ sockets: Array<{
15
+ pattern: string;
16
+ params: string[];
17
+ file: string;
18
+ }>;
19
+ }
20
+ export declare function getManifest(root: string, apiDir: string): VitekManifest;
21
+ export declare function getRoutes(root: string, apiDir: string): ParsedRoute[];
22
+ export declare function getSockets(root: string, apiDir: string): ParsedSocket[];
23
+ export declare function writeManifest(root: string, apiDir: string, outDir: string): string;
24
+ //# sourceMappingURL=manifest.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../../../src/core/introspection/manifest.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AAC9D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAEhE,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,KAAK,CAAC;QACZ,MAAM,EAAE,MAAM,CAAC;QACf,OAAO,EAAE,MAAM,CAAC;QAChB,MAAM,EAAE,MAAM,EAAE,CAAC;QACjB,IAAI,EAAE,MAAM,CAAC;KACd,CAAC,CAAC;IACH,WAAW,EAAE,KAAK,CAAC;QACjB,WAAW,EAAE,MAAM,CAAC;QACpB,IAAI,EAAE,MAAM,CAAC;KACd,CAAC,CAAC;IACH,OAAO,EAAE,KAAK,CAAC;QACb,OAAO,EAAE,MAAM,CAAC;QAChB,MAAM,EAAE,MAAM,EAAE,CAAC;QACjB,IAAI,EAAE,MAAM,CAAC;KACd,CAAC,CAAC;CACJ;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,aAAa,CAoBvE;AAED,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,WAAW,EAAE,CAIrE;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,YAAY,EAAE,CAIvE;AAED,wBAAgB,aAAa,CAC3B,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,GACb,MAAM,CAMR"}
@@ -0,0 +1,41 @@
1
+ import * as path from 'path';
2
+ import * as fs from 'fs';
3
+ import { scanApiDirectory } from '../file-system/scan-api-dir.js';
4
+ export function getManifest(root, apiDir) {
5
+ const fullApiDir = path.isAbsolute(apiDir) ? apiDir : path.resolve(root, apiDir);
6
+ const scanResult = scanApiDirectory(fullApiDir);
7
+ return {
8
+ routes: scanResult.routes.map((r) => ({
9
+ method: r.method,
10
+ pattern: r.pattern,
11
+ params: r.params,
12
+ file: path.relative(root, r.file).replace(/\\/g, '/'),
13
+ })),
14
+ middlewares: scanResult.middlewares.map((m) => ({
15
+ basePattern: m.basePattern,
16
+ path: path.relative(root, m.path).replace(/\\/g, '/'),
17
+ })),
18
+ sockets: scanResult.sockets.map((s) => ({
19
+ pattern: s.pattern,
20
+ params: s.params,
21
+ file: path.relative(root, s.file).replace(/\\/g, '/'),
22
+ })),
23
+ };
24
+ }
25
+ export function getRoutes(root, apiDir) {
26
+ const fullApiDir = path.isAbsolute(apiDir) ? apiDir : path.resolve(root, apiDir);
27
+ const scanResult = scanApiDirectory(fullApiDir);
28
+ return scanResult.routes;
29
+ }
30
+ export function getSockets(root, apiDir) {
31
+ const fullApiDir = path.isAbsolute(apiDir) ? apiDir : path.resolve(root, apiDir);
32
+ const scanResult = scanApiDirectory(fullApiDir);
33
+ return scanResult.sockets;
34
+ }
35
+ export function writeManifest(root, apiDir, outDir) {
36
+ const manifest = getManifest(root, apiDir);
37
+ const manifestPath = path.join(outDir, 'vitek-manifest.json');
38
+ fs.mkdirSync(outDir, { recursive: true });
39
+ fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2), 'utf-8');
40
+ return manifestPath;
41
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=manifest.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manifest.test.d.ts","sourceRoot":"","sources":["../../../src/core/introspection/manifest.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,62 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
+ import * as fs from 'fs';
3
+ import * as path from 'path';
4
+ import { getManifest, getRoutes, getSockets, writeManifest } from './manifest.js';
5
+ describe('manifest', () => {
6
+ let rootDir;
7
+ let apiDir;
8
+ beforeEach(() => {
9
+ rootDir = fs.mkdtempSync(path.join(process.cwd(), 'vitek-manifest-test-'));
10
+ apiDir = path.join(rootDir, 'src', 'api');
11
+ fs.mkdirSync(apiDir, { recursive: true });
12
+ fs.writeFileSync(path.join(apiDir, 'health.get.ts'), 'export default () => ({});');
13
+ });
14
+ afterEach(() => {
15
+ try {
16
+ fs.rmSync(rootDir, { recursive: true });
17
+ }
18
+ catch {
19
+ // ignore cleanup errors
20
+ }
21
+ });
22
+ it('getManifest returns routes, middlewares, sockets', () => {
23
+ const manifest = getManifest(rootDir, 'src/api');
24
+ expect(manifest).toHaveProperty('routes');
25
+ expect(manifest).toHaveProperty('middlewares');
26
+ expect(manifest).toHaveProperty('sockets');
27
+ expect(Array.isArray(manifest.routes)).toBe(true);
28
+ expect(Array.isArray(manifest.middlewares)).toBe(true);
29
+ expect(Array.isArray(manifest.sockets)).toBe(true);
30
+ expect(manifest.routes.length).toBeGreaterThan(0);
31
+ });
32
+ it('getRoutes returns ParsedRoute[]', () => {
33
+ const routes = getRoutes(rootDir, 'src/api');
34
+ expect(Array.isArray(routes)).toBe(true);
35
+ routes.forEach((r) => {
36
+ expect(r).toHaveProperty('method');
37
+ expect(r).toHaveProperty('pattern');
38
+ expect(r).toHaveProperty('params');
39
+ expect(r).toHaveProperty('file');
40
+ });
41
+ });
42
+ it('getSockets returns ParsedSocket[]', () => {
43
+ const sockets = getSockets(rootDir, 'src/api');
44
+ expect(Array.isArray(sockets)).toBe(true);
45
+ });
46
+ it('getManifest with non-existent apiDir returns empty arrays', () => {
47
+ const manifest = getManifest(rootDir, 'non-existent-dir');
48
+ expect(manifest.routes).toEqual([]);
49
+ expect(manifest.middlewares).toEqual([]);
50
+ expect(manifest.sockets).toEqual([]);
51
+ });
52
+ it('writeManifest writes vitek-manifest.json', () => {
53
+ const outDir = path.join(rootDir, 'dist');
54
+ const written = writeManifest(rootDir, 'src/api', outDir);
55
+ expect(written).toBe(path.join(outDir, 'vitek-manifest.json'));
56
+ expect(fs.existsSync(written)).toBe(true);
57
+ const content = JSON.parse(fs.readFileSync(written, 'utf-8'));
58
+ expect(content).toHaveProperty('routes');
59
+ expect(content).toHaveProperty('middlewares');
60
+ expect(content).toHaveProperty('sockets');
61
+ });
62
+ });
@@ -9,7 +9,14 @@ import type { Middleware } from '../routing/route-types.js';
9
9
  export interface LoadedMiddleware {
10
10
  middleware: Middleware[];
11
11
  basePattern: string;
12
+ /** Optional path matcher for global middleware (basePattern ''). Only routes matching one of these patterns get this middleware. E.g. ['protected/*', 'admin'] */
13
+ pathPatterns?: string[];
12
14
  }
15
+ /**
16
+ * Returns true if routePattern matches a single path pattern.
17
+ * Pattern may be exact ('admin') or prefix with * ('admin/*'). No leading slash.
18
+ */
19
+ export declare function matchPathPattern(routePattern: string, configPattern: string): boolean;
13
20
  /**
14
21
  * Returns the middlewares applicable to a route, ordered from most generic to most specific
15
22
  */
@@ -1 +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"}
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;IACpB,kKAAkK;IAClK,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;CACzB;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,YAAY,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,GAAG,OAAO,CAQrF;AA+BD;;GAEG;AACH,wBAAgB,wBAAwB,CACtC,WAAW,EAAE,gBAAgB,EAAE,EAC/B,YAAY,EAAE,MAAM,GACnB,UAAU,EAAE,CAUd"}
@@ -2,35 +2,43 @@
2
2
  * Determines which middlewares are applicable to a route based on the pattern
3
3
  * Core logic - no dependencies
4
4
  */
5
+ /**
6
+ * Returns true if routePattern matches a single path pattern.
7
+ * Pattern may be exact ('admin') or prefix with * ('admin/*'). No leading slash.
8
+ */
9
+ export function matchPathPattern(routePattern, configPattern) {
10
+ const route = routePattern.replace(/^\/+|\/+$/g, '');
11
+ const pattern = configPattern.replace(/^\/+|\/+$/g, '');
12
+ if (pattern.endsWith('/*')) {
13
+ const prefix = pattern.slice(0, -2);
14
+ return route === prefix || route.startsWith(prefix + '/');
15
+ }
16
+ return route === pattern || route.startsWith(pattern + '/');
17
+ }
5
18
  /**
6
19
  * Checks if a middleware base pattern applies to a route pattern
7
20
  * 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"
21
+ * For global middleware (basePattern '') with pathPatterns, applies only if route matches one of them.
13
22
  */
14
- function isMiddlewareApplicable(basePattern, routePattern) {
15
- // Global middleware (empty basePattern) applies to all routes
23
+ function isMiddlewareApplicable(loaded, routePattern) {
24
+ const { basePattern, pathPatterns } = loaded;
25
+ // Global middleware (empty basePattern)
16
26
  if (basePattern === '') {
27
+ if (pathPatterns != null && pathPatterns.length > 0) {
28
+ return pathPatterns.some((p) => matchPathPattern(routePattern, p));
29
+ }
17
30
  return true;
18
31
  }
19
32
  // If route is empty (root), only global middleware applies
20
33
  if (routePattern === '') {
21
34
  return false;
22
35
  }
23
- // Normalize patterns for comparison (remove leading/trailing slashes)
24
36
  const normalizedBase = basePattern.replace(/^\/+|\/+$/g, '');
25
37
  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) {
38
+ if (normalizedRoute === normalizedBase)
29
39
  return true;
30
- }
31
- if (normalizedRoute.startsWith(normalizedBase + '/')) {
40
+ if (normalizedRoute.startsWith(normalizedBase + '/'))
32
41
  return true;
33
- }
34
42
  return false;
35
43
  }
36
44
  /**
@@ -39,7 +47,7 @@ function isMiddlewareApplicable(basePattern, routePattern) {
39
47
  export function getApplicableMiddlewares(middlewares, routePattern) {
40
48
  const applicable = [];
41
49
  for (const loadedMiddleware of middlewares) {
42
- if (isMiddlewareApplicable(loadedMiddleware.basePattern, routePattern)) {
50
+ if (isMiddlewareApplicable(loadedMiddleware, routePattern)) {
43
51
  applicable.push(...loadedMiddleware.middleware);
44
52
  }
45
53
  }
@@ -1,5 +1,5 @@
1
1
  import { describe, it, expect } from 'vitest';
2
- import { getApplicableMiddlewares } from './get-applicable-middlewares.js';
2
+ import { getApplicableMiddlewares, matchPathPattern } from './get-applicable-middlewares.js';
3
3
  function noopMiddleware() {
4
4
  return async (_ctx, next) => next();
5
5
  }
@@ -71,4 +71,39 @@ describe('getApplicableMiddlewares', () => {
71
71
  ];
72
72
  expect(getApplicableMiddlewares(middlewares, 'posts/1')).toEqual([mw1, mw2]);
73
73
  });
74
+ describe('global middleware with pathPatterns', () => {
75
+ it('applies only to routes matching pathPatterns when set', () => {
76
+ const globalMw = noopMiddleware();
77
+ const middlewares = [
78
+ { basePattern: '', middleware: [globalMw], pathPatterns: ['protected/*', 'admin'] },
79
+ ];
80
+ expect(getApplicableMiddlewares(middlewares, 'protected')).toEqual([globalMw]);
81
+ expect(getApplicableMiddlewares(middlewares, 'protected/1')).toEqual([globalMw]);
82
+ expect(getApplicableMiddlewares(middlewares, 'admin')).toEqual([globalMw]);
83
+ expect(getApplicableMiddlewares(middlewares, 'admin/users')).toEqual([globalMw]);
84
+ expect(getApplicableMiddlewares(middlewares, 'posts')).toEqual([]);
85
+ expect(getApplicableMiddlewares(middlewares, 'health')).toEqual([]);
86
+ });
87
+ it('global without pathPatterns still applies to all routes', () => {
88
+ const globalMw = noopMiddleware();
89
+ const middlewares = [
90
+ { basePattern: '', middleware: [globalMw] },
91
+ ];
92
+ expect(getApplicableMiddlewares(middlewares, 'posts')).toEqual([globalMw]);
93
+ expect(getApplicableMiddlewares(middlewares, 'any/route')).toEqual([globalMw]);
94
+ });
95
+ });
96
+ });
97
+ describe('matchPathPattern', () => {
98
+ it('matches exact pattern', () => {
99
+ expect(matchPathPattern('admin', 'admin')).toBe(true);
100
+ expect(matchPathPattern('admin/users', 'admin')).toBe(true);
101
+ expect(matchPathPattern('posts', 'admin')).toBe(false);
102
+ });
103
+ it('matches glob pattern with /*', () => {
104
+ expect(matchPathPattern('protected', 'protected/*')).toBe(true);
105
+ expect(matchPathPattern('protected/1', 'protected/*')).toBe(true);
106
+ expect(matchPathPattern('protected/1/2', 'protected/*')).toBe(true);
107
+ expect(matchPathPattern('posts', 'protected/*')).toBe(false);
108
+ });
74
109
  });
@@ -1,83 +1,7 @@
1
- /**
2
- * OpenAPI/Swagger specification generation
3
- * Core logic - no Vite dependencies
4
- */
5
- export type RouteForDocs = {
6
- pattern: string;
7
- method: string;
8
- params: string[];
9
- file: string;
10
- bodyType?: string;
11
- queryType?: string;
12
- };
13
- /**
14
- * OpenAPI Info object
15
- */
16
- export interface OpenApiInfo {
17
- title: string;
18
- version: string;
19
- description?: string;
20
- }
21
- /**
22
- * OpenAPI Server object
23
- */
24
- export interface OpenApiServer {
25
- url: string;
26
- description?: string;
27
- }
28
- /**
29
- * OpenAPI specification options
30
- */
31
- export interface OpenApiOptions {
32
- /** API information. Uses defaults if not provided. */
33
- info?: OpenApiInfo;
34
- /** Server URLs for the API. Defaults to current URL if not provided. */
35
- servers?: OpenApiServer[];
36
- /** Base path for API routes. Defaults to "/api". */
37
- apiBasePath?: string;
38
- }
39
- /**
40
- * JSDoc metadata extracted from a route file
41
- */
42
- export interface RouteMetadata {
43
- summary?: string;
44
- description?: string;
45
- tags?: string[];
46
- deprecated?: boolean;
47
- responses?: Record<string, ResponseMetadata>;
48
- bodyDescription?: string;
49
- queryDescription?: string;
50
- paramDescriptions?: Record<string, string>;
51
- }
52
- /**
53
- * Response metadata from JSDoc
54
- */
55
- export interface ResponseMetadata {
56
- description: string;
57
- type?: string;
58
- example?: unknown;
59
- }
60
- /**
61
- * Generates a complete OpenAPI 3.0 specification
62
- */
1
+ import { type RouteForDocs, type OpenApiOptions } from './types.js';
2
+ export type { RouteForDocs, OpenApiInfo, OpenApiServer, OpenApiOptions, RouteMetadata, ResponseMetadata, ApiDocsHtmlOptions } from './types.js';
63
3
  export declare function generateOpenApiSpec(routes: RouteForDocs[], options: OpenApiOptions): object;
64
- /**
65
- * Generates the OpenAPI JSON file
66
- */
67
4
  export declare function generateOpenApiFile(outputPath: string, routes: RouteForDocs[], options: OpenApiOptions): Promise<void>;
68
- /**
69
- * Generates Swagger UI HTML
70
- */
71
5
  export declare function generateSwaggerUiHtml(apiJsonPath: string, title?: string): string;
72
- /**
73
- * Options for combined API docs (REST + WebSockets)
74
- */
75
- export interface ApiDocsHtmlOptions {
76
- /** Path to AsyncAPI JSON (e.g. /asyncapi.json). When set, adds WebSockets tab. */
77
- asyncApiJsonPath?: string;
78
- }
79
- /**
80
- * Generates combined API documentation HTML with optional REST (Swagger) and WebSockets (AsyncAPI) tabs
81
- */
82
- export declare function generateApiDocsHtml(openApiJsonPath: string, title: string, options?: ApiDocsHtmlOptions): string;
6
+ export declare function generateApiDocsHtml(openApiJsonPath: string, title: string, options?: import('./types.js').ApiDocsHtmlOptions): string;
83
7
  //# sourceMappingURL=generate.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"generate.d.ts","sourceRoot":"","sources":["../../../src/core/openapi/generate.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAKH,MAAM,MAAM,YAAY,GAAG;IACzB,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,CAAC;AAEF;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,sDAAsD;IACtD,IAAI,CAAC,EAAE,WAAW,CAAC;IACnB,wEAAwE;IACxE,OAAO,CAAC,EAAE,aAAa,EAAE,CAAC;IAC1B,oDAAoD;IACpD,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;IAC7C,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,iBAAiB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC5C;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AASD;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,YAAY,EAAE,EAAE,OAAO,EAAE,cAAc,GAAG,MAAM,CAiB3F;AAgdD;;GAEG;AACH,wBAAsB,mBAAmB,CACvC,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,YAAY,EAAE,EACtB,OAAO,EAAE,cAAc,GACtB,OAAO,CAAC,IAAI,CAAC,CAUf;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,WAAW,EAAE,MAAM,EAAE,KAAK,GAAE,MAA4B,GAAG,MAAM,CAwBtG;AAED;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,kFAAkF;IAClF,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CACjC,eAAe,EAAE,MAAM,EACvB,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,kBAAuB,GAC/B,MAAM,CAuER"}
1
+ {"version":3,"file":"generate.d.ts","sourceRoot":"","sources":["../../../src/core/openapi/generate.ts"],"names":[],"mappings":"AAEA,OAAO,EAAwB,KAAK,YAAY,EAAE,KAAK,cAAc,EAAE,MAAM,YAAY,CAAC;AAG1F,YAAY,EAAE,YAAY,EAAE,WAAW,EAAE,aAAa,EAAE,cAAc,EAAE,aAAa,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAEhJ,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,YAAY,EAAE,EAAE,OAAO,EAAE,cAAc,GAAG,MAAM,CAiB3F;AAED,wBAAsB,mBAAmB,CACvC,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,YAAY,EAAE,EACtB,OAAO,EAAE,cAAc,GACtB,OAAO,CAAC,IAAI,CAAC,CASf;AAED,wBAAgB,qBAAqB,CAAC,WAAW,EAAE,MAAM,EAAE,KAAK,GAAE,MAA4B,GAAG,MAAM,CAwBtG;AAED,wBAAgB,mBAAmB,CACjC,eAAe,EAAE,MAAM,EACvB,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,OAAO,YAAY,EAAE,kBAAuB,GACpD,MAAM,CAuER"}