te.js 2.1.6 → 2.2.1

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 (55) hide show
  1. package/README.md +1 -12
  2. package/auto-docs/analysis/handler-analyzer.test.js +106 -0
  3. package/auto-docs/analysis/source-resolver.test.js +58 -0
  4. package/auto-docs/constants.js +13 -2
  5. package/auto-docs/openapi/generator.js +7 -5
  6. package/auto-docs/openapi/generator.test.js +132 -0
  7. package/auto-docs/openapi/spec-builders.js +39 -19
  8. package/cli/docs-command.js +44 -36
  9. package/cors/index.test.js +82 -0
  10. package/docs/README.md +1 -2
  11. package/docs/api-reference.md +124 -186
  12. package/docs/configuration.md +0 -13
  13. package/docs/getting-started.md +19 -21
  14. package/docs/rate-limiting.md +59 -58
  15. package/lib/llm/client.js +7 -2
  16. package/lib/llm/index.js +14 -1
  17. package/lib/llm/parse.test.js +60 -0
  18. package/package.json +3 -1
  19. package/radar/index.js +382 -0
  20. package/rate-limit/base.js +12 -15
  21. package/rate-limit/index.js +19 -22
  22. package/rate-limit/index.test.js +93 -0
  23. package/rate-limit/storage/memory.js +13 -13
  24. package/rate-limit/storage/redis-install.js +70 -0
  25. package/rate-limit/storage/redis.js +94 -52
  26. package/server/ammo/body-parser.js +156 -152
  27. package/server/ammo/body-parser.test.js +79 -0
  28. package/server/ammo/enhancer.js +8 -4
  29. package/server/ammo.js +138 -12
  30. package/server/context/request-context.js +51 -0
  31. package/server/context/request-context.test.js +53 -0
  32. package/server/endpoint.js +15 -0
  33. package/server/error.js +56 -3
  34. package/server/error.test.js +45 -0
  35. package/server/errors/channels/channels.test.js +148 -0
  36. package/server/errors/channels/index.js +1 -1
  37. package/server/errors/llm-cache.js +1 -1
  38. package/server/errors/llm-cache.test.js +160 -0
  39. package/server/errors/llm-error-service.js +1 -1
  40. package/server/errors/llm-rate-limiter.test.js +105 -0
  41. package/server/files/uploader.js +38 -26
  42. package/server/handler.js +1 -1
  43. package/server/targets/registry.js +3 -3
  44. package/server/targets/registry.test.js +108 -0
  45. package/te.js +233 -183
  46. package/utils/auto-register.js +1 -1
  47. package/utils/configuration.js +23 -9
  48. package/utils/configuration.test.js +58 -0
  49. package/utils/errors-llm-config.js +74 -8
  50. package/utils/request-logger.js +49 -3
  51. package/utils/startup.js +80 -0
  52. package/database/index.js +0 -165
  53. package/database/mongodb.js +0 -146
  54. package/database/redis.js +0 -201
  55. package/docs/database.md +0 -390
