@nestia/sdk 13.0.4 → 14.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/assets/bundle/distribute/package.json +2 -2
- package/assets/bundle/distribute/tsconfig.json +15 -105
- package/lib/INestiaConfig.d.ts +19 -10
- package/lib/NestiaSdkApplication.js +58 -27
- package/lib/NestiaSdkApplication.js.map +1 -1
- package/lib/NestiaSwaggerComposer.js +11 -10
- package/lib/NestiaSwaggerComposer.js.map +1 -1
- package/lib/analyses/ConfigAnalyzer.js +4 -4
- package/lib/analyses/ConfigAnalyzer.js.map +1 -1
- package/lib/analyses/ParameterNameAnalyzer.d.ts +20 -0
- package/lib/analyses/ParameterNameAnalyzer.js +48 -0
- package/lib/analyses/ParameterNameAnalyzer.js.map +1 -0
- package/lib/analyses/PathAnalyzer.d.ts +30 -0
- package/lib/analyses/PathAnalyzer.js +108 -16
- package/lib/analyses/PathAnalyzer.js.map +1 -1
- package/lib/analyses/ReflectControllerAnalyzer.js +2 -1
- package/lib/analyses/ReflectControllerAnalyzer.js.map +1 -1
- package/lib/analyses/ReflectHttpOperationAnalyzer.js +7 -7
- package/lib/analyses/ReflectHttpOperationAnalyzer.js.map +1 -1
- package/lib/analyses/ReflectHttpOperationParameterAnalyzer.js +122 -23
- package/lib/analyses/ReflectHttpOperationParameterAnalyzer.js.map +1 -1
- package/lib/analyses/ReflectHttpOperationResponseAnalyzer.js +5 -2
- package/lib/analyses/ReflectHttpOperationResponseAnalyzer.js.map +1 -1
- package/lib/analyses/ReflectMcpOperationAnalyzer.js +5 -2
- package/lib/analyses/ReflectMcpOperationAnalyzer.js.map +1 -1
- package/lib/analyses/ReflectWebSocketOperationAnalyzer.js +6 -3
- package/lib/analyses/ReflectWebSocketOperationAnalyzer.js.map +1 -1
- package/lib/analyses/SecurityAnalyzer.d.ts +10 -0
- package/lib/analyses/SecurityAnalyzer.js +24 -19
- package/lib/analyses/SecurityAnalyzer.js.map +1 -1
- package/lib/analyses/SwaggerExampleAnalyzer.d.ts +15 -0
- package/lib/analyses/SwaggerExampleAnalyzer.js +20 -0
- package/lib/analyses/SwaggerExampleAnalyzer.js.map +1 -0
- package/lib/analyses/TypedHttpRouteAnalyzer.js +30 -21
- package/lib/analyses/TypedHttpRouteAnalyzer.js.map +1 -1
- package/lib/analyses/TypedWebSocketRouteAnalyzer.js +1 -1
- package/lib/analyses/TypedWebSocketRouteAnalyzer.js.map +1 -1
- package/lib/executable/internal/NestiaSdkWatcher.js +3 -3
- package/lib/executable/internal/NestiaSdkWatcher.js.map +1 -1
- package/lib/executable/sdk.js +16 -1
- package/lib/executable/sdk.js.map +1 -1
- package/lib/factories/ExpressionFactory.d.ts +3 -0
- package/lib/factories/ExpressionFactory.js +6 -2
- package/lib/factories/ExpressionFactory.js.map +1 -1
- package/lib/factories/LiteralFactory.js +3 -1
- package/lib/factories/LiteralFactory.js.map +1 -1
- package/lib/generates/SdkGenerator.js +3 -3
- package/lib/generates/SdkGenerator.js.map +1 -1
- package/lib/generates/SwaggerGenerator.js +135 -27
- package/lib/generates/SwaggerGenerator.js.map +1 -1
- package/lib/generates/internal/FilePrinter.d.ts +14 -0
- package/lib/generates/internal/FilePrinter.js +31 -0
- package/lib/generates/internal/FilePrinter.js.map +1 -1
- package/lib/generates/internal/ImportDictionary.d.ts +2 -0
- package/lib/generates/internal/ImportDictionary.js +13 -0
- package/lib/generates/internal/ImportDictionary.js.map +1 -1
- package/lib/generates/internal/SdkAliasCollection.d.ts +2 -1
- package/lib/generates/internal/SdkAliasCollection.js +8 -4
- package/lib/generates/internal/SdkAliasCollection.js.map +1 -1
- package/lib/generates/internal/SdkDistributionComposer.js +72 -14
- package/lib/generates/internal/SdkDistributionComposer.js.map +1 -1
- package/lib/generates/internal/SdkFileProgrammer.js +83 -12
- package/lib/generates/internal/SdkFileProgrammer.js.map +1 -1
- package/lib/generates/internal/SdkHttpFunctionProgrammer.js +21 -23
- package/lib/generates/internal/SdkHttpFunctionProgrammer.js.map +1 -1
- package/lib/generates/internal/SdkHttpNamespaceProgrammer.js +27 -34
- package/lib/generates/internal/SdkHttpNamespaceProgrammer.js.map +1 -1
- package/lib/generates/internal/SdkHttpParameterProgrammer.d.ts +40 -1
- package/lib/generates/internal/SdkHttpParameterProgrammer.js +105 -6
- package/lib/generates/internal/SdkHttpParameterProgrammer.js.map +1 -1
- package/lib/generates/internal/SdkHttpRouteProgrammer.js +16 -32
- package/lib/generates/internal/SdkHttpRouteProgrammer.js.map +1 -1
- package/lib/generates/internal/SdkHttpSimulationProgrammer.js +18 -14
- package/lib/generates/internal/SdkHttpSimulationProgrammer.js.map +1 -1
- package/lib/generates/internal/SdkMcpRouteProgrammer.js +39 -11
- package/lib/generates/internal/SdkMcpRouteProgrammer.js.map +1 -1
- package/lib/generates/internal/SdkPathTemplate.d.ts +21 -0
- package/lib/generates/internal/SdkPathTemplate.js +50 -0
- package/lib/generates/internal/SdkPathTemplate.js.map +1 -0
- package/lib/generates/internal/SdkTypeProgrammer.js +15 -15
- package/lib/generates/internal/SdkTypeProgrammer.js.map +1 -1
- package/lib/generates/internal/SdkTypeTagProgrammer.d.ts +20 -1
- package/lib/generates/internal/SdkTypeTagProgrammer.js +386 -67
- package/lib/generates/internal/SdkTypeTagProgrammer.js.map +1 -1
- package/lib/generates/internal/SdkWebSocketNamespaceProgrammer.js +27 -36
- package/lib/generates/internal/SdkWebSocketNamespaceProgrammer.js.map +1 -1
- package/lib/generates/internal/SdkWebSocketParameterProgrammer.d.ts +35 -1
- package/lib/generates/internal/SdkWebSocketParameterProgrammer.js +72 -5
- package/lib/generates/internal/SdkWebSocketParameterProgrammer.js.map +1 -1
- package/lib/generates/internal/SdkWebSocketRouteProgrammer.js +57 -33
- package/lib/generates/internal/SdkWebSocketRouteProgrammer.js.map +1 -1
- package/lib/generates/internal/SwaggerOperationComposer.js +19 -8
- package/lib/generates/internal/SwaggerOperationComposer.js.map +1 -1
- package/lib/generates/internal/SwaggerOperationParameterComposer.d.ts +1 -1
- package/lib/generates/internal/SwaggerOperationParameterComposer.js +201 -19
- package/lib/generates/internal/SwaggerOperationParameterComposer.js.map +1 -1
- package/lib/generates/internal/SwaggerOperationResponseComposer.d.ts +2 -0
- package/lib/generates/internal/SwaggerOperationResponseComposer.js +39 -7
- package/lib/generates/internal/SwaggerOperationResponseComposer.js.map +1 -1
- package/lib/internal/legacy.d.ts +49 -18
- package/lib/internal/legacy.js +84 -25
- package/lib/internal/legacy.js.map +1 -1
- package/lib/structures/IOperationMetadata.d.ts +22 -0
- package/lib/structures/IReflectHttpOperationException.d.ts +3 -2
- package/lib/structures/IReflectHttpOperationParameter.d.ts +8 -3
- package/lib/structures/IReflectHttpOperationSuccess.d.ts +4 -3
- package/lib/structures/ITypedHttpRoute.d.ts +8 -0
- package/lib/structures/ITypedHttpRouteException.d.ts +3 -1
- package/lib/structures/ITypedHttpRouteParameter.d.ts +3 -1
- package/lib/structures/ITypedHttpRouteSuccess.d.ts +3 -1
- package/lib/structures/ITypedWebSocketRoute.d.ts +8 -0
- package/lib/utils/SourceFinder.d.ts +10 -0
- package/lib/utils/SourceFinder.js +27 -4
- package/lib/utils/SourceFinder.js.map +1 -1
- package/lib/utils/VersioningStrategy.d.ts +13 -1
- package/lib/utils/VersioningStrategy.js +24 -12
- package/lib/utils/VersioningStrategy.js.map +1 -1
- package/native/go.mod +1 -1
- package/native/go.sum +2 -2
- package/native/sdk/sdk_http_rules.go +210 -0
- package/native/sdk/sdk_metadata_json.go +73 -3
- package/native/sdk/sdk_transform.go +801 -49
- package/package.json +8 -8
- package/src/INestiaConfig.ts +19 -10
- package/src/NestiaSdkApplication.ts +63 -34
- package/src/NestiaSwaggerComposer.ts +11 -8
- package/src/analyses/ConfigAnalyzer.ts +3 -3
- package/src/analyses/ParameterNameAnalyzer.ts +51 -0
- package/src/analyses/PathAnalyzer.ts +106 -14
- package/src/analyses/ReflectControllerAnalyzer.ts +2 -1
- package/src/analyses/ReflectHttpOperationAnalyzer.ts +4 -4
- package/src/analyses/ReflectHttpOperationParameterAnalyzer.ts +122 -39
- package/src/analyses/ReflectHttpOperationResponseAnalyzer.ts +6 -7
- package/src/analyses/ReflectMcpOperationAnalyzer.ts +6 -2
- package/src/analyses/ReflectWebSocketOperationAnalyzer.ts +7 -3
- package/src/analyses/SecurityAnalyzer.ts +30 -20
- package/src/analyses/SwaggerExampleAnalyzer.ts +23 -0
- package/src/analyses/TypedHttpRouteAnalyzer.ts +29 -20
- package/src/analyses/TypedWebSocketRouteAnalyzer.ts +1 -0
- package/src/executable/internal/NestiaSdkWatcher.ts +2 -2
- package/src/executable/sdk.ts +24 -1
- package/src/factories/ExpressionFactory.ts +8 -2
- package/src/factories/LiteralFactory.ts +7 -2
- package/src/generates/SdkGenerator.ts +3 -3
- package/src/generates/SwaggerGenerator.ts +173 -32
- package/src/generates/internal/FilePrinter.ts +28 -0
- package/src/generates/internal/ImportDictionary.ts +11 -0
- package/src/generates/internal/SdkAliasCollection.ts +11 -4
- package/src/generates/internal/SdkDistributionComposer.ts +89 -15
- package/src/generates/internal/SdkFileProgrammer.ts +108 -16
- package/src/generates/internal/SdkHttpFunctionProgrammer.ts +28 -30
- package/src/generates/internal/SdkHttpNamespaceProgrammer.ts +34 -64
- package/src/generates/internal/SdkHttpParameterProgrammer.ts +193 -13
- package/src/generates/internal/SdkHttpRouteProgrammer.ts +17 -31
- package/src/generates/internal/SdkHttpSimulationProgrammer.ts +51 -14
- package/src/generates/internal/SdkMcpRouteProgrammer.ts +112 -15
- package/src/generates/internal/SdkPathTemplate.ts +73 -0
- package/src/generates/internal/SdkTypeProgrammer.ts +19 -22
- package/src/generates/internal/SdkTypeTagProgrammer.ts +506 -78
- package/src/generates/internal/SdkWebSocketNamespaceProgrammer.ts +43 -68
- package/src/generates/internal/SdkWebSocketParameterProgrammer.ts +126 -5
- package/src/generates/internal/SdkWebSocketRouteProgrammer.ts +71 -48
- package/src/generates/internal/SwaggerOperationComposer.ts +20 -6
- package/src/generates/internal/SwaggerOperationParameterComposer.ts +279 -30
- package/src/generates/internal/SwaggerOperationResponseComposer.ts +55 -11
- package/src/internal/legacy.ts +114 -26
- package/src/structures/IOperationMetadata.ts +28 -0
- package/src/structures/IReflectHttpOperationException.ts +7 -2
- package/src/structures/IReflectHttpOperationParameter.ts +9 -2
- package/src/structures/IReflectHttpOperationSuccess.ts +8 -3
- package/src/structures/ITypedHttpRoute.ts +8 -0
- package/src/structures/ITypedHttpRouteException.ts +4 -1
- package/src/structures/ITypedHttpRouteParameter.ts +3 -1
- package/src/structures/ITypedHttpRouteSuccess.ts +4 -1
- package/src/structures/ITypedWebSocketRoute.ts +8 -0
- package/src/utils/SourceFinder.ts +25 -4
- package/src/utils/VersioningStrategy.ts +28 -13
- package/lib/validators/HttpHeadersValidator.d.ts +0 -11
- package/lib/validators/HttpHeadersValidator.js +0 -15
- package/lib/validators/HttpHeadersValidator.js.map +0 -1
- package/lib/validators/HttpQueryValidator.d.ts +0 -10
- package/lib/validators/HttpQueryValidator.js +0 -14
- package/lib/validators/HttpQueryValidator.js.map +0 -1
- package/src/validators/HttpHeadersValidator.ts +0 -12
- package/src/validators/HttpQueryValidator.ts +0 -11
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type Expression, factory } from "@ttsc/factory";
|
|
1
|
+
import { type Expression, SyntaxKind, factory } from "@ttsc/factory";
|
|
2
2
|
|
|
3
3
|
import { ExpressionFactory } from "./ExpressionFactory";
|
|
4
4
|
import { IdentifierFactory } from "./IdentifierFactory";
|
|
@@ -30,7 +30,12 @@ export namespace LiteralFactory {
|
|
|
30
30
|
if (typeof input === "number") return ExpressionFactory.number(input);
|
|
31
31
|
if (typeof input === "string") return factory.createStringLiteral(input);
|
|
32
32
|
if (typeof input === "bigint")
|
|
33
|
-
return
|
|
33
|
+
return input < BigInt(0)
|
|
34
|
+
? factory.createPrefixUnaryExpression(
|
|
35
|
+
SyntaxKind.MinusToken,
|
|
36
|
+
factory.createBigIntLiteral((-input).toString()),
|
|
37
|
+
)
|
|
38
|
+
: factory.createBigIntLiteral(input.toString());
|
|
34
39
|
if (typeof input === "function")
|
|
35
40
|
return factory.createIdentifier("undefined");
|
|
36
41
|
throw new TypeError("LiteralFactory.write: unsupported input type.");
|
|
@@ -118,7 +118,7 @@ export namespace SdkGenerator {
|
|
|
118
118
|
props.errors.push({
|
|
119
119
|
file: props.route.controller.file,
|
|
120
120
|
class: props.route.controller.class.name,
|
|
121
|
-
function: props.route.
|
|
121
|
+
function: props.route.key,
|
|
122
122
|
from: `parameter ${JSON.stringify(p.name)}`,
|
|
123
123
|
contents: [`implicit (unnamed) parameter type.`],
|
|
124
124
|
});
|
|
@@ -129,7 +129,7 @@ export namespace SdkGenerator {
|
|
|
129
129
|
props.errors.push({
|
|
130
130
|
file: props.route.controller.file,
|
|
131
131
|
class: props.route.controller.class.name,
|
|
132
|
-
function: props.route.
|
|
132
|
+
function: props.route.key,
|
|
133
133
|
from: `exception ${JSON.stringify(key)}`,
|
|
134
134
|
contents: [`implicit (unnamed) exception type.`],
|
|
135
135
|
});
|
|
@@ -140,7 +140,7 @@ export namespace SdkGenerator {
|
|
|
140
140
|
props.errors.push({
|
|
141
141
|
file: props.route.controller.file,
|
|
142
142
|
class: props.route.controller.class.name,
|
|
143
|
-
function: props.route.
|
|
143
|
+
function: props.route.key,
|
|
144
144
|
from: "success",
|
|
145
145
|
contents: [`implicit (unnamed) return type.`],
|
|
146
146
|
});
|
|
@@ -13,6 +13,7 @@ import { Singleton } from "tstl";
|
|
|
13
13
|
import type { IJsonSchemaCollection } from "typia";
|
|
14
14
|
|
|
15
15
|
import { INestiaConfig } from "../INestiaConfig";
|
|
16
|
+
import { PathAnalyzer } from "../analyses/PathAnalyzer";
|
|
16
17
|
import {
|
|
17
18
|
JsonSchemasProgrammer,
|
|
18
19
|
MetadataSchema,
|
|
@@ -211,7 +212,8 @@ export namespace SwaggerGenerator {
|
|
|
211
212
|
schemas: {},
|
|
212
213
|
securitySchemes: config.security,
|
|
213
214
|
},
|
|
214
|
-
|
|
215
|
+
// a copy, because composing pushes each route's tags into this list
|
|
216
|
+
tags: clone(config.tags ?? []),
|
|
215
217
|
"x-typia-emended-v12": true,
|
|
216
218
|
};
|
|
217
219
|
};
|
|
@@ -238,7 +240,12 @@ export namespace SwaggerGenerator {
|
|
|
238
240
|
routes: ITypedHttpRoute[];
|
|
239
241
|
}): void => {
|
|
240
242
|
// SWAGGER CUSTOMIZER
|
|
241
|
-
const customizers: Array<
|
|
243
|
+
const customizers: Array<{
|
|
244
|
+
route: ITypedHttpRoute;
|
|
245
|
+
method: OpenApi.Method;
|
|
246
|
+
path: string;
|
|
247
|
+
closures: Function[];
|
|
248
|
+
}> = [];
|
|
242
249
|
const neighbor = {
|
|
243
250
|
at: new Singleton(() => {
|
|
244
251
|
const functor: Map<Function, Endpoint> = new Map();
|
|
@@ -262,63 +269,197 @@ export namespace SwaggerGenerator {
|
|
|
262
269
|
(key: Accessor): OpenApi.IOperation | undefined => {
|
|
263
270
|
const method: OpenApi.Method =
|
|
264
271
|
key.method.toLowerCase() as OpenApi.Method;
|
|
265
|
-
const
|
|
272
|
+
const normalized: string =
|
|
266
273
|
"/" +
|
|
267
274
|
key.path
|
|
268
275
|
.split("/")
|
|
269
276
|
.filter((str) => !!str.length)
|
|
270
|
-
.map((str) =>
|
|
271
|
-
str.startsWith(":") ? `{${str.substring(1)}}` : str,
|
|
272
|
-
)
|
|
273
277
|
.join("/");
|
|
278
|
+
// a route path, `:id`, as the router reads it; an accessor already
|
|
279
|
+
// in OpenAPI form, `{id}`, is taken as is
|
|
280
|
+
const path: string = normalized.includes(":")
|
|
281
|
+
? PathAnalyzer.toOpenApi(normalized)
|
|
282
|
+
: normalized;
|
|
274
283
|
return props.document.paths?.[path]?.[method];
|
|
275
284
|
},
|
|
276
285
|
),
|
|
277
286
|
};
|
|
278
287
|
|
|
279
288
|
// COMPOSE OPERATIONS
|
|
289
|
+
const violations: string[] = [];
|
|
280
290
|
for (const r of props.routes) {
|
|
281
|
-
const
|
|
282
|
-
...props,
|
|
283
|
-
route: r,
|
|
284
|
-
});
|
|
291
|
+
const method: OpenApi.Method = r.method.toLowerCase() as OpenApi.Method;
|
|
285
292
|
const path: string = getPath(r);
|
|
286
293
|
props.document.paths ??= {};
|
|
287
294
|
props.document.paths[path] ??= {};
|
|
288
|
-
props.document.paths[path][
|
|
295
|
+
props.document.paths[path][method] = SwaggerOperationComposer.compose({
|
|
296
|
+
...props,
|
|
297
|
+
route: r,
|
|
298
|
+
});
|
|
299
|
+
violations.push(
|
|
300
|
+
...validateSecurity({
|
|
301
|
+
config: props.config,
|
|
302
|
+
route: r,
|
|
303
|
+
security: props.document.paths[path][method]!.security,
|
|
304
|
+
}),
|
|
305
|
+
);
|
|
289
306
|
|
|
290
307
|
const closure: Function | Function[] | undefined = Reflect.getMetadata(
|
|
291
308
|
"nestia/SwaggerCustomizer",
|
|
292
309
|
r.controller.class.prototype,
|
|
293
|
-
r.
|
|
310
|
+
r.key,
|
|
294
311
|
);
|
|
295
|
-
if (closure !== undefined)
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
method: r.method,
|
|
302
|
-
path,
|
|
303
|
-
route: operation,
|
|
304
|
-
at: (func: Function) => neighbor.at.get().get(func),
|
|
305
|
-
get: (accessor: Accessor) => neighbor.get.get()(accessor),
|
|
306
|
-
} satisfies SwaggerCustomizer.IProps);
|
|
312
|
+
if (closure !== undefined)
|
|
313
|
+
customizers.push({
|
|
314
|
+
route: r,
|
|
315
|
+
method,
|
|
316
|
+
path,
|
|
317
|
+
closures: Array.isArray(closure) ? closure : [closure],
|
|
307
318
|
});
|
|
308
|
-
}
|
|
309
319
|
}
|
|
310
320
|
|
|
311
|
-
|
|
312
|
-
|
|
321
|
+
if (violations.length !== 0)
|
|
322
|
+
throw new Error(
|
|
323
|
+
[
|
|
324
|
+
`Error on Swagger composition: invalid security requirements. Declare every scheme a route names, with its scopes, in the "security" of the Swagger configuration: "swagger.security" of "nestia.config.ts", or the configuration given to NestiaSwaggerComposer.document().`,
|
|
325
|
+
"",
|
|
326
|
+
"List of violations:",
|
|
327
|
+
...violations,
|
|
328
|
+
].join("\n"),
|
|
329
|
+
);
|
|
330
|
+
|
|
331
|
+
// DETACH, then DO CUSTOMIZE
|
|
332
|
+
detach(props.document);
|
|
333
|
+
// Each customizer receives the operation composed for its own route, found
|
|
334
|
+
// before any customizer runs, since one may move or delete paths.
|
|
335
|
+
const operations: OpenApi.IOperation[] = customizers.map(
|
|
336
|
+
(c) => props.document.paths![c.path]![c.method]!,
|
|
337
|
+
);
|
|
338
|
+
customizers.forEach((c, i) => {
|
|
339
|
+
for (const closure of c.closures)
|
|
340
|
+
closure({
|
|
341
|
+
swagger: props.document,
|
|
342
|
+
method: c.route.method,
|
|
343
|
+
path: c.path,
|
|
344
|
+
route: operations[i]!,
|
|
345
|
+
at: (func: Function) => neighbor.at.get().get(func),
|
|
346
|
+
get: (accessor: Accessor) => neighbor.get.get()(accessor),
|
|
347
|
+
} satisfies SwaggerCustomizer.IProps);
|
|
348
|
+
});
|
|
349
|
+
};
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Replaces every member of the composed document with a copy, in place.
|
|
353
|
+
*
|
|
354
|
+
* Operations still hold values nestia does not own by reference: decorator
|
|
355
|
+
* examples, `@ApiExtension` values, security requirements, and the configured
|
|
356
|
+
* servers, security schemes, and document info. They outlive the document,
|
|
357
|
+
* since route metadata and the configuration serve every composition in the
|
|
358
|
+
* process, so a `SwaggerCustomizer` or a caller editing one document edited
|
|
359
|
+
* all of them, and the next composition started from the edit. It runs before
|
|
360
|
+
* the customizers, which may then edit freely.
|
|
361
|
+
*/
|
|
362
|
+
const detach = (document: OpenApi.IDocument): void => {
|
|
363
|
+
const copy: OpenApi.IDocument = clone(document);
|
|
364
|
+
for (const key of Object.keys(document))
|
|
365
|
+
delete (document as unknown as Record<string, unknown>)[key];
|
|
366
|
+
Object.assign(document, copy);
|
|
367
|
+
};
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* Copies the arrays and plain objects `value` is built of, keeping every
|
|
371
|
+
* other value as it is.
|
|
372
|
+
*
|
|
373
|
+
* A document is edited as the tree its JSON is, so every reference to an
|
|
374
|
+
* array or plain object gets its own copy, except a reference back to an
|
|
375
|
+
* enclosing one, which stays circular. Values are not converted: a JSON copy
|
|
376
|
+
* would throw on a bigint example or turn a `Date` into a string before the
|
|
377
|
+
* customizer or caller that handles it runs.
|
|
378
|
+
*/
|
|
379
|
+
const clone = <T>(
|
|
380
|
+
value: T,
|
|
381
|
+
ancestors: Map<object, object> = new Map(),
|
|
382
|
+
): T => {
|
|
383
|
+
if (typeof value !== "object" || value === null) return value;
|
|
384
|
+
const prototype: unknown = Object.getPrototypeOf(value);
|
|
385
|
+
const array: boolean = Array.isArray(value);
|
|
386
|
+
if (!array && prototype !== Object.prototype && prototype !== null)
|
|
387
|
+
return value;
|
|
388
|
+
const circular: object | undefined = ancestors.get(value);
|
|
389
|
+
if (circular !== undefined) return circular as T;
|
|
390
|
+
const output: Record<string, unknown> = (
|
|
391
|
+
array ? new Array((value as unknown[]).length) : {}
|
|
392
|
+
) as Record<string, unknown>;
|
|
393
|
+
ancestors.set(value, output);
|
|
394
|
+
for (const key of Object.keys(value))
|
|
395
|
+
Object.defineProperty(output, key, {
|
|
396
|
+
value: clone((value as Record<string, unknown>)[key], ancestors),
|
|
397
|
+
writable: true,
|
|
398
|
+
enumerable: true,
|
|
399
|
+
configurable: true,
|
|
400
|
+
});
|
|
401
|
+
ancestors.delete(value);
|
|
402
|
+
return output as T;
|
|
313
403
|
};
|
|
314
404
|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
405
|
+
/**
|
|
406
|
+
* The violations of an operation's security requirements against the
|
|
407
|
+
* configured `swagger.security` schemes: a scheme it never declares, an
|
|
408
|
+
* OAuth2 scope no flow of the scheme declares, and, for an OpenAPI 3.0 or
|
|
409
|
+
* Swagger 2.0 document, scopes on a scheme other than OAuth2 or OpenID
|
|
410
|
+
* Connect, which those versions require to be empty. From 3.1 on, such a
|
|
411
|
+
* scheme's array lists the role names the operation requires. An OpenID
|
|
412
|
+
* Connect scheme lists no scopes of its own, so its scopes are not checked.
|
|
413
|
+
*/
|
|
414
|
+
const validateSecurity = (props: {
|
|
415
|
+
config: Omit<INestiaConfig.ISwaggerConfig, "output">;
|
|
416
|
+
route: ITypedHttpRoute;
|
|
417
|
+
security: Record<string, string[]>[] | undefined;
|
|
418
|
+
}): string[] => {
|
|
419
|
+
const violations: string[] = [];
|
|
420
|
+
const report = (message: string): void => {
|
|
421
|
+
violations.push(
|
|
422
|
+
` - ${message} (${props.route.controller.class.name}.${props.route.key}() at "${props.route.method} ${props.route.path}")`,
|
|
423
|
+
);
|
|
424
|
+
};
|
|
425
|
+
for (const requirement of props.security ?? [])
|
|
426
|
+
for (const [name, scopes] of Object.entries(requirement)) {
|
|
427
|
+
const scheme: OpenApi.ISecurityScheme | undefined =
|
|
428
|
+
props.config.security?.[name];
|
|
429
|
+
if (scheme === undefined)
|
|
430
|
+
report(`target security scheme "${name}" does not exist.`);
|
|
431
|
+
else if (scopes.length === 0 || scheme.type === "openIdConnect")
|
|
432
|
+
continue;
|
|
433
|
+
else if (scheme.type !== "oauth2") {
|
|
434
|
+
const version: string = props.config.openapi ?? "3.2";
|
|
435
|
+
if (version === "3.0" || version === "2.0")
|
|
436
|
+
report(
|
|
437
|
+
`target security scheme "${name}" is neither "oauth2" nor "openIdConnect" type, but you've configured the scopes, which OpenAPI ${version} requires to be empty.`,
|
|
438
|
+
);
|
|
439
|
+
} else {
|
|
440
|
+
const declared: Set<string> = new Set(
|
|
441
|
+
Object.values(scheme.flows ?? {}).flatMap((flow) =>
|
|
442
|
+
Object.keys(
|
|
443
|
+
(flow as { scopes?: Record<string, string> } | undefined)
|
|
444
|
+
?.scopes ?? {},
|
|
445
|
+
),
|
|
446
|
+
),
|
|
447
|
+
);
|
|
448
|
+
for (const scope of scopes)
|
|
449
|
+
if (declared.has(scope) === false)
|
|
450
|
+
report(
|
|
451
|
+
`target security scheme "${name}" does not have a specific scope "${scope}".`,
|
|
452
|
+
);
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
return violations;
|
|
320
456
|
};
|
|
321
457
|
|
|
458
|
+
// each parameter where the router reads it; a replacement by text would
|
|
459
|
+
// turn `:identity` into `{id}entity` for a path also holding `:id`
|
|
460
|
+
const getPath = (route: ITypedHttpRoute): string =>
|
|
461
|
+
PathAnalyzer.toOpenApi(route.path);
|
|
462
|
+
|
|
322
463
|
const isSwaggerExcluded = (route: ITypedHttpRoute): boolean => {
|
|
323
464
|
const controller: unknown = Reflect.getMetadata(
|
|
324
465
|
"swagger/apiExcludeController",
|
|
@@ -31,6 +31,34 @@ export namespace FilePrinter {
|
|
|
31
31
|
);
|
|
32
32
|
};
|
|
33
33
|
|
|
34
|
+
/**
|
|
35
|
+
* Writes a JSDoc tag so TypeScript reads its text back as it is.
|
|
36
|
+
*
|
|
37
|
+
* TypeScript takes a continued line's margin off up to the column the text
|
|
38
|
+
* began at: past `@<head> ` when the text starts on the tag's line, or the
|
|
39
|
+
* tag's own column when it starts on the next one. So a text whose first line
|
|
40
|
+
* is indented, such as an `@example`'s code, starts on the next line, and any
|
|
41
|
+
* other text continues at its first line's column. Either way every line
|
|
42
|
+
* keeps the indentation it has beyond the margin.
|
|
43
|
+
*
|
|
44
|
+
* @param head The tag's name, followed by a `@param` tag's parameter name
|
|
45
|
+
* @param text The tag's text
|
|
46
|
+
*/
|
|
47
|
+
export const jsDocTag = (head: string, text: string): string => {
|
|
48
|
+
const lines: string[] = text.split("\n").map((line) => line.trimEnd());
|
|
49
|
+
while (lines.length !== 0 && lines[0] === "") lines.shift();
|
|
50
|
+
while (lines.length !== 0 && lines.at(-1) === "") lines.pop();
|
|
51
|
+
if (lines.length === 0) return `@${head}`;
|
|
52
|
+
else if (/^\s/.test(lines[0]!)) return [`@${head}`, ...lines].join("\n");
|
|
53
|
+
const margin: string = " ".repeat(head.length + 2);
|
|
54
|
+
return [
|
|
55
|
+
`@${head} ${lines[0]}`,
|
|
56
|
+
...lines
|
|
57
|
+
.slice(1)
|
|
58
|
+
.map((line) => (line.length ? `${margin}${line}` : line)),
|
|
59
|
+
].join("\n");
|
|
60
|
+
};
|
|
61
|
+
|
|
34
62
|
export const enter = () =>
|
|
35
63
|
factory.createExpressionStatement(factory.createIdentifier("\n"));
|
|
36
64
|
|
|
@@ -59,6 +59,17 @@ export class ImportDictionary {
|
|
|
59
59
|
}
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
+
/** Every identifier the imports bind in the file. */
|
|
63
|
+
public locals(): string[] {
|
|
64
|
+
const output: string[] = [];
|
|
65
|
+
for (const { second: c } of this.components_) {
|
|
66
|
+
if (c.asterisk !== null) output.push(c.asterisk);
|
|
67
|
+
if (c.default !== null) output.push(c.default);
|
|
68
|
+
for (const { first: local } of c.elements) output.push(local);
|
|
69
|
+
}
|
|
70
|
+
return output;
|
|
71
|
+
}
|
|
72
|
+
|
|
62
73
|
public external(props: ImportDictionary.IProps): string {
|
|
63
74
|
const file: string = `node_modules/${props.file}`;
|
|
64
75
|
return this.internal({
|
|
@@ -16,6 +16,7 @@ import { FilePrinter } from "./FilePrinter";
|
|
|
16
16
|
import { ImportDictionary } from "./ImportDictionary";
|
|
17
17
|
import { SdkHttpParameterProgrammer } from "./SdkHttpParameterProgrammer";
|
|
18
18
|
import { SdkTypeProgrammer } from "./SdkTypeProgrammer";
|
|
19
|
+
import type { SdkWebSocketParameterProgrammer } from "./SdkWebSocketParameterProgrammer";
|
|
19
20
|
|
|
20
21
|
export namespace SdkAliasCollection {
|
|
21
22
|
export const name = ({ type }: { type: IReflectType }): TypeNode =>
|
|
@@ -82,7 +83,10 @@ export namespace SdkAliasCollection {
|
|
|
82
83
|
.flat(),
|
|
83
84
|
);
|
|
84
85
|
|
|
85
|
-
export const websocketProps = (
|
|
86
|
+
export const websocketProps = (
|
|
87
|
+
route: ITypedWebSocketRoute,
|
|
88
|
+
names: SdkWebSocketParameterProgrammer.INames,
|
|
89
|
+
): TypeNode =>
|
|
86
90
|
factory.createTypeLiteralNode([
|
|
87
91
|
...route.pathParameters.map((p) =>
|
|
88
92
|
factory.createPropertySignature(
|
|
@@ -96,7 +100,7 @@ export namespace SdkAliasCollection {
|
|
|
96
100
|
? [
|
|
97
101
|
factory.createPropertySignature(
|
|
98
102
|
undefined,
|
|
99
|
-
|
|
103
|
+
names.query,
|
|
100
104
|
undefined,
|
|
101
105
|
factory.createTypeReferenceNode("Query"),
|
|
102
106
|
),
|
|
@@ -104,7 +108,7 @@ export namespace SdkAliasCollection {
|
|
|
104
108
|
: []),
|
|
105
109
|
factory.createPropertySignature(
|
|
106
110
|
undefined,
|
|
107
|
-
|
|
111
|
+
names.provider,
|
|
108
112
|
undefined,
|
|
109
113
|
factory.createTypeReferenceNode("Provider"),
|
|
110
114
|
),
|
|
@@ -233,7 +237,10 @@ export namespace SdkAliasCollection {
|
|
|
233
237
|
branches.map((b) =>
|
|
234
238
|
factory.createPropertySignature(
|
|
235
239
|
undefined,
|
|
236
|
-
|
|
240
|
+
// a status range like "4XX" is no numeric literal
|
|
241
|
+
/^\d+$/.test(b.status)
|
|
242
|
+
? factory.createNumericLiteral(b.status)
|
|
243
|
+
: factory.createStringLiteral(b.status),
|
|
237
244
|
undefined,
|
|
238
245
|
b.type,
|
|
239
246
|
),
|
|
@@ -29,17 +29,34 @@ export namespace SdkDistributionComposer {
|
|
|
29
29
|
await replace({ root, output })(file);
|
|
30
30
|
|
|
31
31
|
// INSTALL PACKAGES
|
|
32
|
+
//
|
|
33
|
+
// The package compiles with ttsc, which applies the typia transform the
|
|
34
|
+
// SDK's `assert` and `simulate` code needs through typia's own plugin;
|
|
35
|
+
// typia removed its `setup` command in 14.0.0.
|
|
32
36
|
const v: IDependencies = await dependencies({
|
|
37
|
+
root,
|
|
33
38
|
websocket: props.websocket,
|
|
34
39
|
});
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
execute(
|
|
40
|
+
// one install per dependency kind: each npm run resolves and reifies the
|
|
41
|
+
// whole tree again, and the runtime set lands together or not at all, so
|
|
42
|
+
// an interrupted setup never leaves @nestia/fetcher, which marks the
|
|
43
|
+
// package configured, without its peers
|
|
44
|
+
execute(
|
|
45
|
+
`npm install --save-dev rimraf ttsc@${v.ttsc} typescript@${v.typescript}`,
|
|
46
|
+
);
|
|
47
|
+
execute(
|
|
48
|
+
[
|
|
49
|
+
"npm install --save",
|
|
50
|
+
`@nestia/fetcher@${v.version}`,
|
|
51
|
+
`typia@${v.typia}`,
|
|
52
|
+
...(props.mcp && v.mcp !== undefined
|
|
53
|
+
? [`@modelcontextprotocol/sdk@${v.mcp}`]
|
|
54
|
+
: []),
|
|
55
|
+
...(props.websocket && v.tgrid !== undefined
|
|
56
|
+
? [`tgrid@${v.tgrid}`]
|
|
57
|
+
: []),
|
|
58
|
+
].join(" "),
|
|
59
|
+
);
|
|
43
60
|
} finally {
|
|
44
61
|
process.chdir(root);
|
|
45
62
|
}
|
|
@@ -82,7 +99,60 @@ export namespace SdkDistributionComposer {
|
|
|
82
99
|
);
|
|
83
100
|
};
|
|
84
101
|
|
|
102
|
+
/**
|
|
103
|
+
* The version of a package the project installed, exactly: the staged package
|
|
104
|
+
* must run the typia, and compile with the ttsc and TypeScript, the project
|
|
105
|
+
* itself uses, with the tgrid and MCP SDK its WebSocket and MCP functions
|
|
106
|
+
* import. `@nestia/sdk`'s own specifier is only the fallback, and a
|
|
107
|
+
* workspace's `catalog:` one names no version npm could install.
|
|
108
|
+
*/
|
|
109
|
+
const installed = (
|
|
110
|
+
root: string,
|
|
111
|
+
name: string,
|
|
112
|
+
fallback: string | undefined,
|
|
113
|
+
): string | undefined =>
|
|
114
|
+
manifestVersion(root, name) ??
|
|
115
|
+
(fallback !== undefined && fallback.startsWith("catalog:") === false
|
|
116
|
+
? fallback
|
|
117
|
+
: undefined);
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* The version in the manifest of the package as installed. Not every package
|
|
121
|
+
* exports its `package.json`, as tgrid does not, and a subpath export may map
|
|
122
|
+
* it to a nested manifest of no name, as `@modelcontextprotocol/sdk` maps it
|
|
123
|
+
* to `dist/cjs/package.json`; so the manifest is the nearest `package.json`
|
|
124
|
+
* naming the package, at or above what the request resolves to.
|
|
125
|
+
*/
|
|
126
|
+
const manifestVersion = (root: string, name: string): string | undefined => {
|
|
127
|
+
for (const request of [`${name}/package.json`, name]) {
|
|
128
|
+
let file: string;
|
|
129
|
+
try {
|
|
130
|
+
file = require.resolve(request, { paths: [root, __dirname] });
|
|
131
|
+
} catch {
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
for (let directory: string = path.dirname(file); ; ) {
|
|
135
|
+
try {
|
|
136
|
+
const json: { name?: unknown; version?: unknown } = JSON.parse(
|
|
137
|
+
fs.readFileSync(path.join(directory, "package.json"), "utf8"),
|
|
138
|
+
);
|
|
139
|
+
if (
|
|
140
|
+
json.name === name &&
|
|
141
|
+
typeof json.version === "string" &&
|
|
142
|
+
json.version.length !== 0
|
|
143
|
+
)
|
|
144
|
+
return json.version;
|
|
145
|
+
} catch {}
|
|
146
|
+
const parent: string = path.dirname(directory);
|
|
147
|
+
if (parent === directory) break;
|
|
148
|
+
directory = parent;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return undefined;
|
|
152
|
+
};
|
|
153
|
+
|
|
85
154
|
const dependencies = async (opts: {
|
|
155
|
+
root: string;
|
|
86
156
|
websocket: boolean;
|
|
87
157
|
}): Promise<IDependencies> => {
|
|
88
158
|
const content: string = await fs.promises.readFile(
|
|
@@ -98,20 +168,22 @@ export namespace SdkDistributionComposer {
|
|
|
98
168
|
...json.devDependencies,
|
|
99
169
|
...json.dependencies,
|
|
100
170
|
};
|
|
101
|
-
const required = (key:
|
|
102
|
-
const value: string | undefined =
|
|
103
|
-
key === "version" ? json.version : dependencies[key];
|
|
171
|
+
const required = (key: string, value: string | undefined): string => {
|
|
104
172
|
if (typeof value !== "string" || value.length === 0)
|
|
105
173
|
throw new Error(
|
|
106
174
|
`Unable to resolve ${key} version for SDK distribution.`,
|
|
107
175
|
);
|
|
108
176
|
return value;
|
|
109
177
|
};
|
|
178
|
+
const version = (name: string): string | undefined =>
|
|
179
|
+
installed(opts.root, name, dependencies[name]);
|
|
110
180
|
return {
|
|
111
|
-
version: required("version
|
|
112
|
-
typia: required("typia"),
|
|
113
|
-
|
|
114
|
-
|
|
181
|
+
version: required("@nestia/fetcher", json.version),
|
|
182
|
+
typia: required("typia", version("typia")),
|
|
183
|
+
ttsc: required("ttsc", version("ttsc")),
|
|
184
|
+
typescript: required("typescript", version("typescript")),
|
|
185
|
+
tgrid: opts.websocket ? version("tgrid") : undefined,
|
|
186
|
+
mcp: version("@modelcontextprotocol/sdk"),
|
|
115
187
|
};
|
|
116
188
|
};
|
|
117
189
|
}
|
|
@@ -119,6 +191,8 @@ export namespace SdkDistributionComposer {
|
|
|
119
191
|
interface IDependencies {
|
|
120
192
|
version: string;
|
|
121
193
|
typia: string;
|
|
194
|
+
ttsc: string;
|
|
195
|
+
typescript: string;
|
|
122
196
|
tgrid: string | undefined;
|
|
123
197
|
mcp: string | undefined;
|
|
124
198
|
}
|