@moostjs/swagger 0.6.24 → 0.6.26
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/README.md +10 -6
- package/dist/index.cjs +79 -62
- package/dist/index.d.ts +85 -3
- package/dist/index.mjs +78 -62
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -5,9 +5,11 @@ Swagger/OpenAPI integration for [Moost](https://moost.org). Automatically genera
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
npm install @moostjs/swagger
|
|
8
|
+
npm install @moostjs/swagger
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
+
The Swagger UI assets ship with the package (`swagger-ui-dist` is a regular dependency). `moost`, `@moostjs/event-http`, and `@wooksjs/http-static` are peer dependencies.
|
|
12
|
+
|
|
11
13
|
## Quick Start
|
|
12
14
|
|
|
13
15
|
```ts
|
|
@@ -18,9 +20,10 @@ import { SwaggerController } from '@moostjs/swagger'
|
|
|
18
20
|
const app = new Moost()
|
|
19
21
|
const http = new MoostHttp()
|
|
20
22
|
|
|
21
|
-
app.adapter(http)
|
|
22
|
-
|
|
23
|
-
|
|
23
|
+
app.adapter(http)
|
|
24
|
+
app.registerControllers(SwaggerController)
|
|
25
|
+
await app.init()
|
|
26
|
+
await http.listen(3000)
|
|
24
27
|
// Swagger UI available at http://localhost:3000/api-docs/
|
|
25
28
|
// JSON spec at http://localhost:3000/api-docs/spec.json
|
|
26
29
|
// YAML spec at http://localhost:3000/api-docs/spec.yaml
|
|
@@ -33,7 +36,7 @@ http.listen(3000)
|
|
|
33
36
|
- `@SwaggerResponse(opts)` / `@SwaggerResponse(code, opts)` — Define response schemas. Supports optional `description` to document the status code; when omitted, a standard HTTP reason phrase is used automatically.
|
|
34
37
|
- `@SwaggerRequestBody(opts)` — Define request body schema.
|
|
35
38
|
- `@SwaggerParam(opts)` — Define a parameter.
|
|
36
|
-
- `@SwaggerExample(example)` — Attach an example value.
|
|
39
|
+
- `@SwaggerExample(example)` — Attach an example value to a DTO class (its component schema).
|
|
37
40
|
- `@SwaggerOperationId(id)` — Override the auto-generated operationId. Falls back to `@Id()` from moost core, then to the auto-generated value.
|
|
38
41
|
- `@SwaggerDeprecated()` — Mark a controller or handler as deprecated.
|
|
39
42
|
- `@SwaggerExclude()` — Exclude a controller or handler from the spec.
|
|
@@ -52,7 +55,8 @@ When no `@SwaggerResponse` is declared for the success status code, the generato
|
|
|
52
55
|
Security schemes are auto-discovered from `@Authenticate()` guards (provided by `@moostjs/event-http`). All four OpenAPI transport types are supported: bearer, basic, apiKey, and cookie.
|
|
53
56
|
|
|
54
57
|
```ts
|
|
55
|
-
import {
|
|
58
|
+
import { Controller, Param } from 'moost'
|
|
59
|
+
import { Authenticate, defineAuthGuard, Get } from '@moostjs/event-http'
|
|
56
60
|
import { SwaggerPublic } from '@moostjs/swagger'
|
|
57
61
|
|
|
58
62
|
const jwtGuard = defineAuthGuard({ bearer: { format: 'JWT' } }, (transports) => {
|
package/dist/index.cjs
CHANGED
|
@@ -160,55 +160,6 @@ function SwaggerLink(codeOrName, nameOrOptions, maybeOptions) {
|
|
|
160
160
|
return getSwaggerMate().decorate("swaggerCallbacks", config, true);
|
|
161
161
|
}
|
|
162
162
|
|
|
163
|
-
//#endregion
|
|
164
|
-
//#region packages/swagger/src/json-to-yaml.ts
|
|
165
|
-
const YAML_SPECIAL = /^[\s#!&*|>'{}[\],?:@`-]|[:#]\s|[\n\r]|\s$/;
|
|
166
|
-
function quoteString(str) {
|
|
167
|
-
if (str === "") return "''";
|
|
168
|
-
if (YAML_SPECIAL.test(str) || str === "true" || str === "false" || str === "null") return JSON.stringify(str);
|
|
169
|
-
const num = Number(str);
|
|
170
|
-
if (str.length > 0 && !Number.isNaN(num) && String(num) === str) return JSON.stringify(str);
|
|
171
|
-
return str;
|
|
172
|
-
}
|
|
173
|
-
function serializeValue(value, indent) {
|
|
174
|
-
if (value === null || value === void 0) return "null";
|
|
175
|
-
if (typeof value === "boolean") return value ? "true" : "false";
|
|
176
|
-
if (typeof value === "number") return Number.isFinite(value) ? String(value) : "null";
|
|
177
|
-
if (typeof value === "string") return quoteString(value);
|
|
178
|
-
const pad = " ".repeat(indent);
|
|
179
|
-
const childPad = " ".repeat(indent + 1);
|
|
180
|
-
if (Array.isArray(value)) {
|
|
181
|
-
if (value.length === 0) return "[]";
|
|
182
|
-
const lines = [];
|
|
183
|
-
for (const item of value) if (isObject(item) || Array.isArray(item)) {
|
|
184
|
-
const nested = serializeValue(item, indent + 1);
|
|
185
|
-
lines.push(`${pad}- ${nested.slice(childPad.length)}`);
|
|
186
|
-
} else lines.push(`${pad}- ${serializeValue(item, 0)}`);
|
|
187
|
-
return lines.join("\n");
|
|
188
|
-
}
|
|
189
|
-
if (isObject(value)) {
|
|
190
|
-
const entries = Object.entries(value);
|
|
191
|
-
if (entries.length === 0) return "{}";
|
|
192
|
-
const lines = [];
|
|
193
|
-
for (const [key, val] of entries) {
|
|
194
|
-
const yamlKey = quoteString(key);
|
|
195
|
-
if (isObject(val) || Array.isArray(val)) {
|
|
196
|
-
const nested = serializeValue(val, indent + 1);
|
|
197
|
-
if (nested === "[]" || nested === "{}") lines.push(`${pad}${yamlKey}: ${nested}`);
|
|
198
|
-
else lines.push(`${pad}${yamlKey}:\n${nested}`);
|
|
199
|
-
} else lines.push(`${pad}${yamlKey}: ${serializeValue(val, 0)}`);
|
|
200
|
-
}
|
|
201
|
-
return lines.join("\n");
|
|
202
|
-
}
|
|
203
|
-
return String(value);
|
|
204
|
-
}
|
|
205
|
-
function isObject(value) {
|
|
206
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
207
|
-
}
|
|
208
|
-
function jsonToYaml(value) {
|
|
209
|
-
return `${serializeValue(value, 0)}\n`;
|
|
210
|
-
}
|
|
211
|
-
|
|
212
163
|
//#endregion
|
|
213
164
|
//#region packages/swagger/src/mapping.ts
|
|
214
165
|
const globalSchemas = {};
|
|
@@ -663,12 +614,13 @@ function ensureComponentName(typeRef, schema, suggestedName) {
|
|
|
663
614
|
* throughout the schema tree to `#/components/schemas/X`.
|
|
664
615
|
* Removes the `$defs` property from the schema after hoisting.
|
|
665
616
|
*/ function hoistDefs(schema) {
|
|
666
|
-
if (
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
617
|
+
if (schema.$defs) {
|
|
618
|
+
for (const [name, def] of Object.entries(schema.$defs)) if (!globalSchemas[name]) {
|
|
619
|
+
globalSchemas[name] = cloneSchema(def);
|
|
620
|
+
hoistDefs(globalSchemas[name]);
|
|
621
|
+
}
|
|
622
|
+
delete schema.$defs;
|
|
670
623
|
}
|
|
671
|
-
delete schema.$defs;
|
|
672
624
|
rewriteDefsRefs(schema);
|
|
673
625
|
}
|
|
674
626
|
/** Recursively rewrite `$ref: '#/$defs/X'` → `$ref: '#/components/schemas/X'` */ function rewriteDefsRefs(schema) {
|
|
@@ -871,19 +823,83 @@ function transportsToSecurityRequirement(transports) {
|
|
|
871
823
|
if (transports.cookie) requirements.push({ cookieAuth: [] });
|
|
872
824
|
return requirements;
|
|
873
825
|
}
|
|
826
|
+
/**
|
|
827
|
+
* Collects schemes and builds the security requirement for one or more
|
|
828
|
+
* `@Authenticate` guards. Multiple stacked guards are AND-combined
|
|
829
|
+
* (merged into a single requirement object via cross-product), while
|
|
830
|
+
* multiple transports within a single guard remain OR-alternatives.
|
|
831
|
+
*/ function applyAuthTransports(transports, schemes) {
|
|
832
|
+
const list = Array.isArray(transports) ? transports : [transports];
|
|
833
|
+
let combined = [{}];
|
|
834
|
+
for (const entry of list) {
|
|
835
|
+
collectSchemesFromTransports(entry, schemes);
|
|
836
|
+
const requirements = transportsToSecurityRequirement(entry);
|
|
837
|
+
if (requirements.length === 0) continue;
|
|
838
|
+
const next = [];
|
|
839
|
+
for (const base of combined) for (const requirement of requirements) next.push({
|
|
840
|
+
...base,
|
|
841
|
+
...requirement
|
|
842
|
+
});
|
|
843
|
+
combined = next;
|
|
844
|
+
}
|
|
845
|
+
return combined.filter((requirement) => Object.keys(requirement).length > 0);
|
|
846
|
+
}
|
|
874
847
|
function resolveOperationSecurity(cmeta, hmeta, schemes) {
|
|
875
848
|
if (hmeta?.swaggerPublic) return [];
|
|
876
849
|
if (hmeta?.swaggerSecurity?.length) return hmeta.swaggerSecurity;
|
|
877
|
-
if (hmeta?.authTransports)
|
|
878
|
-
collectSchemesFromTransports(hmeta.authTransports, schemes);
|
|
879
|
-
return transportsToSecurityRequirement(hmeta.authTransports);
|
|
880
|
-
}
|
|
850
|
+
if (hmeta?.authTransports) return applyAuthTransports(hmeta.authTransports, schemes);
|
|
881
851
|
if (cmeta?.swaggerPublic) return [];
|
|
882
852
|
if (cmeta?.swaggerSecurity?.length) return cmeta.swaggerSecurity;
|
|
883
|
-
if (cmeta?.authTransports)
|
|
884
|
-
|
|
885
|
-
|
|
853
|
+
if (cmeta?.authTransports) return applyAuthTransports(cmeta.authTransports, schemes);
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
//#endregion
|
|
857
|
+
//#region packages/swagger/src/json-to-yaml.ts
|
|
858
|
+
const YAML_SPECIAL = /^[\s#!&*|>'{}[\],?:@`-]|[:#]\s|[\n\r]|\s$/;
|
|
859
|
+
function quoteString(str) {
|
|
860
|
+
if (str === "") return "''";
|
|
861
|
+
if (YAML_SPECIAL.test(str) || str === "true" || str === "false" || str === "null") return JSON.stringify(str);
|
|
862
|
+
const num = Number(str);
|
|
863
|
+
if (str.length > 0 && !Number.isNaN(num) && String(num) === str) return JSON.stringify(str);
|
|
864
|
+
return str;
|
|
865
|
+
}
|
|
866
|
+
function serializeValue(value, indent) {
|
|
867
|
+
if (value === null || value === void 0) return "null";
|
|
868
|
+
if (typeof value === "boolean") return value ? "true" : "false";
|
|
869
|
+
if (typeof value === "number") return Number.isFinite(value) ? String(value) : "null";
|
|
870
|
+
if (typeof value === "string") return quoteString(value);
|
|
871
|
+
const pad = " ".repeat(indent);
|
|
872
|
+
const childPad = " ".repeat(indent + 1);
|
|
873
|
+
if (Array.isArray(value)) {
|
|
874
|
+
if (value.length === 0) return "[]";
|
|
875
|
+
const lines = [];
|
|
876
|
+
for (const item of value) if (isObject(item) || Array.isArray(item)) {
|
|
877
|
+
const nested = serializeValue(item, indent + 1);
|
|
878
|
+
lines.push(`${pad}- ${nested.slice(childPad.length)}`);
|
|
879
|
+
} else lines.push(`${pad}- ${serializeValue(item, 0)}`);
|
|
880
|
+
return lines.join("\n");
|
|
886
881
|
}
|
|
882
|
+
if (isObject(value)) {
|
|
883
|
+
const entries = Object.entries(value);
|
|
884
|
+
if (entries.length === 0) return "{}";
|
|
885
|
+
const lines = [];
|
|
886
|
+
for (const [key, val] of entries) {
|
|
887
|
+
const yamlKey = quoteString(key);
|
|
888
|
+
if (isObject(val) || Array.isArray(val)) {
|
|
889
|
+
const nested = serializeValue(val, indent + 1);
|
|
890
|
+
if (nested === "[]" || nested === "{}") lines.push(`${pad}${yamlKey}: ${nested}`);
|
|
891
|
+
else lines.push(`${pad}${yamlKey}:\n${nested}`);
|
|
892
|
+
} else lines.push(`${pad}${yamlKey}: ${serializeValue(val, 0)}`);
|
|
893
|
+
}
|
|
894
|
+
return lines.join("\n");
|
|
895
|
+
}
|
|
896
|
+
return String(value);
|
|
897
|
+
}
|
|
898
|
+
function isObject(value) {
|
|
899
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
900
|
+
}
|
|
901
|
+
function jsonToYaml(value) {
|
|
902
|
+
return `${serializeValue(value, 0)}\n`;
|
|
887
903
|
}
|
|
888
904
|
|
|
889
905
|
//#endregion
|
|
@@ -1072,4 +1088,5 @@ exports.SwaggerResponse = SwaggerResponse;
|
|
|
1072
1088
|
exports.SwaggerSecurity = SwaggerSecurity;
|
|
1073
1089
|
exports.SwaggerSecurityAll = SwaggerSecurityAll;
|
|
1074
1090
|
exports.SwaggerTag = SwaggerTag;
|
|
1075
|
-
exports.getSwaggerMate = getSwaggerMate;
|
|
1091
|
+
exports.getSwaggerMate = getSwaggerMate;
|
|
1092
|
+
exports.mapToSwaggerSpec = mapToSwaggerSpec;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,55 @@
|
|
|
1
|
-
import { Mate, TMoostMetadata, TMateParamMeta } from 'moost';
|
|
1
|
+
import { TControllerOverview, Mate, TMoostMetadata, TMateParamMeta } from 'moost';
|
|
2
2
|
import { THeaderRef, TStatusRef } from '@moostjs/event-http';
|
|
3
3
|
|
|
4
4
|
type TFunction = Function;
|
|
5
5
|
|
|
6
|
+
interface TOpenApiLink {
|
|
7
|
+
operationId?: string;
|
|
8
|
+
operationRef?: string;
|
|
9
|
+
parameters?: Record<string, string>;
|
|
10
|
+
requestBody?: string;
|
|
11
|
+
description?: string;
|
|
12
|
+
server?: {
|
|
13
|
+
url: string;
|
|
14
|
+
description?: string;
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
interface TEndpointSpec {
|
|
18
|
+
summary?: string;
|
|
19
|
+
tags: string[];
|
|
20
|
+
operationId?: string;
|
|
21
|
+
description?: string;
|
|
22
|
+
deprecated?: boolean;
|
|
23
|
+
responses?: Record<string, {
|
|
24
|
+
description?: string;
|
|
25
|
+
headers?: Record<string, {
|
|
26
|
+
description?: string;
|
|
27
|
+
required?: boolean;
|
|
28
|
+
schema: TSwaggerSchema;
|
|
29
|
+
example?: unknown;
|
|
30
|
+
}>;
|
|
31
|
+
content: Record<string, {
|
|
32
|
+
schema: TSwaggerSchema;
|
|
33
|
+
}>;
|
|
34
|
+
links?: Record<string, TOpenApiLink>;
|
|
35
|
+
}>;
|
|
36
|
+
parameters: {
|
|
37
|
+
name: string;
|
|
38
|
+
in: string;
|
|
39
|
+
description?: string;
|
|
40
|
+
required: boolean;
|
|
41
|
+
schema: TSwaggerSchema;
|
|
42
|
+
}[];
|
|
43
|
+
requestBody?: {
|
|
44
|
+
required?: boolean;
|
|
45
|
+
content: Record<string, {
|
|
46
|
+
schema: TSwaggerSchema;
|
|
47
|
+
}>;
|
|
48
|
+
};
|
|
49
|
+
externalDocs?: TSwaggerExternalDocs;
|
|
50
|
+
security?: TSwaggerSecurityRequirement[];
|
|
51
|
+
callbacks?: Record<string, Record<string, Record<string, unknown>>>;
|
|
52
|
+
}
|
|
6
53
|
interface TSwaggerOptions {
|
|
7
54
|
title?: string;
|
|
8
55
|
description?: string;
|
|
@@ -70,6 +117,41 @@ interface TSwaggerSchema {
|
|
|
70
117
|
};
|
|
71
118
|
$defs?: Record<string, TSwaggerSchema>;
|
|
72
119
|
}
|
|
120
|
+
declare const globalSchemas: Record<string, TSwaggerSchema>;
|
|
121
|
+
declare function mapToSwaggerSpec(metadata: TControllerOverview[], options?: TSwaggerOptions): {
|
|
122
|
+
components: {
|
|
123
|
+
schemas: typeof globalSchemas;
|
|
124
|
+
securitySchemes?: Record<string, TSwaggerSecurityScheme>;
|
|
125
|
+
};
|
|
126
|
+
security?: TSwaggerSecurityRequirement[] | undefined;
|
|
127
|
+
externalDocs?: TSwaggerExternalDocs | undefined;
|
|
128
|
+
servers?: {
|
|
129
|
+
url: string;
|
|
130
|
+
description?: string;
|
|
131
|
+
}[] | undefined;
|
|
132
|
+
openapi: string;
|
|
133
|
+
info: {
|
|
134
|
+
termsOfService?: string | undefined;
|
|
135
|
+
license?: {
|
|
136
|
+
name: string;
|
|
137
|
+
url?: string;
|
|
138
|
+
} | undefined;
|
|
139
|
+
contact?: {
|
|
140
|
+
name?: string;
|
|
141
|
+
url?: string;
|
|
142
|
+
email?: string;
|
|
143
|
+
} | undefined;
|
|
144
|
+
version: string;
|
|
145
|
+
description?: string | undefined;
|
|
146
|
+
title: string;
|
|
147
|
+
};
|
|
148
|
+
paths: Record<string, Record<string, TEndpointSpec>>;
|
|
149
|
+
tags: {
|
|
150
|
+
name: string;
|
|
151
|
+
description?: string;
|
|
152
|
+
externalDocs?: TSwaggerExternalDocs;
|
|
153
|
+
}[];
|
|
154
|
+
};
|
|
73
155
|
|
|
74
156
|
/** Returns the shared `Mate` instance extended with Swagger/OpenAPI metadata fields. */
|
|
75
157
|
declare function getSwaggerMate(): Mate<TMoostMetadata & TSwaggerMate & {
|
|
@@ -365,5 +447,5 @@ declare class SwaggerController {
|
|
|
365
447
|
serve(path: string): Promise<unknown>;
|
|
366
448
|
}
|
|
367
449
|
|
|
368
|
-
export { SwaggerCallback, SwaggerController, SwaggerDeprecated, SwaggerDescription, SwaggerExample, SwaggerExclude, SwaggerExternalDocs, SwaggerLink, SwaggerOperationId, SwaggerParam, SwaggerPublic, SwaggerRequestBody, SwaggerResponse, SwaggerSecurity, SwaggerSecurityAll, SwaggerTag, getSwaggerMate };
|
|
369
|
-
export type { TSwaggerCallbackConfig, TSwaggerCallbackOptions, TSwaggerConfigType, TSwaggerExternalDocs, TSwaggerLinkConfig, TSwaggerLinkOptions, TSwaggerMate, TSwaggerResponseHeader, TSwaggerResponseOpts, TSwaggerSecurityRequirement, TSwaggerSecurityScheme, TSwaggerSecuritySchemeApiKey, TSwaggerSecuritySchemeHttp, TSwaggerSecuritySchemeOAuth2, TSwaggerSecuritySchemeOAuth2Flow, TSwaggerSecuritySchemeOpenIdConnect };
|
|
450
|
+
export { SwaggerCallback, SwaggerController, SwaggerDeprecated, SwaggerDescription, SwaggerExample, SwaggerExclude, SwaggerExternalDocs, SwaggerLink, SwaggerOperationId, SwaggerParam, SwaggerPublic, SwaggerRequestBody, SwaggerResponse, SwaggerSecurity, SwaggerSecurityAll, SwaggerTag, getSwaggerMate, mapToSwaggerSpec };
|
|
451
|
+
export type { TSwaggerCallbackConfig, TSwaggerCallbackOptions, TSwaggerConfigType, TSwaggerExternalDocs, TSwaggerLinkConfig, TSwaggerLinkOptions, TSwaggerMate, TSwaggerOptions, TSwaggerResponseHeader, TSwaggerResponseOpts, TSwaggerSecurityRequirement, TSwaggerSecurityScheme, TSwaggerSecuritySchemeApiKey, TSwaggerSecuritySchemeHttp, TSwaggerSecuritySchemeOAuth2, TSwaggerSecuritySchemeOAuth2Flow, TSwaggerSecuritySchemeOpenIdConnect };
|
package/dist/index.mjs
CHANGED
|
@@ -131,55 +131,6 @@ function SwaggerLink(codeOrName, nameOrOptions, maybeOptions) {
|
|
|
131
131
|
return getSwaggerMate().decorate("swaggerCallbacks", config, true);
|
|
132
132
|
}
|
|
133
133
|
|
|
134
|
-
//#endregion
|
|
135
|
-
//#region packages/swagger/src/json-to-yaml.ts
|
|
136
|
-
const YAML_SPECIAL = /^[\s#!&*|>'{}[\],?:@`-]|[:#]\s|[\n\r]|\s$/;
|
|
137
|
-
function quoteString(str) {
|
|
138
|
-
if (str === "") return "''";
|
|
139
|
-
if (YAML_SPECIAL.test(str) || str === "true" || str === "false" || str === "null") return JSON.stringify(str);
|
|
140
|
-
const num = Number(str);
|
|
141
|
-
if (str.length > 0 && !Number.isNaN(num) && String(num) === str) return JSON.stringify(str);
|
|
142
|
-
return str;
|
|
143
|
-
}
|
|
144
|
-
function serializeValue(value, indent) {
|
|
145
|
-
if (value === null || value === void 0) return "null";
|
|
146
|
-
if (typeof value === "boolean") return value ? "true" : "false";
|
|
147
|
-
if (typeof value === "number") return Number.isFinite(value) ? String(value) : "null";
|
|
148
|
-
if (typeof value === "string") return quoteString(value);
|
|
149
|
-
const pad = " ".repeat(indent);
|
|
150
|
-
const childPad = " ".repeat(indent + 1);
|
|
151
|
-
if (Array.isArray(value)) {
|
|
152
|
-
if (value.length === 0) return "[]";
|
|
153
|
-
const lines = [];
|
|
154
|
-
for (const item of value) if (isObject(item) || Array.isArray(item)) {
|
|
155
|
-
const nested = serializeValue(item, indent + 1);
|
|
156
|
-
lines.push(`${pad}- ${nested.slice(childPad.length)}`);
|
|
157
|
-
} else lines.push(`${pad}- ${serializeValue(item, 0)}`);
|
|
158
|
-
return lines.join("\n");
|
|
159
|
-
}
|
|
160
|
-
if (isObject(value)) {
|
|
161
|
-
const entries = Object.entries(value);
|
|
162
|
-
if (entries.length === 0) return "{}";
|
|
163
|
-
const lines = [];
|
|
164
|
-
for (const [key, val] of entries) {
|
|
165
|
-
const yamlKey = quoteString(key);
|
|
166
|
-
if (isObject(val) || Array.isArray(val)) {
|
|
167
|
-
const nested = serializeValue(val, indent + 1);
|
|
168
|
-
if (nested === "[]" || nested === "{}") lines.push(`${pad}${yamlKey}: ${nested}`);
|
|
169
|
-
else lines.push(`${pad}${yamlKey}:\n${nested}`);
|
|
170
|
-
} else lines.push(`${pad}${yamlKey}: ${serializeValue(val, 0)}`);
|
|
171
|
-
}
|
|
172
|
-
return lines.join("\n");
|
|
173
|
-
}
|
|
174
|
-
return String(value);
|
|
175
|
-
}
|
|
176
|
-
function isObject(value) {
|
|
177
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
178
|
-
}
|
|
179
|
-
function jsonToYaml(value) {
|
|
180
|
-
return `${serializeValue(value, 0)}\n`;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
134
|
//#endregion
|
|
184
135
|
//#region packages/swagger/src/mapping.ts
|
|
185
136
|
const globalSchemas = {};
|
|
@@ -634,12 +585,13 @@ function ensureComponentName(typeRef, schema, suggestedName) {
|
|
|
634
585
|
* throughout the schema tree to `#/components/schemas/X`.
|
|
635
586
|
* Removes the `$defs` property from the schema after hoisting.
|
|
636
587
|
*/ function hoistDefs(schema) {
|
|
637
|
-
if (
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
588
|
+
if (schema.$defs) {
|
|
589
|
+
for (const [name, def] of Object.entries(schema.$defs)) if (!globalSchemas[name]) {
|
|
590
|
+
globalSchemas[name] = cloneSchema(def);
|
|
591
|
+
hoistDefs(globalSchemas[name]);
|
|
592
|
+
}
|
|
593
|
+
delete schema.$defs;
|
|
641
594
|
}
|
|
642
|
-
delete schema.$defs;
|
|
643
595
|
rewriteDefsRefs(schema);
|
|
644
596
|
}
|
|
645
597
|
/** Recursively rewrite `$ref: '#/$defs/X'` → `$ref: '#/components/schemas/X'` */ function rewriteDefsRefs(schema) {
|
|
@@ -842,19 +794,83 @@ function transportsToSecurityRequirement(transports) {
|
|
|
842
794
|
if (transports.cookie) requirements.push({ cookieAuth: [] });
|
|
843
795
|
return requirements;
|
|
844
796
|
}
|
|
797
|
+
/**
|
|
798
|
+
* Collects schemes and builds the security requirement for one or more
|
|
799
|
+
* `@Authenticate` guards. Multiple stacked guards are AND-combined
|
|
800
|
+
* (merged into a single requirement object via cross-product), while
|
|
801
|
+
* multiple transports within a single guard remain OR-alternatives.
|
|
802
|
+
*/ function applyAuthTransports(transports, schemes) {
|
|
803
|
+
const list = Array.isArray(transports) ? transports : [transports];
|
|
804
|
+
let combined = [{}];
|
|
805
|
+
for (const entry of list) {
|
|
806
|
+
collectSchemesFromTransports(entry, schemes);
|
|
807
|
+
const requirements = transportsToSecurityRequirement(entry);
|
|
808
|
+
if (requirements.length === 0) continue;
|
|
809
|
+
const next = [];
|
|
810
|
+
for (const base of combined) for (const requirement of requirements) next.push({
|
|
811
|
+
...base,
|
|
812
|
+
...requirement
|
|
813
|
+
});
|
|
814
|
+
combined = next;
|
|
815
|
+
}
|
|
816
|
+
return combined.filter((requirement) => Object.keys(requirement).length > 0);
|
|
817
|
+
}
|
|
845
818
|
function resolveOperationSecurity(cmeta, hmeta, schemes) {
|
|
846
819
|
if (hmeta?.swaggerPublic) return [];
|
|
847
820
|
if (hmeta?.swaggerSecurity?.length) return hmeta.swaggerSecurity;
|
|
848
|
-
if (hmeta?.authTransports)
|
|
849
|
-
collectSchemesFromTransports(hmeta.authTransports, schemes);
|
|
850
|
-
return transportsToSecurityRequirement(hmeta.authTransports);
|
|
851
|
-
}
|
|
821
|
+
if (hmeta?.authTransports) return applyAuthTransports(hmeta.authTransports, schemes);
|
|
852
822
|
if (cmeta?.swaggerPublic) return [];
|
|
853
823
|
if (cmeta?.swaggerSecurity?.length) return cmeta.swaggerSecurity;
|
|
854
|
-
if (cmeta?.authTransports)
|
|
855
|
-
|
|
856
|
-
|
|
824
|
+
if (cmeta?.authTransports) return applyAuthTransports(cmeta.authTransports, schemes);
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
//#endregion
|
|
828
|
+
//#region packages/swagger/src/json-to-yaml.ts
|
|
829
|
+
const YAML_SPECIAL = /^[\s#!&*|>'{}[\],?:@`-]|[:#]\s|[\n\r]|\s$/;
|
|
830
|
+
function quoteString(str) {
|
|
831
|
+
if (str === "") return "''";
|
|
832
|
+
if (YAML_SPECIAL.test(str) || str === "true" || str === "false" || str === "null") return JSON.stringify(str);
|
|
833
|
+
const num = Number(str);
|
|
834
|
+
if (str.length > 0 && !Number.isNaN(num) && String(num) === str) return JSON.stringify(str);
|
|
835
|
+
return str;
|
|
836
|
+
}
|
|
837
|
+
function serializeValue(value, indent) {
|
|
838
|
+
if (value === null || value === void 0) return "null";
|
|
839
|
+
if (typeof value === "boolean") return value ? "true" : "false";
|
|
840
|
+
if (typeof value === "number") return Number.isFinite(value) ? String(value) : "null";
|
|
841
|
+
if (typeof value === "string") return quoteString(value);
|
|
842
|
+
const pad = " ".repeat(indent);
|
|
843
|
+
const childPad = " ".repeat(indent + 1);
|
|
844
|
+
if (Array.isArray(value)) {
|
|
845
|
+
if (value.length === 0) return "[]";
|
|
846
|
+
const lines = [];
|
|
847
|
+
for (const item of value) if (isObject(item) || Array.isArray(item)) {
|
|
848
|
+
const nested = serializeValue(item, indent + 1);
|
|
849
|
+
lines.push(`${pad}- ${nested.slice(childPad.length)}`);
|
|
850
|
+
} else lines.push(`${pad}- ${serializeValue(item, 0)}`);
|
|
851
|
+
return lines.join("\n");
|
|
857
852
|
}
|
|
853
|
+
if (isObject(value)) {
|
|
854
|
+
const entries = Object.entries(value);
|
|
855
|
+
if (entries.length === 0) return "{}";
|
|
856
|
+
const lines = [];
|
|
857
|
+
for (const [key, val] of entries) {
|
|
858
|
+
const yamlKey = quoteString(key);
|
|
859
|
+
if (isObject(val) || Array.isArray(val)) {
|
|
860
|
+
const nested = serializeValue(val, indent + 1);
|
|
861
|
+
if (nested === "[]" || nested === "{}") lines.push(`${pad}${yamlKey}: ${nested}`);
|
|
862
|
+
else lines.push(`${pad}${yamlKey}:\n${nested}`);
|
|
863
|
+
} else lines.push(`${pad}${yamlKey}: ${serializeValue(val, 0)}`);
|
|
864
|
+
}
|
|
865
|
+
return lines.join("\n");
|
|
866
|
+
}
|
|
867
|
+
return String(value);
|
|
868
|
+
}
|
|
869
|
+
function isObject(value) {
|
|
870
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
871
|
+
}
|
|
872
|
+
function jsonToYaml(value) {
|
|
873
|
+
return `${serializeValue(value, 0)}\n`;
|
|
858
874
|
}
|
|
859
875
|
|
|
860
876
|
//#endregion
|
|
@@ -1022,4 +1038,4 @@ SwaggerController = _ts_decorate([
|
|
|
1022
1038
|
], SwaggerController);
|
|
1023
1039
|
|
|
1024
1040
|
//#endregion
|
|
1025
|
-
export { SwaggerCallback, SwaggerController, SwaggerDeprecated, SwaggerDescription, SwaggerExample, SwaggerExclude, SwaggerExternalDocs, SwaggerLink, SwaggerOperationId, SwaggerParam, SwaggerPublic, SwaggerRequestBody, SwaggerResponse, SwaggerSecurity, SwaggerSecurityAll, SwaggerTag, getSwaggerMate };
|
|
1041
|
+
export { SwaggerCallback, SwaggerController, SwaggerDeprecated, SwaggerDescription, SwaggerExample, SwaggerExclude, SwaggerExternalDocs, SwaggerLink, SwaggerOperationId, SwaggerParam, SwaggerPublic, SwaggerRequestBody, SwaggerResponse, SwaggerSecurity, SwaggerSecurityAll, SwaggerTag, getSwaggerMate, mapToSwaggerSpec };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@moostjs/swagger",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.26",
|
|
4
4
|
"description": "@moostjs/swagger",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"composables",
|
|
@@ -45,10 +45,10 @@
|
|
|
45
45
|
"vitest": "3.2.4"
|
|
46
46
|
},
|
|
47
47
|
"peerDependencies": {
|
|
48
|
-
"@wooksjs/event-http": "^0.7.
|
|
49
|
-
"@wooksjs/http-static": "^0.7.
|
|
50
|
-
"
|
|
51
|
-
"
|
|
48
|
+
"@wooksjs/event-http": "^0.7.19",
|
|
49
|
+
"@wooksjs/http-static": "^0.7.19",
|
|
50
|
+
"moost": "^0.6.26",
|
|
51
|
+
"@moostjs/event-http": "^0.6.26"
|
|
52
52
|
},
|
|
53
53
|
"scripts": {
|
|
54
54
|
"pub": "pnpm publish --access public",
|