expediate 1.0.4 → 1.0.6

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 (69) hide show
  1. package/CHANGELOG.md +138 -0
  2. package/CONTRIBUTING.md +150 -0
  3. package/LICENSE +16 -16
  4. package/README.md +330 -444
  5. package/dist/apis.d.ts +504 -27
  6. package/dist/apis.d.ts.map +1 -1
  7. package/dist/apis.js +618 -107
  8. package/dist/apis.js.map +1 -1
  9. package/dist/cjs/index.js +4066 -0
  10. package/dist/cjs/package.json +1 -0
  11. package/dist/git.d.ts +72 -9
  12. package/dist/git.d.ts.map +1 -1
  13. package/dist/git.js +129 -74
  14. package/dist/git.js.map +1 -1
  15. package/dist/http-objects.d.ts +26 -0
  16. package/dist/http-objects.d.ts.map +1 -0
  17. package/dist/http-objects.js +588 -0
  18. package/dist/http-objects.js.map +1 -0
  19. package/dist/index.d.ts +18 -13
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/index.js +15 -24
  22. package/dist/index.js.map +1 -1
  23. package/dist/jwt-auth.d.ts +158 -57
  24. package/dist/jwt-auth.d.ts.map +1 -1
  25. package/dist/jwt-auth.js +447 -207
  26. package/dist/jwt-auth.js.map +1 -1
  27. package/dist/middleware.d.ts +476 -0
  28. package/dist/middleware.d.ts.map +1 -0
  29. package/dist/middleware.js +647 -0
  30. package/dist/middleware.js.map +1 -0
  31. package/dist/mimetypes.json +882 -1
  32. package/dist/misc.d.ts +268 -25
  33. package/dist/misc.d.ts.map +1 -1
  34. package/dist/misc.js +449 -168
  35. package/dist/misc.js.map +1 -1
  36. package/dist/openapi.d.ts +433 -0
  37. package/dist/openapi.d.ts.map +1 -0
  38. package/dist/openapi.js +624 -0
  39. package/dist/openapi.js.map +1 -0
  40. package/dist/router-types.d.ts +760 -0
  41. package/dist/router-types.d.ts.map +1 -0
  42. package/dist/router-types.js +23 -0
  43. package/dist/router-types.js.map +1 -0
  44. package/dist/router.d.ts +37 -201
  45. package/dist/router.d.ts.map +1 -1
  46. package/dist/router.js +502 -244
  47. package/dist/router.js.map +1 -1
  48. package/dist/static.d.ts +3 -3
  49. package/dist/static.d.ts.map +1 -1
  50. package/dist/static.js +164 -105
  51. package/dist/static.js.map +1 -1
  52. package/docs/THREAT_MODEL.md +52 -0
  53. package/docs/api-builder-v2-design.md +644 -0
  54. package/docs/api-builder-v3-design.md +397 -0
  55. package/docs/api-builder.md +454 -0
  56. package/docs/benchmark.md +27 -0
  57. package/docs/body-parsing.md +223 -0
  58. package/docs/errors.md +359 -0
  59. package/docs/expediate.png +0 -0
  60. package/docs/git.md +139 -0
  61. package/docs/jwt-auth.md +251 -0
  62. package/docs/logo.svg +12 -0
  63. package/docs/middleware.md +264 -0
  64. package/docs/openapi.md +180 -0
  65. package/docs/router.md +356 -0
  66. package/docs/static.md +128 -0
  67. package/docs/wiki.json +123 -0
  68. package/package.json +47 -8
  69. package/.npmignore +0 -16