package/README.md CHANGED
@@ -41,7 +41,6 @@ api.register('/hello/:name', (ammo) => {
41
41
  app.takeoff();
42
42
  ```
43
43
 
44
-
45
44
  ## Features
46
45
 
47
46
  - **AI-Native (MCP)** — Ship with an MCP server so AI assistants can scaffold projects, generate routes, and write correct code with full framework knowledge
@@ -50,14 +49,12 @@ app.takeoff();
50
49
  - **Zero-Config Error Handling** — No try-catch needed! Tejas catches all errors automatically. Opt in to have an LLM determine error code and message when you don't specify them (see [Error Handling](./docs/error-handling.md))
51
50
  - **Built-in Rate Limiting** — Three algorithms (Token Bucket, Sliding Window, Fixed Window) with memory or Redis storage
52
51
  - **Method Safety & CORS** — Opt-in method restriction per route (`register(path, { methods }, handler)` or `ammo.only('GET')`), global allowed-methods filter, and `app.withCORS()` for cross-origin requests
53
- - **Database Ready** — First-class Redis and MongoDB support with auto-install of drivers
54
52
  - **File Uploads** — Easy file handling with size limits and type validation
55
53
  - **Auto-Documentation** — Generate OpenAPI specs from your code with LLM-powered analysis (`tejas generate:docs`)
56
54
  - **Interactive API Docs** — Serve a Scalar API reference UI with `app.serveDocs()`
57
55
  - **Auto-Discovery** — Automatic route registration from `.target.js` files
58
56
  - **Request Logging** — Built-in HTTP request and exception logging
59
57
 
60
-
61
58
  ## AI-Assisted Setup (MCP)
62
59
 
63
60
  > **Recommended** — The best way to get started with Tejas in the age of AI.
@@ -79,8 +76,7 @@ The [Tejas MCP server](https://www.npmjs.com/package/tejas-mcp) gives your IDE's
79
76
 
80
77
  **Other MCP-compatible IDEs** — run `npx tejas-mcp` as the server command (stdio transport, no config needed).
81
78
 
82
- Once connected, prompt your AI with things like *"Scaffold a new te.js project called my-api"* or *"Create a REST API with user CRUD routes"* — the assistant will generate framework-correct code using real te.js patterns.
83
-
79
+ Once connected, prompt your AI with things like _"Scaffold a new te.js project called my-api"_ or _"Create a REST API with user CRUD routes"_ — the assistant will generate framework-correct code using real te.js patterns.
84
80
 
85
81
  ## Quick Start
86
82
 
@@ -132,7 +128,6 @@ node index.js
132
128
  # Server running at http://localhost:3000
133
129
  ```
134
130
 
135
-
136
131
  ## Core Concepts
137
132
 
138
133
  | Tejas Term | Purpose | Express Equivalent |
@@ -144,7 +139,6 @@ node index.js
144
139
  | `midair()` | Register middleware | `use()` |
145
140
  | `takeoff()` | Start server | `listen()` |
146
141
 
147
-
148
142
  ## CLI
149
143
 
150
144
  ```bash
@@ -153,7 +147,6 @@ tejas generate:docs [--ci] # Generate OpenAPI docs (interactive or CI mode)
153
147
  tejas docs:on-push # Auto-generate docs when pushing to production branch
154
148
  ```
155
149
 
156
-
157
150
  ## API Documentation
158
151
 
159
152
  Generate and serve interactive API docs:
@@ -168,7 +161,6 @@ app.takeoff();
168
161
  // Visit http://localhost:1403/docs
169
162
  ```
170
163
 
171
-
172
164
  ## Documentation
173
165
 
174
166
  For comprehensive documentation, see the [docs folder](./docs) or visit [tejas-documentation.vercel.app](https://tejas-documentation.vercel.app).
@@ -179,19 +171,16 @@ For comprehensive documentation, see the [docs folder](./docs) or visit [tejas-d
179
171
  - [Ammo](./docs/ammo.md) — Request/response handling
180
172
  - [Middleware](./docs/middleware.md) — Global, target, and route middleware
181
173
  - [Error Handling](./docs/error-handling.md) — Zero-config error handling
182
- - [Database](./docs/database.md) — Redis and MongoDB integration
183
174
  - [Rate Limiting](./docs/rate-limiting.md) — API protection
184
175
  - [File Uploads](./docs/file-uploads.md) — File handling
185
176
  - [CLI Reference](./docs/cli.md) — Command-line interface
186
177
  - [Auto-Documentation](./docs/auto-docs.md) — OpenAPI generation
187
178
  - [API Reference](./docs/api-reference.md) — Complete API docs
188
179
 
189
-
190
180
  ## Contributing
191
181
 
192
182
  Contributions are welcome! Please feel free to submit a Pull Request.
193
183
 
194
-
195
184
  ## License
196
185
 
197
186
  ISC © [Hirak Chhatbar](https://github.com/hirakchhatbar)
@@ -0,0 +1,106 @@
1
+ /**
2
+ * Unit tests for auto-docs handler-analyzer (detectMethods, analyzeHandler).
3
+ */
4
+ import { describe, it, expect } from 'vitest';
5
+ import {
6
+ detectMethods,
7
+ analyzeHandler,
8
+ ALL_METHODS,
9
+ } from './handler-analyzer.js';
10
+
11
+ describe('handler-analyzer', () => {
12
+ describe('detectMethods', () => {
13
+ it('returns all methods when handler is not a function', () => {
14
+ expect(detectMethods(null)).toEqual(ALL_METHODS);
15
+ expect(detectMethods(undefined)).toEqual(ALL_METHODS);
16
+ });
17
+
18
+ it('detects GET when handler uses ammo.GET', () => {
19
+ const handler = (ammo) => {
20
+ if (ammo.GET) ammo.fire(200, {});
21
+ };
22
+ expect(detectMethods(handler)).toContain('GET');
23
+ });
24
+
25
+ it('detects POST and GET when handler checks both', () => {
26
+ const handler = (ammo) => {
27
+ if (ammo.GET) ammo.fire(200, {});
28
+ if (ammo.POST) ammo.fire(201, {});
29
+ };
30
+ const detected = detectMethods(handler);
31
+ expect(detected).toContain('GET');
32
+ expect(detected).toContain('POST');
33
+ });
34
+
35
+ it('returns all methods when no method checks found (method-agnostic)', () => {
36
+ const handler = () => {};
37
+ expect(detectMethods(handler)).toEqual(ALL_METHODS);
38
+ });
39
+
40
+ it('detects GET and HEAD when handler uses ammo.only("GET")', () => {
41
+ const handler = (ammo) => {
42
+ ammo.only('GET');
43
+ ammo.fire({ status: 'ok' });
44
+ };
45
+ const detected = detectMethods(handler);
46
+ expect(detected).toContain('GET');
47
+ expect(detected).toContain('HEAD');
48
+ expect(detected).toHaveLength(2);
49
+ });
50
+
51
+ it('detects POST and PUT when handler uses ammo.only("POST", "PUT")', () => {
52
+ const handler = (ammo) => {
53
+ ammo.only('POST', 'PUT');
54
+ ammo.fire(200, {});
55
+ };
56
+ const detected = detectMethods(handler);
57
+ expect(detected).toContain('POST');
58
+ expect(detected).toContain('PUT');
59
+ expect(detected).toHaveLength(2);
60
+ });
61
+
62
+ it('detects from ammo.only with double-quoted methods', () => {
63
+ const handler = (ammo) => {
64
+ ammo.only('GET');
65
+ ammo.fire(200);
66
+ };
67
+ const detected = detectMethods(handler);
68
+ expect(detected).toContain('GET');
69
+ expect(detected).toContain('HEAD');
70
+ });
71
+
72
+ it('detects from .only with no space after comma', () => {
73
+ const handler = (ammo) => {
74
+ ammo.only('GET', 'POST');
75
+ ammo.fire(200);
76
+ };
77
+ const detected = detectMethods(handler);
78
+ expect(detected).toContain('GET');
79
+ expect(detected).toContain('HEAD');
80
+ expect(detected).toContain('POST');
81
+ expect(detected).toHaveLength(3);
82
+ });
83
+
84
+ it('prefers ammo.only over property access when both present', () => {
85
+ const handler = (ammo) => {
86
+ ammo.only('POST');
87
+ if (ammo.GET) ammo.fire(200);
88
+ ammo.fire(201, {});
89
+ };
90
+ const detected = detectMethods(handler);
91
+ expect(detected).toEqual(['POST']);
92
+ });
93
+ });
94
+
95
+ describe('analyzeHandler', () => {
96
+ it('returns object with methods array', () => {
97
+ const handler = (ammo) => {
98
+ if (ammo.GET) ammo.fire(200);
99
+ };
100
+ const result = analyzeHandler(handler);
101
+ expect(result).toHaveProperty('methods');
102
+ expect(Array.isArray(result.methods)).toBe(true);
103
+ expect(result.methods).toContain('GET');
104
+ });
105
+ });
106
+ });
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Unit tests for auto-docs source-resolver (extractRelativeImports, resolveTargetFilePath, formatDependencyContext).
3
+ */
4
+ import { describe, it, expect } from 'vitest';
5
+ import path from 'node:path';
6
+ import {
7
+ extractRelativeImports,
8
+ resolveTargetFilePath,
9
+ formatDependencyContext,
10
+ } from './source-resolver.js';
11
+
12
+ describe('source-resolver', () => {
13
+ describe('extractRelativeImports', () => {
14
+ it('extracts relative import paths', () => {
15
+ const source = `import x from './foo.js';\nimport { y } from '../bar.js';`;
16
+ const imports = extractRelativeImports(source);
17
+ expect(imports).toContain('./foo.js');
18
+ expect(imports).toContain('../bar.js');
19
+ });
20
+ it('ignores absolute or node imports', () => {
21
+ const source = `import path from 'path';\nimport x from './local.js';`;
22
+ const imports = extractRelativeImports(source);
23
+ expect(imports).toEqual(['./local.js']);
24
+ });
25
+ it('returns unique paths', () => {
26
+ const source = `import a from './foo.js';\nimport b from './foo.js';`;
27
+ expect(extractRelativeImports(source)).toHaveLength(1);
28
+ });
29
+ });
30
+
31
+ describe('resolveTargetFilePath', () => {
32
+ it('joins dirTargets with groupId and .target.js', () => {
33
+ const resolved = resolveTargetFilePath('users', 'targets');
34
+ expect(resolved).toMatch(/targets[\\/]users\.target\.js$/);
35
+ });
36
+ it('converts groupId slashes to path sep', () => {
37
+ const resolved = resolveTargetFilePath('subdir/users', 'targets');
38
+ expect(resolved).toMatch(/subdir[\\/]users\.target\.js$/);
39
+ });
40
+ });
41
+
42
+ describe('formatDependencyContext', () => {
43
+ it('returns empty string for empty sources', () => {
44
+ expect(formatDependencyContext(new Map(), null)).toBe('');
45
+ });
46
+ it('formats map of path -> source with labels', () => {
47
+ const sources = new Map([
48
+ ['/cwd/targets/users.target.js', 'const x = 1;'],
49
+ ['/cwd/services/user.js', 'const y = 2;'],
50
+ ]);
51
+ const targetPath = '/cwd/targets/users.target.js';
52
+ const out = formatDependencyContext(sources, targetPath, 1000);
53
+ expect(out).toContain('Target');
54
+ expect(out).toContain('const x = 1;');
55
+ expect(out).toContain('const y = 2;');
56
+ });
57
+ });
58
+ });
@@ -7,13 +7,24 @@
7
7
  export const OPENAPI_VERSION = '3.0.3';
8
8
 
9
9
  /** Lowercase HTTP method names (for method-keyed LLM response detection and OpenAPI operation keys). */
10
- export const METHOD_KEYS = new Set(['get', 'put', 'post', 'delete', 'patch', 'head', 'options']);
10
+ export const METHOD_KEYS = new Set([
11
+ 'get',
12
+ 'put',
13
+ 'post',
14
+ 'delete',
15
+ 'patch',
16
+ 'head',
17
+ 'options',
18
+ ]);
11
19
 
12
20
  /** When an endpoint accepts all HTTP methods, document it once under this key. */
13
21
  export const METHOD_AGNOSTIC_OPERATION_KEY = 'get';
14
22
 
15
23
  /** Max handler source length by level (chars; tokens roughly scale). Level 1 = moderate, 2 = high. */
16
- export const HANDLER_SOURCE_MAX_LENGTH_BY_LEVEL = { 1: 2800, 2: 6000 };
24
+ export const HANDLER_SOURCE_MAX_LENGTH_BY_LEVEL = Object.freeze({
25
+ 1: 2800,
26
+ 2: 6000,
27
+ });
17
28
 
18
29
  /** Default max chars for dependency context in formatDependencyContext and level-2 prompts. */
19
30
  export const DEPENDENCY_CONTEXT_MAX_CHARS = 6000;
@@ -39,7 +39,7 @@ async function generateOpenAPISpec(registry, options = {}) {
39
39
  logger = null,
40
40
  } = options;
41
41
  const targets = registry?.targets ?? [];
42
- const paths = {};
42
+ const paths = Object.create(null);
43
43
  const groupEndpoints = new Map();
44
44
  const dependencyContextByGroup = new Map();
45
45
 
@@ -71,10 +71,12 @@ async function generateOpenAPISpec(registry, options = {}) {
71
71
  );
72
72
  applyTagDisplayNames(paths, tagDescriptions);
73
73
 
74
- const tags = Array.from(tagDescriptions.entries()).map(([, { name, description }]) => ({
75
- name,
76
- ...(description && { description }),
77
- }));
74
+ const tags = Array.from(tagDescriptions.entries()).map(
75
+ ([, { name, description }]) => ({
76
+ name,
77
+ ...(description && { description }),
78
+ }),
79
+ );
78
80
 
79
81
  const spec = {
80
82
  openapi: OPENAPI_VERSION,
@@ -0,0 +1,132 @@
1
+ /**
2
+ * Unit tests for auto-docs openapi/generator pure functions.
3
+ */
4
+ import { describe, it, expect } from 'vitest';
5
+ import {
6
+ toOpenAPIPath,
7
+ getPathParameters,
8
+ getQueryParameters,
9
+ buildSchemaFromMetadata,
10
+ buildResponses,
11
+ buildOperation,
12
+ mergeMetadata,
13
+ } from './generator.js';
14
+
15
+ describe('openapi-generator', () => {
16
+ describe('toOpenAPIPath', () => {
17
+ it('converts :param to {param}', () => {
18
+ expect(toOpenAPIPath('/users/:id')).toBe('/users/{id}');
19
+ });
20
+ it('converts multiple params', () => {
21
+ expect(toOpenAPIPath('/users/:userId/posts/:postId')).toBe(
22
+ '/users/{userId}/posts/{postId}',
23
+ );
24
+ });
25
+ it('returns / for empty or non-string', () => {
26
+ expect(toOpenAPIPath('')).toBe('/');
27
+ expect(toOpenAPIPath(null)).toBe('/');
28
+ });
29
+ });
30
+
31
+ describe('getPathParameters', () => {
32
+ it('extracts path params from te.js path', () => {
33
+ const params = getPathParameters('/users/:id');
34
+ expect(params).toHaveLength(1);
35
+ expect(params[0]).toMatchObject({
36
+ name: 'id',
37
+ in: 'path',
38
+ required: true,
39
+ schema: { type: 'string' },
40
+ });
41
+ });
42
+ it('returns empty array for path without params', () => {
43
+ expect(getPathParameters('/users')).toEqual([]);
44
+ });
45
+ });
46
+
47
+ describe('getQueryParameters', () => {
48
+ it('builds query params from metadata', () => {
49
+ const queryMeta = {
50
+ limit: { type: 'integer', required: false },
51
+ q: { type: 'string', required: true },
52
+ };
53
+ const params = getQueryParameters(queryMeta);
54
+ expect(params).toHaveLength(2);
55
+ expect(params.find((p) => p.name === 'limit')).toMatchObject({
56
+ in: 'query',
57
+ required: false,
58
+ });
59
+ expect(params.find((p) => p.name === 'q')).toMatchObject({
60
+ in: 'query',
61
+ required: true,
62
+ });
63
+ });
64
+ it('returns empty array for invalid meta', () => {
65
+ expect(getQueryParameters(null)).toEqual([]);
66
+ });
67
+ });
68
+
69
+ describe('buildSchemaFromMetadata', () => {
70
+ it('builds OpenAPI schema from field meta', () => {
71
+ const meta = {
72
+ name: { type: 'string' },
73
+ email: { type: 'string', format: 'email', required: true },
74
+ };
75
+ const schema = buildSchemaFromMetadata(meta);
76
+ expect(schema.type).toBe('object');
77
+ expect(schema.properties.name.type).toBe('string');
78
+ expect(schema.properties.email.format).toBe('email');
79
+ expect(schema.required).toContain('email');
80
+ });
81
+ });
82
+
83
+ describe('buildResponses', () => {
84
+ it('returns 200 Success when responseMeta empty', () => {
85
+ const r = buildResponses(null);
86
+ expect(r['200']).toEqual({ description: 'Success' });
87
+ });
88
+ it('builds responses from metadata', () => {
89
+ const meta = {
90
+ 200: { description: 'OK' },
91
+ 201: { description: 'Created' },
92
+ };
93
+ const r = buildResponses(meta);
94
+ expect(r['200'].description).toBe('OK');
95
+ expect(r['201'].description).toBe('Created');
96
+ });
97
+ });
98
+
99
+ describe('buildOperation', () => {
100
+ it('builds operation with summary and responses', () => {
101
+ const meta = {
102
+ summary: 'Get user',
103
+ response: { 200: { description: 'OK' } },
104
+ };
105
+ const pathParams = [];
106
+ const op = buildOperation('get', meta, pathParams);
107
+ expect(op.summary).toBe('Get user');
108
+ expect(op.responses['200'].description).toBe('OK');
109
+ });
110
+ });
111
+
112
+ describe('mergeMetadata', () => {
113
+ it('prefers explicit when preferEnhanced false', () => {
114
+ const explicit = { summary: 'A', description: 'B' };
115
+ const enhanced = { summary: 'C', description: 'D' };
116
+ const merged = mergeMetadata(explicit, enhanced, {
117
+ preferEnhanced: false,
118
+ });
119
+ expect(merged.summary).toBe('A');
120
+ expect(merged.description).toBe('B');
121
+ });
122
+ it('prefers enhanced when preferEnhanced true', () => {
123
+ const explicit = { summary: 'A' };
124
+ const enhanced = { summary: 'C', description: 'D' };
125
+ const merged = mergeMetadata(explicit, enhanced, {
126
+ preferEnhanced: true,
127
+ });
128
+ expect(merged.summary).toBe('C');
129
+ expect(merged.description).toBe('D');
130
+ });
131
+ });
132
+ });
@@ -16,7 +16,12 @@ export function isMethodKeyed(obj) {
16
16
  for (const key of Object.keys(obj)) {
17
17
  if (METHOD_KEYS.has(key.toLowerCase())) {
18
18
  const val = obj[key];
19
- if (val && typeof val === 'object' && (val.summary != null || val.response != null)) return true;
19
+ if (
20
+ val &&
21
+ typeof val === 'object' &&
22
+ (val.summary != null || val.response != null)
23
+ )
24
+ return true;
20
25
  }
21
26
  }
22
27
  return false;
@@ -82,7 +87,7 @@ export function getQueryParameters(queryMeta) {
82
87
  */
83
88
  export function buildSchemaFromMetadata(meta) {
84
89
  if (!meta || typeof meta !== 'object') return {};
85
- const properties = {};
90
+ const properties = Object.create(null);
86
91
  const required = [];
87
92
  for (const [key, value] of Object.entries(meta)) {
88
93
  if (value && typeof value === 'object' && value.type) {
@@ -91,7 +96,8 @@ export function buildSchemaFromMetadata(meta) {
91
96
  ...(value.description && { description: value.description }),
92
97
  ...(value.format && { format: value.format }),
93
98
  };
94
- if (value.required === true || value.required === 'true') required.push(key);
99
+ if (value.required === true || value.required === 'true')
100
+ required.push(key);
95
101
  }
96
102
  }
97
103
  if (Object.keys(properties).length === 0) return {};
@@ -108,7 +114,8 @@ export function buildSchemaFromMetadata(meta) {
108
114
  * @returns {object|undefined} OpenAPI requestBody or undefined
109
115
  */
110
116
  export function buildRequestBody(requestMeta) {
111
- if (!requestMeta?.body || typeof requestMeta.body !== 'object') return undefined;
117
+ if (!requestMeta?.body || typeof requestMeta.body !== 'object')
118
+ return undefined;
112
119
  const schema = buildSchemaFromMetadata(requestMeta.body);
113
120
  if (!schema || Object.keys(schema).length === 0) return undefined;
114
121
  return {
@@ -127,7 +134,7 @@ export function buildRequestBody(requestMeta) {
127
134
  * @returns {object} OpenAPI responses
128
135
  */
129
136
  export function buildResponses(responseMeta) {
130
- const responses = {};
137
+ const responses = Object.create(null);
131
138
  if (!responseMeta || typeof responseMeta !== 'object') {
132
139
  responses['200'] = { description: 'Success' };
133
140
  return responses;
@@ -139,9 +146,10 @@ export function buildResponses(responseMeta) {
139
146
  ...(spec.schema && {
140
147
  content: {
141
148
  'application/json': {
142
- schema: typeof spec.schema === 'object' && spec.schema.type
143
- ? spec.schema
144
- : { type: 'object' },
149
+ schema:
150
+ typeof spec.schema === 'object' && spec.schema.type
151
+ ? spec.schema
152
+ : { type: 'object' },
145
153
  },
146
154
  },
147
155
  }),
@@ -159,7 +167,8 @@ export function buildResponses(responseMeta) {
159
167
  * @returns {boolean}
160
168
  */
161
169
  export function isMethodAgnostic(methods) {
162
- if (!Array.isArray(methods) || methods.length !== ALL_METHODS.length) return false;
170
+ if (!Array.isArray(methods) || methods.length !== ALL_METHODS.length)
171
+ return false;
163
172
  const set = new Set(methods.map((m) => m.toUpperCase()));
164
173
  return ALL_METHODS.every((m) => set.has(m));
165
174
  }
@@ -190,7 +199,10 @@ export function buildOperation(method, meta, pathParams, options = {}) {
190
199
  };
191
200
  const methodUpper = method.toUpperCase();
192
201
  const body = buildRequestBody(meta.request);
193
- if (body && (methodAgnostic || (methodUpper !== 'GET' && methodUpper !== 'HEAD'))) {
202
+ if (
203
+ body &&
204
+ (methodAgnostic || (methodUpper !== 'GET' && methodUpper !== 'HEAD'))
205
+ ) {
194
206
  op.requestBody = body;
195
207
  }
196
208
  op.responses = buildResponses(meta.response);
@@ -207,17 +219,17 @@ export function buildOperation(method, meta, pathParams, options = {}) {
207
219
  export function mergeMetadata(explicit, enhanced, options = {}) {
208
220
  const preferEnhanced = options.preferEnhanced === true;
209
221
  const summary = preferEnhanced
210
- ? (enhanced?.summary ?? explicit?.summary ?? '')
211
- : (explicit?.summary ?? enhanced?.summary ?? '');
222
+ ? enhanced?.summary ?? explicit?.summary ?? ''
223
+ : explicit?.summary ?? enhanced?.summary ?? '';
212
224
  const description = preferEnhanced
213
- ? (enhanced?.description ?? explicit?.description ?? '')
214
- : (explicit?.description ?? enhanced?.description ?? '');
225
+ ? enhanced?.description ?? explicit?.description ?? ''
226
+ : explicit?.description ?? enhanced?.description ?? '';
215
227
  const request = preferEnhanced
216
- ? (enhanced?.request ?? explicit?.request)
217
- : (explicit?.request ?? enhanced?.request);
228
+ ? enhanced?.request ?? explicit?.request
229
+ : explicit?.request ?? enhanced?.request;
218
230
  const response = preferEnhanced
219
- ? (enhanced?.response ?? explicit?.response)
220
- : (explicit?.response ?? enhanced?.response);
231
+ ? enhanced?.response ?? explicit?.response
232
+ : explicit?.response ?? enhanced?.response;
221
233
  return {
222
234
  summary: summary || 'Endpoint',
223
235
  description: description || undefined,
@@ -234,7 +246,15 @@ export function mergeMetadata(explicit, enhanced, options = {}) {
234
246
  * @returns {object}
235
247
  */
236
248
  export function mergeMethodAgnosticMeta(metaByMethod, methods, fallbackMeta) {
237
- const preferredOrder = ['post', 'put', 'patch', 'get', 'delete', 'head', 'options'];
249
+ const preferredOrder = [
250
+ 'post',
251
+ 'put',
252
+ 'patch',
253
+ 'get',
254
+ 'delete',
255
+ 'head',
256
+ 'options',
257
+ ];
238
258
  for (const k of preferredOrder) {
239
259
  const m = metaByMethod.get(k);
240
260
  if (m && (m.summary || m.description)) return m;