expediate 1.0.4 → 1.0.5
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.
- package/LICENSE +16 -16
- package/README.md +417 -30
- package/dist/apis.d.ts +138 -21
- package/dist/apis.d.ts.map +1 -1
- package/dist/apis.js +172 -79
- package/dist/apis.js.map +1 -1
- package/dist/cjs/apis.js +327 -0
- package/dist/cjs/git.js +293 -0
- package/dist/cjs/index.js +2583 -0
- package/dist/cjs/jwt-auth.js +532 -0
- package/dist/cjs/middleware.js +511 -0
- package/dist/cjs/mimetypes.json +1 -0
- package/dist/cjs/misc.js +787 -0
- package/dist/cjs/openapi.js +485 -0
- package/dist/cjs/package.json +1 -0
- package/dist/cjs/router.js +898 -0
- package/dist/cjs/static.js +669 -0
- package/dist/git.d.ts +71 -8
- package/dist/git.d.ts.map +1 -1
- package/dist/git.js +127 -72
- package/dist/git.js.map +1 -1
- package/dist/index.d.ts +17 -13
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +14 -24
- package/dist/index.js.map +1 -1
- package/dist/jwt-auth.d.ts +147 -57
- package/dist/jwt-auth.d.ts.map +1 -1
- package/dist/jwt-auth.js +445 -205
- package/dist/jwt-auth.js.map +1 -1
- package/dist/middleware.d.ts +476 -0
- package/dist/middleware.d.ts.map +1 -0
- package/dist/middleware.js +647 -0
- package/dist/middleware.js.map +1 -0
- package/dist/mimetypes.json +1 -1
- package/dist/misc.d.ts +112 -5
- package/dist/misc.d.ts.map +1 -1
- package/dist/misc.js +235 -102
- package/dist/misc.js.map +1 -1
- package/dist/openapi.d.ts +290 -0
- package/dist/openapi.d.ts.map +1 -0
- package/dist/openapi.js +481 -0
- package/dist/openapi.js.map +1 -0
- package/dist/router.d.ts +405 -46
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +658 -153
- package/dist/router.js.map +1 -1
- package/dist/static.d.ts +1 -1
- package/dist/static.d.ts.map +1 -1
- package/dist/static.js +88 -84
- package/dist/static.js.map +1 -1
- package/package.json +21 -4
- package/.npmignore +0 -16
package/dist/openapi.js
ADDED
|
@@ -0,0 +1,481 @@
|
|
|
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
|
+
// ── YAML serialiser (zero-dependency, block-style) ────────────────────────────
|
|
23
|
+
/**
|
|
24
|
+
* YAML reserved keywords that must be quoted as scalars so that YAML parsers
|
|
25
|
+
* do not interpret them as the corresponding typed values.
|
|
26
|
+
*/
|
|
27
|
+
const YAML_KW = new Set([
|
|
28
|
+
'true', 'false', 'yes', 'no', 'on', 'off', 'null', '~',
|
|
29
|
+
]);
|
|
30
|
+
/**
|
|
31
|
+
* Pattern for "safe" plain scalars: they can be represented without quoting.
|
|
32
|
+
* A scalar is safe when it:
|
|
33
|
+
* - contains only letters, digits, `_`, `-`, `.` or `/`
|
|
34
|
+
* - starts with a letter, `_`, `/`, or `$`
|
|
35
|
+
* - is not a YAML reserved keyword
|
|
36
|
+
* - does not look like a number
|
|
37
|
+
*/
|
|
38
|
+
const SAFE_SCALAR = /^[a-zA-Z$_/][a-zA-Z0-9$_\-./]*$/;
|
|
39
|
+
/** Pattern matching integer and floating-point number strings. */
|
|
40
|
+
const LOOKS_LIKE_NUMBER = /^[-+]?(\d+\.?\d*|\.\d+)([eE][+-]?\d+)?$|^0x[0-9a-fA-F]+$|^0o[0-7]+$/;
|
|
41
|
+
/**
|
|
42
|
+
* Serialise a string as a YAML scalar value or key.
|
|
43
|
+
*
|
|
44
|
+
* The string is returned unquoted when it is a valid YAML plain scalar —
|
|
45
|
+
* i.e. it cannot be misinterpreted as another type and does not contain
|
|
46
|
+
* characters that would confuse a YAML parser.
|
|
47
|
+
*
|
|
48
|
+
* Otherwise the value is wrapped in double quotes with control characters,
|
|
49
|
+
* backslashes, and `"` escaped so the output is always valid YAML 1.2.
|
|
50
|
+
*
|
|
51
|
+
* @param s - The string to serialise.
|
|
52
|
+
*/
|
|
53
|
+
function yamlString(s) {
|
|
54
|
+
if (s === '')
|
|
55
|
+
return '""';
|
|
56
|
+
const needsQuote =
|
|
57
|
+
// Would be misinterpreted as a YAML typed value.
|
|
58
|
+
YAML_KW.has(s.toLowerCase()) ||
|
|
59
|
+
LOOKS_LIKE_NUMBER.test(s) ||
|
|
60
|
+
// Starts with a YAML indicator that has special meaning at the start of a
|
|
61
|
+
// plain scalar (block context).
|
|
62
|
+
/^[-?:,[\]{}#&*!|>'"%@`~]/.test(s) ||
|
|
63
|
+
// Inline sequences that break block-mapping parsing.
|
|
64
|
+
s.includes(': ') ||
|
|
65
|
+
s.endsWith(':') ||
|
|
66
|
+
s.includes(' #') ||
|
|
67
|
+
// Leading / trailing whitespace.
|
|
68
|
+
s !== s.trim() ||
|
|
69
|
+
// Flow indicator characters anywhere — present in path patterns such as
|
|
70
|
+
// `/items/{id}` and must be quoted to avoid flow-collection ambiguity.
|
|
71
|
+
/[{}\[\]]/.test(s) ||
|
|
72
|
+
// Control characters.
|
|
73
|
+
/[\x00-\x1f\x7f]/.test(s);
|
|
74
|
+
if (!needsQuote)
|
|
75
|
+
return s;
|
|
76
|
+
// Double-quoted style: always valid, handles all edge cases.
|
|
77
|
+
return '"' +
|
|
78
|
+
s
|
|
79
|
+
.replace(/\\/g, '\\\\')
|
|
80
|
+
.replace(/"/g, '\\"')
|
|
81
|
+
.replace(/\n/g, '\\n')
|
|
82
|
+
.replace(/\r/g, '\\r')
|
|
83
|
+
.replace(/\t/g, '\\t')
|
|
84
|
+
.replace(/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g, c => `\\u${c.charCodeAt(0).toString(16).padStart(4, '0')}`)
|
|
85
|
+
+ '"';
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Serialise a mapping key as a YAML scalar.
|
|
89
|
+
*
|
|
90
|
+
* Uses the same quoting rules as {@link yamlString}. Numbers as keys
|
|
91
|
+
* (e.g. HTTP status codes `200`, `500`) are always quoted so that YAML
|
|
92
|
+
* parsers do not interpret them as integer keys.
|
|
93
|
+
*/
|
|
94
|
+
function yamlKey(key) {
|
|
95
|
+
return yamlString(key);
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Recursively serialise a JSON-compatible value into an array of YAML block
|
|
99
|
+
* notation lines.
|
|
100
|
+
*
|
|
101
|
+
* Each returned line has **no leading indentation** — it is the caller's
|
|
102
|
+
* responsibility to prefix nested lines with `' '` (two spaces) when
|
|
103
|
+
* embedding them inside a mapping value or sequence item.
|
|
104
|
+
*
|
|
105
|
+
* @param value - The value to serialise.
|
|
106
|
+
*/
|
|
107
|
+
function toYamlLines(value) {
|
|
108
|
+
// ── Scalars ─────────────────────────────────────────────────────────────────
|
|
109
|
+
if (value === null || value === undefined)
|
|
110
|
+
return ['null'];
|
|
111
|
+
if (typeof value === 'boolean')
|
|
112
|
+
return [String(value)];
|
|
113
|
+
if (typeof value === 'number')
|
|
114
|
+
return [isFinite(value) ? String(value) : '.inf'];
|
|
115
|
+
if (typeof value === 'string')
|
|
116
|
+
return [yamlString(value)];
|
|
117
|
+
// ── Sequences ────────────────────────────────────────────────────────────────
|
|
118
|
+
if (Array.isArray(value)) {
|
|
119
|
+
if (value.length === 0)
|
|
120
|
+
return ['[]'];
|
|
121
|
+
const lines = [];
|
|
122
|
+
for (const item of value) {
|
|
123
|
+
const itemLines = toYamlLines(item);
|
|
124
|
+
// First line of the item goes on the same line as the dash.
|
|
125
|
+
lines.push(`- ${itemLines[0]}`);
|
|
126
|
+
// Subsequent lines are indented by two spaces (relative to the `-`).
|
|
127
|
+
for (const l of itemLines.slice(1))
|
|
128
|
+
lines.push(` ${l}`);
|
|
129
|
+
}
|
|
130
|
+
return lines;
|
|
131
|
+
}
|
|
132
|
+
// ── Mappings ─────────────────────────────────────────────────────────────────
|
|
133
|
+
if (typeof value === 'object') {
|
|
134
|
+
const obj = value;
|
|
135
|
+
const entries = Object.entries(obj).filter(([, v]) => v !== undefined);
|
|
136
|
+
if (entries.length === 0)
|
|
137
|
+
return ['{}'];
|
|
138
|
+
const lines = [];
|
|
139
|
+
for (const [key, val] of entries) {
|
|
140
|
+
const k = yamlKey(key);
|
|
141
|
+
const valLines = toYamlLines(val);
|
|
142
|
+
// Inline only when the value is a scalar, null, or an empty collection
|
|
143
|
+
// (`{}` / `[]`). Non-empty objects and arrays — even when they happen
|
|
144
|
+
// to serialise to a single line (e.g. `$ref: "#/..."`) — must go block
|
|
145
|
+
// style; inlining them produces ambiguous YAML like `key: $ref: "..."`.
|
|
146
|
+
const isComplex = typeof val === 'object' && val !== null;
|
|
147
|
+
const isEmptyCollection = valLines.length === 1 &&
|
|
148
|
+
(valLines[0] === '{}' || valLines[0] === '[]');
|
|
149
|
+
if (!isComplex || isEmptyCollection) {
|
|
150
|
+
// Scalar or empty collection: fits on the same line as the key.
|
|
151
|
+
lines.push(`${k}: ${valLines[0]}`);
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
// Block style: key on its own line, value indented below.
|
|
155
|
+
lines.push(`${k}:`);
|
|
156
|
+
for (const l of valLines)
|
|
157
|
+
lines.push(` ${l}`);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return lines;
|
|
161
|
+
}
|
|
162
|
+
return [String(value)];
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Serialise an {@link OpenApiDocument} to either JSON or YAML.
|
|
166
|
+
*
|
|
167
|
+
* - `'json'` — pretty-printed with 2-space indentation.
|
|
168
|
+
* - `'yaml'` — block-style YAML 1.2, produced by a zero-dependency serialiser
|
|
169
|
+
* built into expediate.
|
|
170
|
+
*
|
|
171
|
+
* @param doc - The document to serialise.
|
|
172
|
+
* @param format - Output format (`'json'` by default).
|
|
173
|
+
*/
|
|
174
|
+
export function serializeSpec(doc, format = 'json') {
|
|
175
|
+
if (format === 'yaml')
|
|
176
|
+
return toYamlLines(doc).join('\n') + '\n';
|
|
177
|
+
return JSON.stringify(doc, null, 2);
|
|
178
|
+
}
|
|
179
|
+
// ---------------------------------------------------------------------------
|
|
180
|
+
// Symbol for attaching metadata to handler functions
|
|
181
|
+
// ---------------------------------------------------------------------------
|
|
182
|
+
/**
|
|
183
|
+
* Unique symbol used as a non-enumerable property key on handler functions
|
|
184
|
+
* that have been annotated via {@link describe}.
|
|
185
|
+
*
|
|
186
|
+
* Using a `unique symbol` (rather than a plain string key) prevents accidental
|
|
187
|
+
* collisions with user-defined properties on handler objects.
|
|
188
|
+
*/
|
|
189
|
+
export const DESCRIBE_META = Symbol('expediate.openapi.meta');
|
|
190
|
+
// ---------------------------------------------------------------------------
|
|
191
|
+
// Public API
|
|
192
|
+
// ---------------------------------------------------------------------------
|
|
193
|
+
/**
|
|
194
|
+
* Annotate a service method handler with OpenAPI operation metadata.
|
|
195
|
+
*
|
|
196
|
+
* The metadata is attached to the returned function via a non-enumerable
|
|
197
|
+
* property keyed by {@link DESCRIBE_META}. The returned function is otherwise
|
|
198
|
+
* identical to `handler` — it can be used directly in a `ServiceDefinition`
|
|
199
|
+
* route map.
|
|
200
|
+
*
|
|
201
|
+
* ```ts
|
|
202
|
+
* GET: {
|
|
203
|
+
* '/items/:id': describe(
|
|
204
|
+
* function (this: TodoService, p) {
|
|
205
|
+
* return this.findOrFail(p.id);
|
|
206
|
+
* },
|
|
207
|
+
* {
|
|
208
|
+
* summary: 'Get a single item by ID',
|
|
209
|
+
* tags: ['items'],
|
|
210
|
+
* parameters: [{ name: 'id', in: 'path', required: true, schema: { type: 'string' } }],
|
|
211
|
+
* responses: {
|
|
212
|
+
* '200': { description: 'The item', content: { 'application/json': { schema: { $ref: '#/components/schemas/Item' } } } },
|
|
213
|
+
* '404': { description: 'Not found' },
|
|
214
|
+
* },
|
|
215
|
+
* },
|
|
216
|
+
* ),
|
|
217
|
+
* }
|
|
218
|
+
* ```
|
|
219
|
+
*
|
|
220
|
+
* @param handler - The service method to annotate.
|
|
221
|
+
* @param meta - OpenAPI operation metadata.
|
|
222
|
+
* @returns The same handler function, with metadata attached.
|
|
223
|
+
*/
|
|
224
|
+
export function describe(handler, meta) {
|
|
225
|
+
// Wrap the handler so we have a fresh function object to attach metadata to,
|
|
226
|
+
// avoiding unexpected mutations of functions shared across route maps.
|
|
227
|
+
const described = function (ctx, body) {
|
|
228
|
+
return handler.apply(this, [ctx, body]);
|
|
229
|
+
};
|
|
230
|
+
// Attach metadata as a non-enumerable property so it is invisible to
|
|
231
|
+
// Object.keys() / JSON.stringify() and does not pollute the function's
|
|
232
|
+
// "own" enumerable surface.
|
|
233
|
+
Object.defineProperty(described, DESCRIBE_META, {
|
|
234
|
+
value: meta,
|
|
235
|
+
enumerable: false,
|
|
236
|
+
configurable: true,
|
|
237
|
+
writable: false,
|
|
238
|
+
});
|
|
239
|
+
return described;
|
|
240
|
+
}
|
|
241
|
+
// ---------------------------------------------------------------------------
|
|
242
|
+
// Internal path-translation helpers
|
|
243
|
+
// ---------------------------------------------------------------------------
|
|
244
|
+
/**
|
|
245
|
+
* Convert an Express-style path pattern to an OpenAPI path pattern.
|
|
246
|
+
*
|
|
247
|
+
* - `:param` segments become `{param}`.
|
|
248
|
+
* - An optional `basePath` prefix is prepended (with duplicate-slash guards).
|
|
249
|
+
*
|
|
250
|
+
* @example
|
|
251
|
+
* ```ts
|
|
252
|
+
* toOpenApiPath('/items/:id', '/api/v1') // → '/api/v1/items/{id}'
|
|
253
|
+
* toOpenApiPath('/items', '') // → '/items'
|
|
254
|
+
* ```
|
|
255
|
+
*/
|
|
256
|
+
function toOpenApiPath(pattern, basePath) {
|
|
257
|
+
const converted = pattern.replace(/:([A-Za-z_][A-Za-z0-9_]*)/g, '{$1}');
|
|
258
|
+
if (!basePath)
|
|
259
|
+
return converted;
|
|
260
|
+
const base = basePath.replace(/\/+$/, '');
|
|
261
|
+
const path = converted.replace(/^\/+/, '/');
|
|
262
|
+
return base + (path.startsWith('/') ? path : `/${path}`);
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Extract named path parameters from an Express-style pattern as
|
|
266
|
+
* {@link ParameterObject} entries with `in: 'path'` and `required: true`.
|
|
267
|
+
*
|
|
268
|
+
* Parameters already listed in `annotated` (by name) are skipped to avoid
|
|
269
|
+
* duplicates when the caller has provided explicit metadata for them.
|
|
270
|
+
*
|
|
271
|
+
* @param pattern - Express-style route pattern (e.g. `/items/:id`).
|
|
272
|
+
* @param annotated - Explicit parameters provided by the caller (may be empty).
|
|
273
|
+
*/
|
|
274
|
+
function extractPathParams(pattern, annotated) {
|
|
275
|
+
const annotatedNames = new Set(annotated.map(p => p.name));
|
|
276
|
+
const params = [];
|
|
277
|
+
for (const match of pattern.matchAll(/:([A-Za-z_][A-Za-z0-9_]*)/g)) {
|
|
278
|
+
const name = match[1];
|
|
279
|
+
if (!annotatedNames.has(name)) {
|
|
280
|
+
params.push({
|
|
281
|
+
name,
|
|
282
|
+
in: 'path',
|
|
283
|
+
required: true,
|
|
284
|
+
schema: { type: 'string' },
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
return params;
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* Build an `operationId` string from an HTTP verb and a path pattern.
|
|
292
|
+
*
|
|
293
|
+
* The algorithm:
|
|
294
|
+
* 1. Lowercase the verb (e.g. `'GET'` → `'get'`).
|
|
295
|
+
* 2. Split the pattern on `/` and `:` (and `{` / `}` for pre-translated paths).
|
|
296
|
+
* 3. CamelCase each non-empty segment (first-letter uppercase).
|
|
297
|
+
* 4. Prefix each path-param segment with `'By'`.
|
|
298
|
+
* 5. Join everything into a single camelCase string.
|
|
299
|
+
*
|
|
300
|
+
* @example
|
|
301
|
+
* ```ts
|
|
302
|
+
* buildOperationId('GET', '/items') // → 'getItems'
|
|
303
|
+
* buildOperationId('GET', '/items/:id') // → 'getItemsById'
|
|
304
|
+
* buildOperationId('DELETE', '/a/:b/:c') // → 'deleteAByBByC'
|
|
305
|
+
* ```
|
|
306
|
+
*/
|
|
307
|
+
function buildOperationId(verb, pattern) {
|
|
308
|
+
const parts = pattern.split(/[/:{} ]+/).filter(Boolean);
|
|
309
|
+
let result = verb.toLowerCase();
|
|
310
|
+
for (const part of parts) {
|
|
311
|
+
const isParam = pattern.includes(`:${part}`) || pattern.includes(`{${part}}`);
|
|
312
|
+
const pascal = part.charAt(0).toUpperCase() + part.slice(1);
|
|
313
|
+
result += isParam ? `By${pascal}` : pascal;
|
|
314
|
+
}
|
|
315
|
+
return result;
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* Build the default responses for an operation when none are provided by the
|
|
319
|
+
* caller.
|
|
320
|
+
*
|
|
321
|
+
* - `POST` → `201 No Content` (successful write with no response body).
|
|
322
|
+
* - All other verbs → `200 OK` with a generic JSON response.
|
|
323
|
+
* - `500` → always added as a reference to the built-in `ApiError` component.
|
|
324
|
+
*/
|
|
325
|
+
function buildDefaultResponses(verb) {
|
|
326
|
+
const ok = verb === 'POST'
|
|
327
|
+
? { description: 'Created' }
|
|
328
|
+
: { description: 'OK', content: { 'application/json': {} } };
|
|
329
|
+
return {
|
|
330
|
+
[verb === 'POST' ? '201' : '200']: ok,
|
|
331
|
+
'500': { $ref: '#/components/responses/ApiError' },
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
/**
|
|
335
|
+
* Convert caller-provided `responses` metadata into the OpenAPI responses
|
|
336
|
+
* map, injecting the built-in `ApiError` reference for `500` unless the
|
|
337
|
+
* caller has explicitly provided one.
|
|
338
|
+
*/
|
|
339
|
+
function buildAnnotatedResponses(responses) {
|
|
340
|
+
const result = { ...responses };
|
|
341
|
+
if (!result['500']) {
|
|
342
|
+
result['500'] = { $ref: '#/components/responses/ApiError' };
|
|
343
|
+
}
|
|
344
|
+
return result;
|
|
345
|
+
}
|
|
346
|
+
// ---------------------------------------------------------------------------
|
|
347
|
+
// Core spec generator
|
|
348
|
+
// ---------------------------------------------------------------------------
|
|
349
|
+
/** The five HTTP verbs supported by `apiBuilder`. */
|
|
350
|
+
const VERBS = ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'];
|
|
351
|
+
/**
|
|
352
|
+
* Generate an OpenAPI 3.1.0 document from a {@link ServiceDefinition}.
|
|
353
|
+
*
|
|
354
|
+
* Route handlers that have been annotated with {@link describe} contribute
|
|
355
|
+
* rich operation metadata (summary, description, parameters, requestBody,
|
|
356
|
+
* responses). Unannotated handlers receive sensible defaults automatically.
|
|
357
|
+
*
|
|
358
|
+
* The generated document always includes:
|
|
359
|
+
* - An `ApiError` schema (shape: `{ status?, message?, data? }`) in
|
|
360
|
+
* `components.schemas`.
|
|
361
|
+
* - An `ApiError` response (`500` reference) in `components.responses`.
|
|
362
|
+
*
|
|
363
|
+
* Caller-supplied `opts.schemas` and service-level `openapi.schemas` are
|
|
364
|
+
* deep-merged on top of the built-in components (caller schemas take
|
|
365
|
+
* precedence over service schemas, which take precedence over built-ins).
|
|
366
|
+
*
|
|
367
|
+
* @param service - The service definition to document.
|
|
368
|
+
* @param opts - Top-level spec options (title, version, basePath, …).
|
|
369
|
+
* @returns A fully-formed OpenAPI 3.1.0 document object.
|
|
370
|
+
*
|
|
371
|
+
* @example
|
|
372
|
+
* ```ts
|
|
373
|
+
* const spec = openApiSpec(todoDefinition, {
|
|
374
|
+
* title: 'Todo API',
|
|
375
|
+
* version: '1.0.0',
|
|
376
|
+
* basePath: '/api',
|
|
377
|
+
* });
|
|
378
|
+
*
|
|
379
|
+
* app.get('/openapi.json', (_req, res) => {
|
|
380
|
+
* res.json(spec);
|
|
381
|
+
* });
|
|
382
|
+
* ```
|
|
383
|
+
*/
|
|
384
|
+
export function openApiSpec(service, opts) {
|
|
385
|
+
const basePath = opts.basePath ?? '';
|
|
386
|
+
const svcMeta = service.openapi;
|
|
387
|
+
const defaultTag = svcMeta?.tag;
|
|
388
|
+
// ── Components ──────────────────────────────────────────────────────────────
|
|
389
|
+
const builtinSchemas = {
|
|
390
|
+
ApiError: {
|
|
391
|
+
type: 'object',
|
|
392
|
+
properties: {
|
|
393
|
+
status: { type: 'integer', description: 'HTTP status code' },
|
|
394
|
+
message: { type: 'string', description: 'Human-readable error message' },
|
|
395
|
+
data: { description: 'Structured error payload (overrides message when present)' },
|
|
396
|
+
},
|
|
397
|
+
},
|
|
398
|
+
};
|
|
399
|
+
const builtinResponses = {
|
|
400
|
+
ApiError: {
|
|
401
|
+
description: 'API error response',
|
|
402
|
+
content: {
|
|
403
|
+
'application/json': { schema: { $ref: '#/components/schemas/ApiError' } },
|
|
404
|
+
},
|
|
405
|
+
},
|
|
406
|
+
};
|
|
407
|
+
// Merge schemas: built-ins ← service-level ← caller-level (last wins).
|
|
408
|
+
const schemas = {
|
|
409
|
+
...builtinSchemas,
|
|
410
|
+
...svcMeta?.schemas,
|
|
411
|
+
...opts.schemas,
|
|
412
|
+
};
|
|
413
|
+
const responses = {
|
|
414
|
+
...builtinResponses,
|
|
415
|
+
...svcMeta?.responses,
|
|
416
|
+
};
|
|
417
|
+
// ── Tags ─────────────────────────────────────────────────────────────────────
|
|
418
|
+
const tags = [];
|
|
419
|
+
if (defaultTag) {
|
|
420
|
+
tags.push({ name: defaultTag, description: svcMeta?.tagDescription });
|
|
421
|
+
}
|
|
422
|
+
// ── Paths ────────────────────────────────────────────────────────────────────
|
|
423
|
+
const paths = {};
|
|
424
|
+
for (const verb of VERBS) {
|
|
425
|
+
const routeMap = service[verb];
|
|
426
|
+
if (!routeMap)
|
|
427
|
+
continue;
|
|
428
|
+
for (const [pattern, handler] of Object.entries(routeMap)) {
|
|
429
|
+
const openApiPath = toOpenApiPath(pattern, basePath);
|
|
430
|
+
if (!paths[openApiPath])
|
|
431
|
+
paths[openApiPath] = {};
|
|
432
|
+
// Retrieve attached metadata (if any).
|
|
433
|
+
const meta = handler[DESCRIBE_META];
|
|
434
|
+
// ── Parameters ─────────────────────────────────────────────────────────
|
|
435
|
+
const annotatedParams = meta?.parameters ?? [];
|
|
436
|
+
const inferredParams = extractPathParams(pattern, annotatedParams);
|
|
437
|
+
const parameters = [...annotatedParams, ...inferredParams];
|
|
438
|
+
// ── Responses ──────────────────────────────────────────────────────────
|
|
439
|
+
const operationResponses = meta?.responses
|
|
440
|
+
? buildAnnotatedResponses(meta.responses)
|
|
441
|
+
: buildDefaultResponses(verb);
|
|
442
|
+
// ── Operation object ───────────────────────────────────────────────────
|
|
443
|
+
const operation = {
|
|
444
|
+
operationId: meta?.operationId ?? buildOperationId(verb, pattern),
|
|
445
|
+
...(meta?.summary && { summary: meta.summary }),
|
|
446
|
+
...(meta?.description && { description: meta.description }),
|
|
447
|
+
...(meta?.deprecated && { deprecated: true }),
|
|
448
|
+
...(parameters.length > 0 && { parameters }),
|
|
449
|
+
...(meta?.requestBody && { requestBody: meta.requestBody }),
|
|
450
|
+
responses: operationResponses,
|
|
451
|
+
};
|
|
452
|
+
// Apply default tag when the operation has no explicit tags.
|
|
453
|
+
const opTags = meta?.tags ?? (defaultTag ? [defaultTag] : undefined);
|
|
454
|
+
if (opTags)
|
|
455
|
+
operation['tags'] = opTags;
|
|
456
|
+
// Carry through any vendor extensions (x-* keys).
|
|
457
|
+
if (meta) {
|
|
458
|
+
for (const [k, v] of Object.entries(meta)) {
|
|
459
|
+
if (k.startsWith('x-'))
|
|
460
|
+
operation[k] = v;
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
paths[openApiPath][verb.toLowerCase()] = operation;
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
// ── Assemble document ────────────────────────────────────────────────────────
|
|
467
|
+
const doc = {
|
|
468
|
+
openapi: '3.1.0',
|
|
469
|
+
info: {
|
|
470
|
+
title: opts.title,
|
|
471
|
+
version: opts.version,
|
|
472
|
+
...(opts.description && { description: opts.description }),
|
|
473
|
+
},
|
|
474
|
+
...(opts.servers && { servers: opts.servers }),
|
|
475
|
+
...(tags.length > 0 && { tags }),
|
|
476
|
+
paths,
|
|
477
|
+
components: { schemas, responses },
|
|
478
|
+
};
|
|
479
|
+
return doc;
|
|
480
|
+
}
|
|
481
|
+
//# sourceMappingURL=openapi.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openapi.js","sourceRoot":"","sources":["../src/openapi.ts"],"names":[],"mappings":"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"}
|