@di-framework/di-framework-http 0.0.0-prerelease.308 → 0.0.0-prerelease.310
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 +25 -26
- package/dist/index.d.ts +5 -5
- package/dist/index.js +238 -19
- package/dist/src/cli.js +638 -1
- package/dist/src/decorators.d.ts +2 -0
- package/dist/src/openapi.d.ts +1 -0
- package/dist/src/typed-router.d.ts +4 -4
- package/index.ts +5 -5
- package/package.json +2 -2
- package/src/cli.test.ts +34 -43
- package/src/cli.ts +11 -14
- package/src/decorators.test.ts +62 -13
- package/src/decorators.ts +67 -11
- package/src/openapi.test.ts +129 -46
- package/src/openapi.ts +119 -15
- package/src/registry.ts +1 -1
- package/src/typed-router.test.ts +72 -77
- package/src/typed-router.ts +16 -42
package/README.md
CHANGED
|
@@ -36,9 +36,9 @@ import {
|
|
|
36
36
|
type Json,
|
|
37
37
|
Controller,
|
|
38
38
|
Endpoint,
|
|
39
|
-
} from
|
|
40
|
-
import { Component, Container } from
|
|
41
|
-
import { useContainer } from
|
|
39
|
+
} from '@di-framework/di-framework-http';
|
|
40
|
+
import { Component, Container } from '@di-framework/di-framework/decorators';
|
|
41
|
+
import { useContainer } from '@di-framework/di-framework/container';
|
|
42
42
|
|
|
43
43
|
const router = TypedRouter();
|
|
44
44
|
|
|
@@ -66,29 +66,28 @@ export class EchoController {
|
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
@Endpoint({
|
|
69
|
-
summary:
|
|
70
|
-
description:
|
|
69
|
+
summary: 'Echo a message',
|
|
70
|
+
description: 'Returns the provided message with a server timestamp.',
|
|
71
71
|
responses: {
|
|
72
|
-
|
|
72
|
+
'200': { description: 'Successful echo' },
|
|
73
73
|
},
|
|
74
74
|
})
|
|
75
|
-
static post = router.post<
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
75
|
+
static post = router.post<RequestSpec<Json<EchoPayload>>, ResponseSpec<EchoResponse>>(
|
|
76
|
+
'/echo',
|
|
77
|
+
(req) => {
|
|
78
|
+
// Demonstrate auto DI registration: resolve the controller instance from
|
|
79
|
+
// the global container without any manual registration.
|
|
80
|
+
const controller = useContainer().resolve(EchoController);
|
|
81
|
+
return json(controller.echoMessage(req.content.message));
|
|
82
|
+
},
|
|
83
|
+
);
|
|
84
84
|
}
|
|
85
85
|
|
|
86
86
|
// Add a simple GET route
|
|
87
|
-
router.get(
|
|
87
|
+
router.get('/', () => json({ message: 'API is healthy' }));
|
|
88
88
|
|
|
89
89
|
export default {
|
|
90
|
-
fetch: (request: Request, env: any, ctx: any) =>
|
|
91
|
-
router.fetch(request, env, ctx),
|
|
90
|
+
fetch: (request: Request, env: any, ctx: any) => router.fetch(request, env, ctx),
|
|
92
91
|
};
|
|
93
92
|
```
|
|
94
93
|
|
|
@@ -103,7 +102,7 @@ import {
|
|
|
103
102
|
type RequestSpec,
|
|
104
103
|
type ResponseSpec,
|
|
105
104
|
type Multipart,
|
|
106
|
-
} from
|
|
105
|
+
} from '@di-framework/di-framework-http';
|
|
107
106
|
|
|
108
107
|
const router = TypedRouter();
|
|
109
108
|
|
|
@@ -111,10 +110,10 @@ type UploadPayload = { files: File[] };
|
|
|
111
110
|
type UploadResult = { filenames: string[] };
|
|
112
111
|
|
|
113
112
|
router.post<RequestSpec<Multipart<UploadPayload>>, ResponseSpec<UploadResult>>(
|
|
114
|
-
|
|
113
|
+
'/upload',
|
|
115
114
|
(req) => {
|
|
116
115
|
// req.content is typed as FormData
|
|
117
|
-
const files = req.content.getAll(
|
|
116
|
+
const files = req.content.getAll('files') as File[];
|
|
118
117
|
return json({ filenames: files.map((f) => f.name) });
|
|
119
118
|
},
|
|
120
119
|
{ multipart: true },
|
|
@@ -144,13 +143,13 @@ bun x di-framework-http generate --controllers ./src/index.ts
|
|
|
144
143
|
You can also generate the spec programmatically using the `generateOpenAPI` function and the default `registry`:
|
|
145
144
|
|
|
146
145
|
```typescript
|
|
147
|
-
import registry, { generateOpenAPI } from
|
|
148
|
-
import
|
|
146
|
+
import registry, { generateOpenAPI } from '@di-framework/di-framework-http';
|
|
147
|
+
import './controllers/MyController'; // Import to trigger registration
|
|
149
148
|
|
|
150
149
|
const spec = generateOpenAPI(
|
|
151
150
|
{
|
|
152
|
-
title:
|
|
153
|
-
version:
|
|
151
|
+
title: 'My API',
|
|
152
|
+
version: '1.0.0',
|
|
154
153
|
},
|
|
155
154
|
registry,
|
|
156
155
|
);
|
|
@@ -161,7 +160,7 @@ console.log(JSON.stringify(spec, null, 2));
|
|
|
161
160
|
If you need full control, you can iterate the `registry` manually:
|
|
162
161
|
|
|
163
162
|
```typescript
|
|
164
|
-
import registry from
|
|
163
|
+
import registry from '@di-framework/di-framework-http';
|
|
165
164
|
|
|
166
165
|
for (const target of registry.getTargets()) {
|
|
167
166
|
// target is the decorated class
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export * from
|
|
2
|
-
export * from
|
|
3
|
-
export * from
|
|
4
|
-
export * from
|
|
5
|
-
export { default as registry } from
|
|
1
|
+
export * from './src/typed-router.ts';
|
|
2
|
+
export * from './src/decorators.ts';
|
|
3
|
+
export * from './src/openapi.ts';
|
|
4
|
+
export * from './src/registry.ts';
|
|
5
|
+
export { default as registry } from './src/registry.ts';
|
package/dist/index.js
CHANGED
|
@@ -1,9 +1,5 @@
|
|
|
1
1
|
// src/typed-router.ts
|
|
2
|
-
import {
|
|
3
|
-
Router,
|
|
4
|
-
withContent,
|
|
5
|
-
json as ittyJson
|
|
6
|
-
} from "itty-router";
|
|
2
|
+
import { Router, withContent, json as ittyJson } from "itty-router";
|
|
7
3
|
function json(data, init) {
|
|
8
4
|
return ittyJson(data, init);
|
|
9
5
|
}
|
|
@@ -21,15 +17,7 @@ function TypedRouter(opts) {
|
|
|
21
17
|
req.content = await req.formData();
|
|
22
18
|
} catch {}
|
|
23
19
|
}
|
|
24
|
-
const methodsToProxy = [
|
|
25
|
-
"get",
|
|
26
|
-
"post",
|
|
27
|
-
"put",
|
|
28
|
-
"delete",
|
|
29
|
-
"patch",
|
|
30
|
-
"head",
|
|
31
|
-
"options"
|
|
32
|
-
];
|
|
20
|
+
const methodsToProxy = ["get", "post", "put", "delete", "patch", "head", "options"];
|
|
33
21
|
const wrapper = new Proxy(r, {
|
|
34
22
|
get(target, prop, receiver) {
|
|
35
23
|
if (typeof prop === "string" && methodsToProxy.includes(prop)) {
|
|
@@ -90,6 +78,7 @@ var TELEMETRY_METADATA_KEY = "di:telemetry";
|
|
|
90
78
|
var TELEMETRY_LISTENER_METADATA_KEY = "di:telemetry-listener";
|
|
91
79
|
var PUBLISHER_METADATA_KEY = "di:publisher";
|
|
92
80
|
var SUBSCRIBER_METADATA_KEY = "di:subscriber";
|
|
81
|
+
var CRON_METADATA_KEY = "di:cron";
|
|
93
82
|
var metadataStore = new Map;
|
|
94
83
|
function defineMetadata(key, value, target) {
|
|
95
84
|
if (!metadataStore.has(target)) {
|
|
@@ -103,11 +92,64 @@ function getMetadata(key, target) {
|
|
|
103
92
|
function getOwnMetadata(key, target) {
|
|
104
93
|
return getMetadata(key, target);
|
|
105
94
|
}
|
|
95
|
+
function parseCronField(field, min, max) {
|
|
96
|
+
if (field === "*") {
|
|
97
|
+
const out = [];
|
|
98
|
+
for (let i = min;i <= max; i++)
|
|
99
|
+
out.push(i);
|
|
100
|
+
return out;
|
|
101
|
+
}
|
|
102
|
+
if (field.startsWith("*/")) {
|
|
103
|
+
const step = parseInt(field.slice(2), 10);
|
|
104
|
+
const out = [];
|
|
105
|
+
for (let i = min;i <= max; i++) {
|
|
106
|
+
if (i % step === 0)
|
|
107
|
+
out.push(i);
|
|
108
|
+
}
|
|
109
|
+
return out;
|
|
110
|
+
}
|
|
111
|
+
if (field.includes(",")) {
|
|
112
|
+
return field.split(",").map((s) => parseInt(s.trim(), 10));
|
|
113
|
+
}
|
|
114
|
+
if (field.includes("-")) {
|
|
115
|
+
const [lo = 0, hi = 0] = field.split("-").map((s) => parseInt(s.trim(), 10));
|
|
116
|
+
const out = [];
|
|
117
|
+
for (let i = lo;i <= hi; i++)
|
|
118
|
+
out.push(i);
|
|
119
|
+
return out;
|
|
120
|
+
}
|
|
121
|
+
return [parseInt(field, 10)];
|
|
122
|
+
}
|
|
123
|
+
function parseCronExpression(expr) {
|
|
124
|
+
const parts = expr.trim().split(/\s+/);
|
|
125
|
+
if (parts.length !== 5)
|
|
126
|
+
throw new Error(`Invalid cron expression "${expr}": expected 5 fields (minute hour dayOfMonth month dayOfWeek)`);
|
|
127
|
+
return {
|
|
128
|
+
minute: parseCronField(parts[0], 0, 59),
|
|
129
|
+
hour: parseCronField(parts[1], 0, 23),
|
|
130
|
+
dayOfMonth: parseCronField(parts[2], 1, 31),
|
|
131
|
+
month: parseCronField(parts[3], 1, 12),
|
|
132
|
+
dayOfWeek: parseCronField(parts[4], 0, 6)
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
function getNextCronTime(fields, from) {
|
|
136
|
+
const next = new Date(from);
|
|
137
|
+
next.setSeconds(0, 0);
|
|
138
|
+
next.setMinutes(next.getMinutes() + 1);
|
|
139
|
+
for (let i = 0;i < 1051920; i++) {
|
|
140
|
+
if (fields.minute.includes(next.getMinutes()) && fields.hour.includes(next.getHours()) && fields.dayOfMonth.includes(next.getDate()) && fields.month.includes(next.getMonth() + 1) && fields.dayOfWeek.includes(next.getDay())) {
|
|
141
|
+
return next;
|
|
142
|
+
}
|
|
143
|
+
next.setMinutes(next.getMinutes() + 1);
|
|
144
|
+
}
|
|
145
|
+
throw new Error(`No matching cron time found for expression within 2 years`);
|
|
146
|
+
}
|
|
106
147
|
|
|
107
148
|
class Container {
|
|
108
149
|
services = new Map;
|
|
109
150
|
resolutionStack = new Set;
|
|
110
151
|
listeners = new Map;
|
|
152
|
+
cronJobs = [];
|
|
111
153
|
register(serviceClass, options = { singleton: true }) {
|
|
112
154
|
const name = serviceClass.name;
|
|
113
155
|
this.services.set(name, {
|
|
@@ -193,9 +235,16 @@ class Container {
|
|
|
193
235
|
}
|
|
194
236
|
clear() {
|
|
195
237
|
const count = this.services.size;
|
|
238
|
+
this.stopCronJobs();
|
|
196
239
|
this.services.clear();
|
|
197
240
|
this.emit("cleared", { count });
|
|
198
241
|
}
|
|
242
|
+
stopCronJobs() {
|
|
243
|
+
for (const job of this.cronJobs) {
|
|
244
|
+
job.stop();
|
|
245
|
+
}
|
|
246
|
+
this.cronJobs = [];
|
|
247
|
+
}
|
|
199
248
|
getServiceNames() {
|
|
200
249
|
const names = new Set;
|
|
201
250
|
this.services.forEach((_, key) => {
|
|
@@ -307,6 +356,55 @@ class Container {
|
|
|
307
356
|
}
|
|
308
357
|
});
|
|
309
358
|
}
|
|
359
|
+
applyCron(instance, constructor) {
|
|
360
|
+
const cronMethods = getMetadata(CRON_METADATA_KEY, constructor.prototype) || {};
|
|
361
|
+
Object.entries(cronMethods).forEach(([methodName, schedule]) => {
|
|
362
|
+
const method = instance[methodName];
|
|
363
|
+
if (typeof method !== "function")
|
|
364
|
+
return;
|
|
365
|
+
if (typeof schedule === "number") {
|
|
366
|
+
const timer = setInterval(() => {
|
|
367
|
+
try {
|
|
368
|
+
method.call(instance);
|
|
369
|
+
} catch (err) {
|
|
370
|
+
console.error(`[Cron] ${constructor.name}.${methodName} threw`, err);
|
|
371
|
+
}
|
|
372
|
+
}, schedule);
|
|
373
|
+
this.cronJobs.push({ stop: () => clearInterval(timer) });
|
|
374
|
+
} else {
|
|
375
|
+
const fields = parseCronExpression(schedule);
|
|
376
|
+
let stopped = false;
|
|
377
|
+
const scheduleNext = () => {
|
|
378
|
+
if (stopped)
|
|
379
|
+
return;
|
|
380
|
+
const now = new Date;
|
|
381
|
+
const next = getNextCronTime(fields, now);
|
|
382
|
+
const delay = next.getTime() - now.getTime();
|
|
383
|
+
const timer = setTimeout(() => {
|
|
384
|
+
if (stopped)
|
|
385
|
+
return;
|
|
386
|
+
try {
|
|
387
|
+
method.call(instance);
|
|
388
|
+
} catch (err) {
|
|
389
|
+
console.error(`[Cron] ${constructor.name}.${methodName} threw`, err);
|
|
390
|
+
}
|
|
391
|
+
scheduleNext();
|
|
392
|
+
}, delay);
|
|
393
|
+
job.stop = () => {
|
|
394
|
+
stopped = true;
|
|
395
|
+
clearTimeout(timer);
|
|
396
|
+
};
|
|
397
|
+
};
|
|
398
|
+
const job = {
|
|
399
|
+
stop: () => {
|
|
400
|
+
stopped = true;
|
|
401
|
+
}
|
|
402
|
+
};
|
|
403
|
+
this.cronJobs.push(job);
|
|
404
|
+
scheduleNext();
|
|
405
|
+
}
|
|
406
|
+
});
|
|
407
|
+
}
|
|
310
408
|
instantiate(type, overrides = {}) {
|
|
311
409
|
if (typeof type !== "function") {
|
|
312
410
|
throw new Error("Service type must be a constructor or factory function");
|
|
@@ -342,6 +440,7 @@ class Container {
|
|
|
342
440
|
const instance = new type(...dependencies);
|
|
343
441
|
this.applyTelemetry(instance, type);
|
|
344
442
|
this.applyEvents(instance, type);
|
|
443
|
+
this.applyCron(instance, type);
|
|
345
444
|
const injectProperties = getMetadata(INJECT_METADATA_KEY, type) || {};
|
|
346
445
|
const protoInjectProperties = getMetadata(INJECT_METADATA_KEY, type.prototype) || {};
|
|
347
446
|
const allInjectProperties = {
|
|
@@ -462,34 +561,119 @@ function Container2(options = {}) {
|
|
|
462
561
|
}
|
|
463
562
|
|
|
464
563
|
// src/decorators.ts
|
|
564
|
+
var INJECT_METADATA_KEY2 = "di:inject";
|
|
565
|
+
var SCHEMAS = Symbol.for("proseva:component-schemas");
|
|
566
|
+
function isRecord(value) {
|
|
567
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
568
|
+
}
|
|
465
569
|
function Controller(options = {}) {
|
|
466
|
-
const
|
|
570
|
+
const containerDecorator = Container2(options);
|
|
467
571
|
return function(target) {
|
|
468
572
|
target.isController = true;
|
|
469
573
|
registry_default.addTarget(target);
|
|
470
|
-
|
|
574
|
+
containerDecorator(target);
|
|
575
|
+
const container2 = options.container ?? useContainer();
|
|
576
|
+
const rawMetadata = getOwnMetadata(INJECT_METADATA_KEY2, target);
|
|
577
|
+
const injectMetadata = isRecord(rawMetadata) ? rawMetadata : {};
|
|
578
|
+
for (const [propName, targetType] of Object.entries(injectMetadata)) {
|
|
579
|
+
if (!propName.startsWith("param_") && targetType) {
|
|
580
|
+
target[propName] = container2.resolve(targetType);
|
|
581
|
+
}
|
|
582
|
+
}
|
|
471
583
|
};
|
|
472
584
|
}
|
|
585
|
+
function extractSchemaRefs(obj, out) {
|
|
586
|
+
if (typeof obj !== "object" || obj === null)
|
|
587
|
+
return;
|
|
588
|
+
if (Array.isArray(obj)) {
|
|
589
|
+
for (const item of obj)
|
|
590
|
+
extractSchemaRefs(item, out);
|
|
591
|
+
return;
|
|
592
|
+
}
|
|
593
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
594
|
+
if (key === "$ref" && typeof value === "string") {
|
|
595
|
+
const match = /^#\/components\/schemas\/(.+)$/.exec(value);
|
|
596
|
+
if (match?.[1])
|
|
597
|
+
out.add(match[1]);
|
|
598
|
+
} else {
|
|
599
|
+
extractSchemaRefs(value, out);
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
}
|
|
473
603
|
function Endpoint(metadata) {
|
|
474
604
|
return function(target, propertyKey) {
|
|
475
605
|
if (propertyKey) {
|
|
476
606
|
const property = target[propertyKey];
|
|
477
607
|
const constructor = typeof target === "function" ? target : target.constructor;
|
|
478
608
|
registry_default.addTarget(constructor);
|
|
479
|
-
property
|
|
609
|
+
if (property) {
|
|
610
|
+
property.isEndpoint = true;
|
|
611
|
+
if (metadata) {
|
|
612
|
+
property.metadata = metadata;
|
|
613
|
+
}
|
|
614
|
+
}
|
|
480
615
|
if (metadata) {
|
|
481
|
-
|
|
616
|
+
const existing = constructor[SCHEMAS] ?? new Set;
|
|
617
|
+
extractSchemaRefs(metadata, existing);
|
|
618
|
+
constructor[SCHEMAS] = existing;
|
|
482
619
|
}
|
|
483
620
|
} else {
|
|
484
621
|
target.isEndpoint = true;
|
|
485
622
|
if (metadata) {
|
|
486
623
|
target.metadata = metadata;
|
|
624
|
+
const existing = target[SCHEMAS] ?? new Set;
|
|
625
|
+
extractSchemaRefs(metadata, existing);
|
|
626
|
+
target[SCHEMAS] = existing;
|
|
487
627
|
}
|
|
488
628
|
registry_default.addTarget(target);
|
|
489
629
|
}
|
|
490
630
|
};
|
|
491
631
|
}
|
|
492
632
|
// src/openapi.ts
|
|
633
|
+
function collectRefs(obj, out) {
|
|
634
|
+
if (typeof obj !== "object" || obj === null)
|
|
635
|
+
return;
|
|
636
|
+
if (Array.isArray(obj)) {
|
|
637
|
+
for (const item of obj)
|
|
638
|
+
collectRefs(item, out);
|
|
639
|
+
return;
|
|
640
|
+
}
|
|
641
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
642
|
+
if (key === "$ref" && typeof value === "string") {
|
|
643
|
+
const match = /^#\/components\/schemas\/(.+)$/.exec(value);
|
|
644
|
+
if (match?.[1])
|
|
645
|
+
out.add(match[1]);
|
|
646
|
+
} else {
|
|
647
|
+
collectRefs(value, out);
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
function resolveSchema(name, resolved, schemas) {
|
|
652
|
+
if (name in resolved)
|
|
653
|
+
return;
|
|
654
|
+
const schema = schemas[name];
|
|
655
|
+
if (!schema)
|
|
656
|
+
return;
|
|
657
|
+
resolved[name] = schema;
|
|
658
|
+
const transitive = new Set;
|
|
659
|
+
collectRefs(schema, transitive);
|
|
660
|
+
for (const dep of transitive) {
|
|
661
|
+
resolveSchema(dep, resolved, schemas);
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
function toOpenAPIPath(path) {
|
|
665
|
+
return path.replace(/:([a-zA-Z_]\w*)/g, "{$1}");
|
|
666
|
+
}
|
|
667
|
+
function extractPathParams(path) {
|
|
668
|
+
const names = [];
|
|
669
|
+
const re = /:([a-zA-Z_]\w*)/g;
|
|
670
|
+
let m;
|
|
671
|
+
while ((m = re.exec(path)) !== null) {
|
|
672
|
+
if (m[1])
|
|
673
|
+
names.push(m[1]);
|
|
674
|
+
}
|
|
675
|
+
return names;
|
|
676
|
+
}
|
|
493
677
|
function generateOpenAPI(options = {}, registryToUse = registry_default) {
|
|
494
678
|
const spec = {
|
|
495
679
|
openapi: "3.1.0",
|
|
@@ -504,12 +688,16 @@ function generateOpenAPI(options = {}, registryToUse = registry_default) {
|
|
|
504
688
|
}
|
|
505
689
|
};
|
|
506
690
|
const targets = registryToUse.getTargets();
|
|
691
|
+
const metaParamsMap = new Map;
|
|
507
692
|
for (const target of targets) {
|
|
508
693
|
for (const key of Object.getOwnPropertyNames(target)) {
|
|
509
694
|
const property = target[key];
|
|
510
695
|
if (property && property.isEndpoint) {
|
|
511
696
|
const path = property.path || "/unknown";
|
|
512
|
-
const method = property.method || "get";
|
|
697
|
+
const method = (property.method || "get").toLowerCase();
|
|
698
|
+
if (property.metadata?.parameters) {
|
|
699
|
+
metaParamsMap.set(`${path}|${method}`, property.metadata.parameters);
|
|
700
|
+
}
|
|
513
701
|
if (!spec.paths[path]) {
|
|
514
702
|
spec.paths[path] = {};
|
|
515
703
|
}
|
|
@@ -527,6 +715,36 @@ function generateOpenAPI(options = {}, registryToUse = registry_default) {
|
|
|
527
715
|
}
|
|
528
716
|
}
|
|
529
717
|
}
|
|
718
|
+
const rewrittenPaths = {};
|
|
719
|
+
for (const [rawPath, methods] of Object.entries(spec.paths)) {
|
|
720
|
+
const openApiPath = toOpenAPIPath(rawPath);
|
|
721
|
+
const pathParamNames = extractPathParams(rawPath);
|
|
722
|
+
const autoParams = pathParamNames.map((name) => ({
|
|
723
|
+
name,
|
|
724
|
+
in: "path",
|
|
725
|
+
required: true,
|
|
726
|
+
schema: { type: "string" }
|
|
727
|
+
}));
|
|
728
|
+
rewrittenPaths[openApiPath] ??= {};
|
|
729
|
+
for (const [method, operation] of Object.entries(methods)) {
|
|
730
|
+
const decoratorParams = metaParamsMap.get(`${rawPath}|${method}`) ?? [];
|
|
731
|
+
if (autoParams.length > 0 || decoratorParams.length > 0) {
|
|
732
|
+
operation.parameters = [...autoParams, ...decoratorParams];
|
|
733
|
+
}
|
|
734
|
+
rewrittenPaths[openApiPath][method] = operation;
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
spec.paths = rewrittenPaths;
|
|
738
|
+
const resolved = {};
|
|
739
|
+
for (const target of targets) {
|
|
740
|
+
const refs = target[SCHEMAS];
|
|
741
|
+
if (!refs)
|
|
742
|
+
continue;
|
|
743
|
+
for (const name of refs) {
|
|
744
|
+
resolveSchema(name, resolved, options.schemas || {});
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
spec.components.schemas = resolved;
|
|
530
748
|
return spec;
|
|
531
749
|
}
|
|
532
750
|
export {
|
|
@@ -534,6 +752,7 @@ export {
|
|
|
534
752
|
json,
|
|
535
753
|
generateOpenAPI,
|
|
536
754
|
TypedRouter,
|
|
755
|
+
SCHEMAS,
|
|
537
756
|
Registry,
|
|
538
757
|
Endpoint,
|
|
539
758
|
Controller
|