tina4-nodejs 3.13.132 → 3.13.133
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/CLAUDE.md +3 -2
- package/package.json +1 -1
- package/packages/cli/dist/bin.js +151 -117
- package/packages/core/dist/index.js +151 -117
- package/packages/orm/dist/index.js +151 -117
- package/packages/swagger/dist/index.js +150 -116
- package/packages/swagger/src/generator.ts +261 -154
|
@@ -157,16 +157,85 @@ function sanitizeSecurity(
|
|
|
157
157
|
});
|
|
158
158
|
}
|
|
159
159
|
|
|
160
|
+
/**
|
|
161
|
+
* Shared, mutable state threaded through the per-route builders. Bundled so
|
|
162
|
+
* `generate()` reads as a flat orchestrator and each helper keeps a single
|
|
163
|
+
* responsibility (parity with the Python/PHP decomposition of `generate`).
|
|
164
|
+
*/
|
|
165
|
+
interface BuildContext {
|
|
166
|
+
models: ModelDefinition[];
|
|
167
|
+
tableToSchema: Map<string, string>;
|
|
168
|
+
schemes: Record<string, Record<string, unknown>>;
|
|
169
|
+
defaultScheme: string;
|
|
170
|
+
includePrefixes: string[];
|
|
171
|
+
excludePrefixes: string[];
|
|
172
|
+
refSchemas: Set<string>;
|
|
173
|
+
usedTags: string[];
|
|
174
|
+
seenIds: Set<string>;
|
|
175
|
+
}
|
|
176
|
+
|
|
160
177
|
export function generate(
|
|
161
178
|
routes: RouteDefinition[],
|
|
162
179
|
models: ModelDefinition[] = []
|
|
163
180
|
): OpenAPISpec {
|
|
181
|
+
const schemes = resolveSecuritySchemes();
|
|
182
|
+
const spec: OpenAPISpec = {
|
|
183
|
+
openapi: resolveOpenApiVersion(),
|
|
184
|
+
info: buildInfo(),
|
|
185
|
+
servers: resolveServers(),
|
|
186
|
+
paths: {},
|
|
187
|
+
components: {
|
|
188
|
+
schemas: {},
|
|
189
|
+
// Configurable security schemes (v3.13.42): bearerFormat via env, optional
|
|
190
|
+
// apiKey scheme, plus any programmatically-registered schemes (which may
|
|
191
|
+
// override bearerAuth — e.g. an oauth2 scheme with scopes).
|
|
192
|
+
securitySchemes: schemes,
|
|
193
|
+
},
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
// Generate schemas from models, keyed by the model CLASS name ('Item'), which
|
|
197
|
+
// is the type name a generated client wants — never the tableName ('items').
|
|
198
|
+
// `tableToSchema` maps a path-inferred tableName back to that schema key so a
|
|
199
|
+
// POST/PUT request body $ref resolves to the same 'Item' entry.
|
|
200
|
+
const tableToSchema = new Map<string, string>();
|
|
201
|
+
buildComponentSchemas(models, spec, tableToSchema);
|
|
202
|
+
|
|
203
|
+
const ctx: BuildContext = {
|
|
204
|
+
models,
|
|
205
|
+
tableToSchema,
|
|
206
|
+
schemes,
|
|
207
|
+
// Default scheme secured routes use when no explicit meta.security is set.
|
|
208
|
+
defaultScheme: process.env.TINA4_SWAGGER_DEFAULT_SCHEME ?? "bearerAuth",
|
|
209
|
+
// Path filters (comma-separated raw-path prefixes).
|
|
210
|
+
includePrefixes: csv(process.env.TINA4_SWAGGER_INCLUDE),
|
|
211
|
+
excludePrefixes: csv(process.env.TINA4_SWAGGER_EXCLUDE),
|
|
212
|
+
// Reusable custom schemas referenced by routes via meta.requestSchema/responseSchemas.
|
|
213
|
+
refSchemas: new Set<string>(),
|
|
214
|
+
usedTags: [],
|
|
215
|
+
seenIds: new Set<string>(),
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
// Generate paths from routes
|
|
219
|
+
for (const route of routes) {
|
|
220
|
+
buildOperation(route, spec, ctx);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
buildRefSchemas(spec, ctx.refSchemas);
|
|
224
|
+
buildTags(spec, ctx.usedTags);
|
|
225
|
+
|
|
226
|
+
return spec;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* The `info` block: title/version/description, plus optional contact and
|
|
231
|
+
* license. The app's version defaults to 1.0.0 — NOT the framework's (Node
|
|
232
|
+
* shipped 0.0.1) — and description to the empty string, not a canned sentence;
|
|
233
|
+
* both are the settled cross-framework defaults (parity with the Python master),
|
|
234
|
+
* with TINA4_SWAGGER_VERSION / _DESCRIPTION still overriding.
|
|
235
|
+
*/
|
|
236
|
+
function buildInfo(): OpenAPISpecInfo {
|
|
164
237
|
const info: OpenAPISpecInfo = {
|
|
165
238
|
title: process.env.TINA4_SWAGGER_TITLE ?? "Tina4 API",
|
|
166
|
-
// The app's version, defaulting to 1.0.0 — NOT the framework's (Node shipped
|
|
167
|
-
// 0.0.1). description defaults to the empty string, not a canned sentence.
|
|
168
|
-
// Both are the settled cross-framework defaults (parity with the Python
|
|
169
|
-
// master); TINA4_SWAGGER_VERSION / _DESCRIPTION still override.
|
|
170
239
|
version: process.env.TINA4_SWAGGER_VERSION ?? "1.0.0",
|
|
171
240
|
description: process.env.TINA4_SWAGGER_DESCRIPTION ?? "",
|
|
172
241
|
};
|
|
@@ -190,183 +259,220 @@ export function generate(
|
|
|
190
259
|
info.license = url ? { name, url } : { name };
|
|
191
260
|
}
|
|
192
261
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
openapi: resolveOpenApiVersion(),
|
|
196
|
-
info,
|
|
197
|
-
servers: resolveServers(),
|
|
198
|
-
paths: {},
|
|
199
|
-
components: {
|
|
200
|
-
schemas: {},
|
|
201
|
-
// Configurable security schemes (v3.13.42): bearerFormat via env, optional
|
|
202
|
-
// apiKey scheme, plus any programmatically-registered schemes (which may
|
|
203
|
-
// override bearerAuth — e.g. an oauth2 scheme with scopes).
|
|
204
|
-
securitySchemes: schemes,
|
|
205
|
-
},
|
|
206
|
-
};
|
|
207
|
-
|
|
208
|
-
// Default scheme secured routes use when no explicit meta.security is set.
|
|
209
|
-
const defaultScheme = process.env.TINA4_SWAGGER_DEFAULT_SCHEME ?? "bearerAuth";
|
|
210
|
-
|
|
211
|
-
// Path filters (comma-separated raw-path prefixes).
|
|
212
|
-
const includePrefixes = csv(process.env.TINA4_SWAGGER_INCLUDE);
|
|
213
|
-
const excludePrefixes = csv(process.env.TINA4_SWAGGER_EXCLUDE);
|
|
214
|
-
|
|
215
|
-
// Reusable custom schemas referenced by routes via meta.requestSchema/responseSchemas.
|
|
216
|
-
const refSchemas = new Set<string>();
|
|
262
|
+
return info;
|
|
263
|
+
}
|
|
217
264
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
265
|
+
/**
|
|
266
|
+
* Populate `components.schemas` from ORM models (keyed by the class name) and
|
|
267
|
+
* record the tableName -> schema-key map used to resolve request-body $refs.
|
|
268
|
+
*/
|
|
269
|
+
function buildComponentSchemas(
|
|
270
|
+
models: ModelDefinition[],
|
|
271
|
+
spec: OpenAPISpec,
|
|
272
|
+
tableToSchema: Map<string, string>
|
|
273
|
+
): void {
|
|
223
274
|
for (const model of models) {
|
|
224
275
|
const schemaKey = schemaNameForModel(model);
|
|
225
276
|
tableToSchema.set(model.tableName, schemaKey);
|
|
226
277
|
spec.components!.schemas![schemaKey] = modelToSchema(model);
|
|
227
278
|
}
|
|
279
|
+
}
|
|
228
280
|
|
|
229
|
-
|
|
230
|
-
|
|
281
|
+
/**
|
|
282
|
+
* Build ONE path operation for a route and attach it to `spec.paths`, honouring
|
|
283
|
+
* the include/exclude filter and delegating each part of the operation to a
|
|
284
|
+
* focused sub-builder (parameters, request body, response schemas, security).
|
|
285
|
+
*/
|
|
286
|
+
function buildOperation(route: RouteDefinition, spec: OpenAPISpec, ctx: BuildContext): void {
|
|
287
|
+
if (!isIncludedPath(route.pattern, ctx.includePrefixes, ctx.excludePrefixes)) return;
|
|
288
|
+
const openApiPath = patternToOpenAPI(route.pattern);
|
|
289
|
+
const method = route.method.toLowerCase();
|
|
231
290
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
const openApiPath = patternToOpenAPI(route.pattern);
|
|
236
|
-
const method = route.method.toLowerCase();
|
|
291
|
+
if (!spec.paths[openApiPath]) {
|
|
292
|
+
spec.paths[openApiPath] = {};
|
|
293
|
+
}
|
|
237
294
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
295
|
+
const tags = route.meta?.tags ?? inferTags(route.pattern);
|
|
296
|
+
for (const t of tags) {
|
|
297
|
+
if (!ctx.usedTags.includes(t)) ctx.usedTags.push(t);
|
|
298
|
+
}
|
|
241
299
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
300
|
+
const operation: Record<string, unknown> = {
|
|
301
|
+
operationId: uniqueOperationId(method, openApiPath, ctx.seenIds),
|
|
302
|
+
summary: route.meta?.summary ?? `${route.method} ${route.pattern}`,
|
|
303
|
+
tags,
|
|
304
|
+
responses: route.meta?.responses ?? {
|
|
305
|
+
"200": { description: "Successful response" },
|
|
306
|
+
},
|
|
307
|
+
};
|
|
246
308
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
summary: route.meta?.summary ?? `${route.method} ${route.pattern}`,
|
|
250
|
-
tags,
|
|
251
|
-
responses: route.meta?.responses ?? {
|
|
252
|
-
"200": { description: "Successful response" },
|
|
253
|
-
},
|
|
254
|
-
};
|
|
309
|
+
if (route.meta?.description) operation.description = route.meta.description;
|
|
310
|
+
if (route.meta?.deprecated) operation.deprecated = true;
|
|
255
311
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
// Add path parameters. Each typed token maps to its JSON Schema fragment
|
|
260
|
-
// (int -> integer, uuid -> string+format, slug -> string+pattern, ...) via
|
|
261
|
-
// PARAM_TYPE_SCHEMA below, mirroring the Python master's _PARAM_TYPE_SCHEMA
|
|
262
|
-
// and the router's accepted token set. An unknown token degrades to string
|
|
263
|
-
// rather than dropping the parameter, and the token never reaches the key.
|
|
264
|
-
const pathParams = extractPathParams(route.pattern);
|
|
265
|
-
if (pathParams.length > 0) {
|
|
266
|
-
operation.parameters = pathParams.map(({ name, schema }) => ({
|
|
267
|
-
name,
|
|
268
|
-
in: "path",
|
|
269
|
-
required: true,
|
|
270
|
-
schema,
|
|
271
|
-
}));
|
|
272
|
-
}
|
|
312
|
+
const parameters = operationParameters(route, method, ctx.models);
|
|
313
|
+
if (parameters.length > 0) operation.parameters = parameters;
|
|
273
314
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
315
|
+
operationRequestBody(route, method, operation, ctx);
|
|
316
|
+
operationResponseSchemas(route, operation, ctx.refSchemas);
|
|
317
|
+
operationSecurity(route, method, operation, ctx.schemes, ctx.defaultScheme);
|
|
318
|
+
|
|
319
|
+
spec.paths[openApiPath][method] = operation;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* The operation's parameters: path parameters (each typed token mapped to its
|
|
324
|
+
* JSON Schema fragment via PARAM_TYPE_SCHEMA — int -> integer, uuid ->
|
|
325
|
+
* string+format, slug -> string+pattern, ...; an unknown token degrades to
|
|
326
|
+
* string rather than dropping the parameter), followed by the page/limit/sort
|
|
327
|
+
* query parameters a GET list endpoint over a known model gets.
|
|
328
|
+
*/
|
|
329
|
+
function operationParameters(
|
|
330
|
+
route: RouteDefinition,
|
|
331
|
+
method: string,
|
|
332
|
+
models: ModelDefinition[]
|
|
333
|
+
): Array<Record<string, unknown>> {
|
|
334
|
+
let parameters: Array<Record<string, unknown>> = [];
|
|
335
|
+
|
|
336
|
+
const pathParams = extractPathParams(route.pattern);
|
|
337
|
+
if (pathParams.length > 0) {
|
|
338
|
+
parameters = pathParams.map(({ name, schema }) => ({
|
|
339
|
+
name,
|
|
340
|
+
in: "path",
|
|
341
|
+
required: true,
|
|
342
|
+
schema,
|
|
343
|
+
}));
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// Add query parameters for GET list endpoints
|
|
347
|
+
if (method === "get" && !route.pattern.includes("[id]") && !route.pattern.includes("[...")) {
|
|
348
|
+
const modelName = inferModelFromPath(route.pattern);
|
|
349
|
+
if (modelName && models.some((m) => m.tableName === modelName)) {
|
|
350
|
+
parameters = [
|
|
351
|
+
...parameters,
|
|
352
|
+
{ name: "page", in: "query", schema: { type: "integer", default: 1 } },
|
|
353
|
+
{ name: "limit", in: "query", schema: { type: "integer", default: 20 } },
|
|
354
|
+
{ name: "sort", in: "query", schema: { type: "string" }, description: "Sort fields (prefix with - for descending)" },
|
|
355
|
+
];
|
|
285
356
|
}
|
|
357
|
+
}
|
|
286
358
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
359
|
+
return parameters;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/** A media-type object: the schema, plus meta.example only when one was given.
|
|
363
|
+
* Key order (schema, then example) matches the emitted document. */
|
|
364
|
+
function mediaWithExample(schema: unknown, example: unknown): Record<string, unknown> {
|
|
365
|
+
const media: Record<string, unknown> = { schema };
|
|
366
|
+
if (example !== undefined) media.example = example;
|
|
367
|
+
return media;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* Attach the request body (and, for a path-inferred model write, its 200
|
|
372
|
+
* response schema). A registered custom schema $ref (meta.requestSchema) wins,
|
|
373
|
+
* else the inferred-from-model body (POST/PUT to a resource), else an
|
|
374
|
+
* example-only body.
|
|
375
|
+
*/
|
|
376
|
+
function operationRequestBody(
|
|
377
|
+
route: RouteDefinition,
|
|
378
|
+
method: string,
|
|
379
|
+
operation: Record<string, unknown>,
|
|
380
|
+
ctx: BuildContext
|
|
381
|
+
): void {
|
|
382
|
+
const reqSchemaRef = parseRequestSchema(route.meta?.requestSchema);
|
|
383
|
+
if (reqSchemaRef && (method === "post" || method === "put" || method === "patch")) {
|
|
384
|
+
ctx.refSchemas.add(reqSchemaRef.name);
|
|
385
|
+
const media = mediaWithExample({ $ref: `#/components/schemas/${reqSchemaRef.name}` }, route.meta?.example);
|
|
386
|
+
operation.requestBody = {
|
|
387
|
+
content: { [reqSchemaRef.contentType]: media },
|
|
388
|
+
};
|
|
389
|
+
} else if (method === "post" || method === "put") {
|
|
390
|
+
const modelName = inferModelFromPath(route.pattern);
|
|
391
|
+
const schemaKey = modelName ? ctx.tableToSchema.get(modelName) : undefined;
|
|
392
|
+
if (schemaKey) {
|
|
393
|
+
const sref = `#/components/schemas/${schemaKey}`;
|
|
297
394
|
operation.requestBody = {
|
|
298
|
-
|
|
395
|
+
required: true,
|
|
396
|
+
content: { "application/json": mediaWithExample({ $ref: sref }, route.meta?.example) },
|
|
299
397
|
};
|
|
300
|
-
} else if (method === "post" || method === "put") {
|
|
301
|
-
const modelName = inferModelFromPath(route.pattern);
|
|
302
|
-
const schemaKey = modelName ? tableToSchema.get(modelName) : undefined;
|
|
303
|
-
if (schemaKey) {
|
|
304
|
-
const sref = `#/components/schemas/${schemaKey}`;
|
|
305
|
-
const media: Record<string, unknown> = { schema: { $ref: sref } };
|
|
306
|
-
if (route.meta?.example !== undefined) media.example = route.meta.example;
|
|
307
|
-
operation.requestBody = {
|
|
308
|
-
required: true,
|
|
309
|
-
content: { "application/json": media },
|
|
310
|
-
};
|
|
311
398
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
};
|
|
322
|
-
}
|
|
323
|
-
} else if (route.meta?.example !== undefined) {
|
|
324
|
-
// Non-model body with an explicit example.
|
|
325
|
-
operation.requestBody = {
|
|
326
|
-
content: { "application/json": { schema: inferSchema(route.meta.example), example: route.meta.example } },
|
|
399
|
+
// Response documents 200 with the resource schema — parity with the
|
|
400
|
+
// Python master, which emits ONLY 200 for a model write. The old code
|
|
401
|
+
// stamped an unconditional 422 and a 201 the generator has no way to know
|
|
402
|
+
// a given route returns; both were fiction on a path-inferred write. A
|
|
403
|
+
// route that genuinely answers another code declares it via
|
|
404
|
+
// meta.responses, which is honoured above and never clobbered here.
|
|
405
|
+
if (route.meta?.responses === undefined) {
|
|
406
|
+
operation.responses = {
|
|
407
|
+
"200": { description: "Successful response", content: { "application/json": { schema: { $ref: sref } } } },
|
|
327
408
|
};
|
|
328
409
|
}
|
|
410
|
+
} else if (route.meta?.example !== undefined) {
|
|
411
|
+
// Non-model body with an explicit example.
|
|
412
|
+
operation.requestBody = {
|
|
413
|
+
content: { "application/json": { schema: inferSchema(route.meta.example), example: route.meta.example } },
|
|
414
|
+
};
|
|
329
415
|
}
|
|
416
|
+
}
|
|
417
|
+
}
|
|
330
418
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
419
|
+
/**
|
|
420
|
+
* Registered response schemas ($ref) — explicit and authoritative, keyed by
|
|
421
|
+
* status; each maps to the resource, or an array of it when marked as a list.
|
|
422
|
+
*/
|
|
423
|
+
function operationResponseSchemas(
|
|
424
|
+
route: RouteDefinition,
|
|
425
|
+
operation: Record<string, unknown>,
|
|
426
|
+
refSchemas: Set<string>
|
|
427
|
+
): void {
|
|
428
|
+
const respSchemas = parseResponseSchemas(route.meta?.responseSchemas);
|
|
429
|
+
if (respSchemas.length > 0) {
|
|
430
|
+
const responses = operation.responses as Record<string, unknown>;
|
|
431
|
+
for (const { status, name, isList } of respSchemas) {
|
|
432
|
+
refSchemas.add(name);
|
|
433
|
+
const sref = `#/components/schemas/${name}`;
|
|
434
|
+
const schema = isList ? { type: "array", items: { $ref: sref } } : { $ref: sref };
|
|
435
|
+
responses[status] = {
|
|
436
|
+
description: status.startsWith("2") ? "Successful response" : "Response",
|
|
437
|
+
content: { "application/json": { schema } },
|
|
438
|
+
};
|
|
344
439
|
}
|
|
440
|
+
}
|
|
441
|
+
}
|
|
345
442
|
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
443
|
+
/**
|
|
444
|
+
* Per-route security (v3.13.42). Explicit meta.security wins (empty list =
|
|
445
|
+
* explicitly public); otherwise a secured route gets the default scheme (plus
|
|
446
|
+
* ssoSession when bearer + SSO is configured). A secured op also documents a
|
|
447
|
+
* 401. Scopes are kept valid (only oauth2/openIdConnect carry them).
|
|
448
|
+
*/
|
|
449
|
+
function operationSecurity(
|
|
450
|
+
route: RouteDefinition,
|
|
451
|
+
method: string,
|
|
452
|
+
operation: Record<string, unknown>,
|
|
453
|
+
schemes: Record<string, Record<string, unknown>>,
|
|
454
|
+
defaultScheme: string
|
|
455
|
+
): void {
|
|
456
|
+
const hasExplicitSecurity =
|
|
457
|
+
route.meta?.security !== undefined || (route.meta?.scopes !== undefined && route.meta.scopes.length > 0);
|
|
458
|
+
if (hasExplicitSecurity) {
|
|
459
|
+
const normalized = normalizeSecurity(route.meta?.security, route.meta?.scopes);
|
|
460
|
+
operation.security = normalized.length > 0 ? sanitizeSecurity(normalized, schemes) : [];
|
|
461
|
+
if (normalized.length > 0) {
|
|
362
462
|
const responses = operation.responses as Record<string, unknown>;
|
|
363
463
|
if (!responses["401"]) responses["401"] = { description: "Unauthorized" };
|
|
364
464
|
}
|
|
365
|
-
|
|
366
|
-
|
|
465
|
+
} else if (routeRequiresAuth(route, method)) {
|
|
466
|
+
const requirements = [{ [defaultScheme]: [] }];
|
|
467
|
+
if (defaultScheme === "bearerAuth" && schemes.ssoSession) requirements.push({ ssoSession: [] });
|
|
468
|
+
operation.security = sanitizeSecurity(requirements, schemes);
|
|
469
|
+
const responses = operation.responses as Record<string, unknown>;
|
|
470
|
+
if (!responses["401"]) responses["401"] = { description: "Unauthorized" };
|
|
367
471
|
}
|
|
472
|
+
}
|
|
368
473
|
|
|
369
|
-
|
|
474
|
+
/** Registered component schemas referenced via meta.requestSchema/responseSchemas. */
|
|
475
|
+
function buildRefSchemas(spec: OpenAPISpec, refSchemas: Set<string>): void {
|
|
370
476
|
if (refSchemas.size > 0) {
|
|
371
477
|
const schemas = spec.components!.schemas!;
|
|
372
478
|
for (const name of refSchemas) {
|
|
@@ -375,12 +481,13 @@ export function generate(
|
|
|
375
481
|
}
|
|
376
482
|
}
|
|
377
483
|
}
|
|
484
|
+
}
|
|
378
485
|
|
|
486
|
+
/** Top-level tags[] from the tags collected across operations (registration order). */
|
|
487
|
+
function buildTags(spec: OpenAPISpec, usedTags: string[]): void {
|
|
379
488
|
if (usedTags.length > 0) {
|
|
380
489
|
spec.tags = usedTags.map((name) => ({ name }));
|
|
381
490
|
}
|
|
382
|
-
|
|
383
|
-
return spec;
|
|
384
491
|
}
|
|
385
492
|
|
|
386
493
|
function routeRequiresAuth(route: RouteDefinition, method: string): boolean {
|