@@ -0,0 +1,624 @@
1
+ /* Copyright 2021 Fabien Bavent
2
+ *
3
+ * Permission is hereby granted, free of charge, to any person obtaining a
4
+ * copy of this software and associated documentation files (the "Software"),
5
+ * to deal in the Software without restriction, including without limitation
6
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
7
+ * and/or sell copies of the Software, and to permit persons to whom the
8
+ * Software is furnished to do so, subject to the following conditions:
9
+ *
10
+ * The above copyright notice and this permission notice shall be included
11
+ * in all copies or substantial portions of the Software.
12
+ *
13
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
14
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
16
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
18
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
19
+ * DEALINGS IN THE SOFTWARE.
20
+ */
21
+ 'use strict';
22
+ import { joinPath, routeScore, normalizePermission } from './apis.js';
23
+ // ── YAML serialiser (zero-dependency, block-style) ────────────────────────────
24
+ /**
25
+ * YAML reserved keywords that must be quoted as scalars so that YAML parsers
26
+ * do not interpret them as the corresponding typed values.
27
+ */
28
+ const YAML_KW = new Set([
29
+ 'true', 'false', 'yes', 'no', 'on', 'off', 'null', '~',
30
+ ]);
31
+ /** Pattern matching integer and floating-point number strings. */
32
+ const LOOKS_LIKE_NUMBER = /^[-+]?(\d+\.?\d*|\.\d+)([eE][+-]?\d+)?$|^0x[0-9a-fA-F]+$|^0o[0-7]+$/;
33
+ /**
34
+ * Serialise a string as a YAML scalar value or key.
35
+ *
36
+ * The string is returned unquoted when it is a valid YAML plain scalar —
37
+ * i.e. it cannot be misinterpreted as another type and does not contain
38
+ * characters that would confuse a YAML parser.
39
+ *
40
+ * Otherwise the value is wrapped in double quotes with control characters,
41
+ * backslashes, and `"` escaped so the output is always valid YAML 1.2.
42
+ *
43
+ * @param s - The string to serialise.
44
+ */
45
+ function yamlString(s) {
46
+ if (s === '')
47
+ return '""';
48
+ const needsQuote =
49
+ // Would be misinterpreted as a YAML typed value.
50
+ YAML_KW.has(s.toLowerCase()) ||
51
+ LOOKS_LIKE_NUMBER.test(s) ||
52
+ // Starts with a YAML indicator that has special meaning at the start of a
53
+ // plain scalar (block context).
54
+ /^[-?:,[\]{}#&*!|>'"%@`~]/.test(s) ||
55
+ // Inline sequences that break block-mapping parsing.
56
+ s.includes(': ') ||
57
+ s.endsWith(':') ||
58
+ s.includes(' #') ||
59
+ // Leading / trailing whitespace.
60
+ s !== s.trim() ||
61
+ // Flow indicator characters anywhere — present in path patterns such as
62
+ // `/items/{id}` and must be quoted to avoid flow-collection ambiguity.
63
+ /[{}[\]]/.test(s) ||
64
+ // Control characters.
65
+ /[\x00-\x1f\x7f]/.test(s);
66
+ if (!needsQuote)
67
+ return s;
68
+ // Double-quoted style: always valid, handles all edge cases.
69
+ return '"' +
70
+ s
71
+ .replace(/\\/g, '\\\\')
72
+ .replace(/"/g, '\\"')
73
+ .replace(/\n/g, '\\n')
74
+ .replace(/\r/g, '\\r')
75
+ .replace(/\t/g, '\\t')
76
+ .replace(/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g, c => `\\u${c.charCodeAt(0).toString(16).padStart(4, '0')}`)
77
+ + '"';
78
+ }
79
+ /**
80
+ * Serialise a mapping key as a YAML scalar.
81
+ *
82
+ * Uses the same quoting rules as {@link yamlString}. Numbers as keys
83
+ * (e.g. HTTP status codes `200`, `500`) are always quoted so that YAML
84
+ * parsers do not interpret them as integer keys.
85
+ */
86
+ function yamlKey(key) {
87
+ return yamlString(key);
88
+ }
89
+ /**
90
+ * Recursively serialise a JSON-compatible value into an array of YAML block
91
+ * notation lines.
92
+ *
93
+ * Each returned line has **no leading indentation** — it is the caller's
94
+ * responsibility to prefix nested lines with `' '` (two spaces) when
95
+ * embedding them inside a mapping value or sequence item.
96
+ *
97
+ * @param value - The value to serialise.
98
+ */
99
+ function toYamlLines(value) {
100
+ // ── Scalars ─────────────────────────────────────────────────────────────────
101
+ if (value === null || value === undefined)
102
+ return ['null'];
103
+ if (typeof value === 'boolean')
104
+ return [String(value)];
105
+ if (typeof value === 'number')
106
+ return [isFinite(value) ? String(value) : '.inf'];
107
+ if (typeof value === 'string')
108
+ return [yamlString(value)];
109
+ // ── Sequences ────────────────────────────────────────────────────────────────
110
+ if (Array.isArray(value)) {
111
+ if (value.length === 0)
112
+ return ['[]'];
113
+ const lines = [];
114
+ for (const item of value) {
115
+ const itemLines = toYamlLines(item);
116
+ // First line of the item goes on the same line as the dash.
117
+ lines.push(`- ${itemLines[0]}`);
118
+ // Subsequent lines are indented by two spaces (relative to the `-`).
119
+ for (const l of itemLines.slice(1))
120
+ lines.push(` ${l}`);
121
+ }
122
+ return lines;
123
+ }
124
+ // ── Mappings ─────────────────────────────────────────────────────────────────
125
+ if (typeof value === 'object') {
126
+ const obj = value;
127
+ const entries = Object.entries(obj).filter(([, v]) => v !== undefined);
128
+ if (entries.length === 0)
129
+ return ['{}'];
130
+ const lines = [];
131
+ for (const [key, val] of entries) {
132
+ const k = yamlKey(key);
133
+ const valLines = toYamlLines(val);
134
+ // Inline only when the value is a scalar, null, or an empty collection
135
+ // (`{}` / `[]`). Non-empty objects and arrays — even when they happen
136
+ // to serialise to a single line (e.g. `$ref: "#/..."`) — must go block
137
+ // style; inlining them produces ambiguous YAML like `key: $ref: "..."`.
138
+ const isComplex = typeof val === 'object' && val !== null;
139
+ const isEmptyCollection = valLines.length === 1 &&
140
+ (valLines[0] === '{}' || valLines[0] === '[]');
141
+ if (!isComplex || isEmptyCollection) {
142
+ // Scalar or empty collection: fits on the same line as the key.
143
+ lines.push(`${k}: ${valLines[0]}`);
144
+ }
145
+ else {
146
+ // Block style: key on its own line, value indented below.
147
+ lines.push(`${k}:`);
148
+ for (const l of valLines)
149
+ lines.push(` ${l}`);
150
+ }
151
+ }
152
+ return lines;
153
+ }
154
+ // Defensive fallback: every JSON value type (null, boolean, number, string,
155
+ // array, object) is handled above, so `value` here is only reachable for
156
+ // bigint/symbol/function — none of which occur in a JSON-derived spec.
157
+ // eslint-disable-next-line @typescript-eslint/no-base-to-string
158
+ return [String(value)];
159
+ }
160
+ /**
161
+ * Serialise an {@link OpenApiDocument} to either JSON or YAML.
162
+ *
163
+ * - `'json'` — pretty-printed with 2-space indentation.
164
+ * - `'yaml'` — block-style YAML 1.2, produced by a zero-dependency serialiser
165
+ * built into expediate.
166
+ *
167
+ * @param doc - The document to serialise.
168
+ * @param format - Output format (`'json'` by default).
169
+ */
170
+ export function serializeSpec(doc, format = 'json') {
171
+ if (format === 'yaml')
172
+ return toYamlLines(doc).join('\n') + '\n';
173
+ return JSON.stringify(doc, null, 2);
174
+ }
175
+ // ---------------------------------------------------------------------------
176
+ // Symbol for attaching metadata to handler functions
177
+ // ---------------------------------------------------------------------------
178
+ /**
179
+ * Unique symbol used as a non-enumerable property key on handler functions
180
+ * that have been annotated via {@link describe}.
181
+ *
182
+ * Using a `unique symbol` (rather than a plain string key) prevents accidental
183
+ * collisions with user-defined properties on handler objects.
184
+ */
185
+ export const DESCRIBE_META = Symbol('expediate.openapi.meta');
186
+ // ---------------------------------------------------------------------------
187
+ // Public API
188
+ // ---------------------------------------------------------------------------
189
+ /**
190
+ * Annotate a service method handler with OpenAPI operation metadata.
191
+ *
192
+ * The metadata is attached to the returned function via a non-enumerable
193
+ * property keyed by {@link DESCRIBE_META}. The returned function is otherwise
194
+ * identical to `handler` — it can be used directly in a `ServiceDefinition`
195
+ * route map.
196
+ *
197
+ * ```ts
198
+ * GET: {
199
+ * '/items/:id': describe(
200
+ * function (this: TodoService, p) {
201
+ * return this.findOrFail(p.id);
202
+ * },
203
+ * {
204
+ * summary: 'Get a single item by ID',
205
+ * tags: ['items'],
206
+ * parameters: [{ name: 'id', in: 'path', required: true, schema: { type: 'string' } }],
207
+ * responses: {
208
+ * '200': { description: 'The item', content: { 'application/json': { schema: { $ref: '#/components/schemas/Item' } } } },
209
+ * '404': { description: 'Not found' },
210
+ * },
211
+ * },
212
+ * ),
213
+ * }
214
+ * ```
215
+ *
216
+ * @param handler - The service method to annotate.
217
+ * @param meta - OpenAPI operation metadata.
218
+ * @returns The same handler function, with metadata attached.
219
+ */
220
+ export function describe(handler, meta) {
221
+ // Wrap the handler so we have a fresh function object to attach metadata to,
222
+ // avoiding unexpected mutations of functions shared across route maps.
223
+ const described = function (ctx, body) {
224
+ return handler.apply(this, [ctx, body]);
225
+ };
226
+ // Attach metadata as a non-enumerable property so it is invisible to
227
+ // Object.keys() / JSON.stringify() and does not pollute the function's
228
+ // "own" enumerable surface.
229
+ Object.defineProperty(described, DESCRIBE_META, {
230
+ value: meta,
231
+ enumerable: false,
232
+ configurable: true,
233
+ writable: false,
234
+ });
235
+ return described;
236
+ }
237
+ // ---------------------------------------------------------------------------
238
+ // Internal path-translation helpers
239
+ // ---------------------------------------------------------------------------
240
+ /**
241
+ * Convert an Express-style path pattern to an OpenAPI path pattern.
242
+ *
243
+ * - `:param` segments become `{param}`.
244
+ * - An optional `basePath` prefix is prepended (with duplicate-slash guards).
245
+ *
246
+ * @example
247
+ * ```ts
248
+ * toOpenApiPath('/items/:id', '/api/v1') // → '/api/v1/items/{id}'
249
+ * toOpenApiPath('/items', '') // → '/items'
250
+ * ```
251
+ */
252
+ function toOpenApiPath(pattern, basePath) {
253
+ const converted = pattern.replace(/:([A-Za-z_][A-Za-z0-9_]*)/g, '{$1}');
254
+ if (!basePath)
255
+ return converted;
256
+ const base = basePath.replace(/\/+$/, '');
257
+ const path = converted.replace(/^\/+/, '/');
258
+ return base + (path.startsWith('/') ? path : `/${path}`);
259
+ }
260
+ /**
261
+ * Extract named path parameters from an Express-style pattern as
262
+ * {@link ParameterObject} entries with `in: 'path'` and `required: true`.
263
+ *
264
+ * Parameters already listed in `annotated` (by name) are skipped to avoid
265
+ * duplicates when the caller has provided explicit metadata for them.
266
+ *
267
+ * @param pattern - Express-style route pattern (e.g. `/items/:id`).
268
+ * @param annotated - Explicit parameters provided by the caller (may be empty).
269
+ */
270
+ function extractPathParams(pattern, annotated) {
271
+ const annotatedNames = new Set(annotated.map(p => p.name));
272
+ const params = [];
273
+ for (const match of pattern.matchAll(/:([A-Za-z_][A-Za-z0-9_]*)/g)) {
274
+ const name = match[1];
275
+ if (!annotatedNames.has(name)) {
276
+ params.push({
277
+ name,
278
+ in: 'path',
279
+ required: true,
280
+ schema: { type: 'string' },
281
+ });
282
+ }
283
+ }
284
+ return params;
285
+ }
286
+ /**
287
+ * Build an `operationId` string from an HTTP verb and a path pattern.
288
+ *
289
+ * The algorithm:
290
+ * 1. Lowercase the verb (e.g. `'GET'` → `'get'`).
291
+ * 2. Split the pattern on `/` and `:` (and `{` / `}` for pre-translated paths).
292
+ * 3. CamelCase each non-empty segment (first-letter uppercase).
293
+ * 4. Prefix each path-param segment with `'By'`.
294
+ * 5. Join everything into a single camelCase string.
295
+ *
296
+ * @example
297
+ * ```ts
298
+ * buildOperationId('GET', '/items') // → 'getItems'
299
+ * buildOperationId('GET', '/items/:id') // → 'getItemsById'
300
+ * buildOperationId('DELETE', '/a/:b/:c') // → 'deleteAByBByC'
301
+ * ```
302
+ */
303
+ function buildOperationId(verb, pattern) {
304
+ const parts = pattern.split(/[/:{} ]+/).filter(Boolean);
305
+ let result = verb.toLowerCase();
306
+ for (const part of parts) {
307
+ const isParam = pattern.includes(`:${part}`) || pattern.includes(`{${part}}`);
308
+ const pascal = part.charAt(0).toUpperCase() + part.slice(1);
309
+ result += isParam ? `By${pascal}` : pascal;
310
+ }
311
+ return result;
312
+ }
313
+ /**
314
+ * Build the default responses for an operation when none are provided by the
315
+ * caller.
316
+ *
317
+ * - `POST` → `201 No Content` (successful write with no response body).
318
+ * - All other verbs → `200 OK` with a generic JSON response.
319
+ * - `500` → always added as a reference to the built-in `ApiError` component.
320
+ */
321
+ function buildDefaultResponses(verb) {
322
+ const ok = verb === 'POST'
323
+ ? { description: 'Created' }
324
+ : { description: 'OK', content: { 'application/json': {} } };
325
+ return {
326
+ [verb === 'POST' ? '201' : '200']: ok,
327
+ '500': { $ref: '#/components/responses/ApiError' },
328
+ };
329
+ }
330
+ /**
331
+ * Convert caller-provided `responses` metadata into the OpenAPI responses
332
+ * map, injecting the built-in `ApiError` reference for `500` unless the
333
+ * caller has explicitly provided one.
334
+ */
335
+ function buildAnnotatedResponses(responses) {
336
+ const result = { ...responses };
337
+ if (!result['500']) {
338
+ result['500'] = { $ref: '#/components/responses/ApiError' };
339
+ }
340
+ return result;
341
+ }
342
+ // ---------------------------------------------------------------------------
343
+ // Multi-source route collection (spec-generation counterpart to `collectRoutes`)
344
+ // ---------------------------------------------------------------------------
345
+ /**
346
+ * The five HTTP verbs `openApiSpec` looks for route maps under.
347
+ *
348
+ * Kept as a private duplicate of `apis.ts`'s internal `VERBS` (only the
349
+ * derived {@link ApiVerb} type is exported from there) — not worth exporting
350
+ * a const for.
351
+ */
352
+ const VERBS = ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'];
353
+ /**
354
+ * Build the merged, globally-sorted route table for one or more
355
+ * {@link OpenApiSource} values — the spec-generation counterpart to
356
+ * `apis.ts`'s `collectRoutes`.
357
+ *
358
+ * Deliberately kept separate from `collectRoutes` so the real request
359
+ * pipeline (`apiBuilder`) is never affected by spec-only concerns: this
360
+ * function only inspects route *shapes* to produce documentation and never
361
+ * invokes a handler. A route value is resolved as `describe()`-attached
362
+ * metadata when it is a function, or used directly as an {@link OperationMeta}
363
+ * object when it is not (the {@link ServiceOpenApi} case).
364
+ *
365
+ * Duplicate `(verb, path)` pairs are detected **across all sources**, not
366
+ * just within one — extending `collectRoutes`'s single-service duplicate
367
+ * check to the merged multi-source document.
368
+ *
369
+ * @throws Error on a duplicate `(verb, path)` pair across any of the sources.
370
+ */
371
+ function collectOpenApiRoutes(sources) {
372
+ const routes = [];
373
+ /** Duplicate detection across ALL sources: `"VERB /joined/path"` → declarer label. */
374
+ const seen = new Map();
375
+ sources.forEach((source, sourceIndex) => {
376
+ const defaultTag = source.openapi?.tag;
377
+ const permissionsExtension = source.auth?.permissionsExtension ?? 'x-required-permissions';
378
+ // Root route maps form an implicit, anonymous controller — same trick as
379
+ // `collectRoutes`'s `rootController`.
380
+ const rootController = {
381
+ prefix: '',
382
+ GET: source.GET,
383
+ POST: source.POST,
384
+ PUT: source.PUT,
385
+ DELETE: source.DELETE,
386
+ PATCH: source.PATCH,
387
+ };
388
+ const controllers = [rootController, ...(source.controllers ?? [])];
389
+ controllers.forEach((controller, controllerIndex) => {
390
+ const label = controllerIndex === 0
391
+ ? `source #${sourceIndex}`
392
+ : (controller.tags?.[0] ?? controller.prefix ?? `source #${sourceIndex} controller #${controllerIndex}`);
393
+ const prefix = controller.prefix ?? '';
394
+ for (const verb of VERBS) {
395
+ const routeMap = controller[verb];
396
+ if (!routeMap)
397
+ continue;
398
+ for (const [pattern, value] of Object.entries(routeMap)) {
399
+ const path = joinPath(prefix, pattern);
400
+ // Loud failure on duplicates, across the whole merged document.
401
+ const dupKey = `${verb} ${path}`;
402
+ const declarer = seen.get(dupKey);
403
+ if (declarer !== undefined) {
404
+ throw new Error(`openApiSpec: duplicate route ${verb} ${path}\n` +
405
+ ` declared by '${declarer}' and '${label}'`);
406
+ }
407
+ seen.set(dupKey, label);
408
+ const meta = typeof value === 'function'
409
+ ? value[DESCRIBE_META]
410
+ : value;
411
+ routes.push({
412
+ verb,
413
+ path,
414
+ meta,
415
+ tags: meta?.tags ?? controller.tags,
416
+ permission: normalizePermission(meta?.permission ?? controller.permission),
417
+ defaultTag,
418
+ permissionsExtension,
419
+ });
420
+ }
421
+ }
422
+ });
423
+ });
424
+ // Global specificity sort across all sources and controllers.
425
+ routes.sort((a, b) => routeScore(b.path) - routeScore(a.path) || b.path.localeCompare(a.path));
426
+ return routes;
427
+ }
428
+ // ---------------------------------------------------------------------------
429
+ // Core spec generator
430
+ // ---------------------------------------------------------------------------
431
+ /** Default OpenAPI security scheme when `AuthBinding.scheme` is absent. */
432
+ const DEFAULT_SECURITY_SCHEME = {
433
+ type: 'http',
434
+ scheme: 'bearer',
435
+ bearerFormat: 'JWT',
436
+ };
437
+ /**
438
+ * Generate an OpenAPI 3.1.0 document from one or more {@link OpenApiSource}
439
+ * values — real {@link ServiceDefinition}s, spec-only {@link ServiceOpenApi}
440
+ * descriptions, or a mix of both in a single array.
441
+ *
442
+ * Route handlers that have been annotated with {@link describe} contribute
443
+ * rich operation metadata (summary, description, parameters, requestBody,
444
+ * responses). Unannotated handlers receive sensible defaults automatically.
445
+ * `ServiceOpenApi` route maps provide that same {@link OperationMeta} shape
446
+ * directly, since there is no handler to annotate.
447
+ *
448
+ * The generated document always includes:
449
+ * - An `ApiError` schema (shape: `{ status?, message?, data? }`) in
450
+ * `components.schemas`.
451
+ * - An `ApiError` response (`500` reference) in `components.responses`.
452
+ *
453
+ * Caller-supplied `opts.schemas` and each source's `openapi.schemas` /
454
+ * `schemas` are merged on top of the built-in components, source by source
455
+ * in array order — for a single source this preserves the original
456
+ * precedence exactly: built-ins ← `openapi.schemas` ← `opts.schemas` ←
457
+ * `schemas`.
458
+ *
459
+ * Routes are merged and duplicate-checked **across all sources** (see
460
+ * {@link collectOpenApiRoutes}), so passing several sources still produces
461
+ * ONE document. Each route's default tag and permissions vendor-extension
462
+ * name are resolved from its own originating source; the
463
+ * `components.securitySchemes.bearerAuth` value comes from the first source
464
+ * in array order that declares a custom `auth.scheme`, else the default.
465
+ *
466
+ * @param service - The source(s) to document.
467
+ * @param opts - Top-level spec options (title, version, basePath, …).
468
+ * @returns A fully-formed OpenAPI 3.1.0 document object.
469
+ *
470
+ * @example
471
+ * ```ts
472
+ * const spec = openApiSpec(todoDefinition, {
473
+ * title: 'Todo API',
474
+ * version: '1.0.0',
475
+ * basePath: '/api',
476
+ * });
477
+ *
478
+ * app.get('/openapi.json', (_req, res) => {
479
+ * res.json(spec);
480
+ * });
481
+ * ```
482
+ *
483
+ * @example Merging a real service with hand-documented auth routes
484
+ * ```ts
485
+ * const authDocs: ServiceOpenApi = {
486
+ * openapi: { tag: 'auth' },
487
+ * POST: {
488
+ * '/auth/login': { summary: 'Log in' },
489
+ * '/auth/refresh': { summary: 'Refresh a token' },
490
+ * '/auth/logout': { summary: 'Log out' },
491
+ * },
492
+ * };
493
+ *
494
+ * const spec = openApiSpec([authDocs, todoDefinition], { title: 'Todo API', version: '1.0.0' });
495
+ * ```
496
+ */
497
+ export function openApiSpec(service, opts) {
498
+ const sources = Array.isArray(service) ? service : [service];
499
+ const basePath = opts.basePath ?? '';
500
+ // ── Components ──────────────────────────────────────────────────────────────
501
+ const builtinSchemas = {
502
+ ApiError: {
503
+ type: 'object',
504
+ properties: {
505
+ status: { type: 'integer', description: 'HTTP status code' },
506
+ message: { type: 'string', description: 'Human-readable error message' },
507
+ data: { description: 'Structured error payload (overrides message when present)' },
508
+ },
509
+ },
510
+ };
511
+ const builtinResponses = {
512
+ ApiError: {
513
+ description: 'API error response',
514
+ content: {
515
+ 'application/json': { schema: { $ref: '#/components/schemas/ApiError' } },
516
+ },
517
+ },
518
+ };
519
+ // Merge schemas: built-ins ← each source's openapi.schemas (array order)
520
+ // ← caller-level opts.schemas ← each source's own `schemas` (array order,
521
+ // last wins). For a single source this is exactly the original order:
522
+ // built-ins, service-level openapi meta, caller-level, service-definition
523
+ // `schemas` — which supersedes `opts.schemas`.
524
+ let schemas = { ...builtinSchemas };
525
+ for (const source of sources)
526
+ schemas = { ...schemas, ...source.openapi?.schemas };
527
+ schemas = { ...schemas, ...opts.schemas };
528
+ for (const source of sources)
529
+ schemas = { ...schemas, ...source.schemas };
530
+ let responses = { ...builtinResponses };
531
+ for (const source of sources)
532
+ responses = { ...responses, ...source.openapi?.responses };
533
+ // ── Tags ─────────────────────────────────────────────────────────────────────
534
+ const tags = [];
535
+ const seenTags = new Set();
536
+ for (const source of sources) {
537
+ const tag = source.openapi?.tag;
538
+ if (tag && !seenTags.has(tag)) {
539
+ seenTags.add(tag);
540
+ tags.push({ name: tag, description: source.openapi?.tagDescription });
541
+ }
542
+ }
543
+ // ── Security scheme ──────────────────────────────────────────────────────────
544
+ // First source in array order that declares a custom scheme wins; falls
545
+ // back to the default HTTP bearer/JWT scheme.
546
+ const securityScheme = sources.find(s => s.auth?.scheme)?.auth?.scheme ?? DEFAULT_SECURITY_SCHEME;
547
+ // ── Paths ────────────────────────────────────────────────────────────────────
548
+ // Operates on the merged route table (all sources' root route maps and
549
+ // controllers), so multiple sources still produce ONE document. Controller
550
+ // `tags` fill in `OperationMeta.tags` when a route declares none.
551
+ const paths = {};
552
+ const routes = collectOpenApiRoutes(sources);
553
+ let securedRoutes = false;
554
+ for (const route of routes) {
555
+ const { verb, path: pattern, meta } = route;
556
+ const openApiPath = toOpenApiPath(pattern, basePath);
557
+ if (!paths[openApiPath])
558
+ paths[openApiPath] = {};
559
+ // ── Parameters ─────────────────────────────────────────────────────────
560
+ const annotatedParams = meta?.parameters ?? [];
561
+ const inferredParams = extractPathParams(pattern, annotatedParams);
562
+ const parameters = [...annotatedParams, ...inferredParams];
563
+ // ── Responses ──────────────────────────────────────────────────────────
564
+ const operationResponses = meta?.responses
565
+ ? buildAnnotatedResponses(meta.responses)
566
+ : buildDefaultResponses(verb);
567
+ // ── Operation object ───────────────────────────────────────────────────
568
+ const operation = {
569
+ operationId: meta?.operationId ?? buildOperationId(verb, pattern),
570
+ ...(meta?.summary && { summary: meta.summary }),
571
+ ...(meta?.description && { description: meta.description }),
572
+ ...(meta?.deprecated && { deprecated: true }),
573
+ ...(parameters.length > 0 && { parameters }),
574
+ ...(meta?.requestBody && { requestBody: meta.requestBody }),
575
+ responses: operationResponses,
576
+ };
577
+ // Apply route tags (meta-level, else controller-level), then this route's
578
+ // source default tag when neither is declared.
579
+ const opTags = route.tags ?? (route.defaultTag ? [route.defaultTag] : undefined);
580
+ if (opTags)
581
+ operation.tags = opTags;
582
+ // Carry through any vendor extensions (x-* keys).
583
+ if (meta) {
584
+ for (const [k, v] of Object.entries(meta)) {
585
+ if (k.startsWith('x-'))
586
+ operation[k] = v;
587
+ }
588
+ }
589
+ // ── Security ───────────────────────────────────────────────────────────
590
+ // Routes carrying a permission requirement (route- or controller-level)
591
+ // are marked with the bearerAuth scheme and the vendor extension listing
592
+ // the required permissions.
593
+ if (route.permission) {
594
+ securedRoutes = true;
595
+ operation.security = [{ bearerAuth: [] }];
596
+ operation[route.permissionsExtension] = route.permission;
597
+ }
598
+ paths[openApiPath][verb.toLowerCase()] = operation;
599
+ }
600
+ // ── Assemble document ────────────────────────────────────────────────────────
601
+ const doc = {
602
+ openapi: '3.1.0',
603
+ info: {
604
+ title: opts.title,
605
+ version: opts.version,
606
+ ...(opts.description && { description: opts.description }),
607
+ },
608
+ ...(opts.servers && { servers: opts.servers }),
609
+ ...(tags.length > 0 && { tags }),
610
+ paths,
611
+ components: {
612
+ schemas,
613
+ responses,
614
+ // Emitted once when at least one operation declares a permission.
615
+ ...(securedRoutes && {
616
+ securitySchemes: {
617
+ bearerAuth: securityScheme,
618
+ },
619
+ }),
620
+ },
621
+ };
622
+ return doc;
623
+ }
624
+ //# sourceMappingURL=openapi.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openapi.js","sourceRoot":"","sources":["../src/openapi.ts"],"names":[],"mappings":"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"}