@di-framework/di-framework-http 0.0.0-prerelease.308 → 0.0.0-prerelease.309
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 +232 -17
- package/dist/src/cli.js +634 -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 +59 -9
- 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,14 +561,45 @@ 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) {
|
|
@@ -479,17 +609,67 @@ function Endpoint(metadata) {
|
|
|
479
609
|
property.isEndpoint = true;
|
|
480
610
|
if (metadata) {
|
|
481
611
|
property.metadata = metadata;
|
|
612
|
+
const existing = constructor[SCHEMAS] ?? new Set;
|
|
613
|
+
extractSchemaRefs(metadata, existing);
|
|
614
|
+
constructor[SCHEMAS] = existing;
|
|
482
615
|
}
|
|
483
616
|
} else {
|
|
484
617
|
target.isEndpoint = true;
|
|
485
618
|
if (metadata) {
|
|
486
619
|
target.metadata = metadata;
|
|
620
|
+
const existing = target[SCHEMAS] ?? new Set;
|
|
621
|
+
extractSchemaRefs(metadata, existing);
|
|
622
|
+
target[SCHEMAS] = existing;
|
|
487
623
|
}
|
|
488
624
|
registry_default.addTarget(target);
|
|
489
625
|
}
|
|
490
626
|
};
|
|
491
627
|
}
|
|
492
628
|
// src/openapi.ts
|
|
629
|
+
function collectRefs(obj, out) {
|
|
630
|
+
if (typeof obj !== "object" || obj === null)
|
|
631
|
+
return;
|
|
632
|
+
if (Array.isArray(obj)) {
|
|
633
|
+
for (const item of obj)
|
|
634
|
+
collectRefs(item, out);
|
|
635
|
+
return;
|
|
636
|
+
}
|
|
637
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
638
|
+
if (key === "$ref" && typeof value === "string") {
|
|
639
|
+
const match = /^#\/components\/schemas\/(.+)$/.exec(value);
|
|
640
|
+
if (match?.[1])
|
|
641
|
+
out.add(match[1]);
|
|
642
|
+
} else {
|
|
643
|
+
collectRefs(value, out);
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
function resolveSchema(name, resolved, schemas) {
|
|
648
|
+
if (name in resolved)
|
|
649
|
+
return;
|
|
650
|
+
const schema = schemas[name];
|
|
651
|
+
if (!schema)
|
|
652
|
+
return;
|
|
653
|
+
resolved[name] = schema;
|
|
654
|
+
const transitive = new Set;
|
|
655
|
+
collectRefs(schema, transitive);
|
|
656
|
+
for (const dep of transitive) {
|
|
657
|
+
resolveSchema(dep, resolved, schemas);
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
function toOpenAPIPath(path) {
|
|
661
|
+
return path.replace(/:([a-zA-Z_]\w*)/g, "{$1}");
|
|
662
|
+
}
|
|
663
|
+
function extractPathParams(path) {
|
|
664
|
+
const names = [];
|
|
665
|
+
const re = /:([a-zA-Z_]\w*)/g;
|
|
666
|
+
let m;
|
|
667
|
+
while ((m = re.exec(path)) !== null) {
|
|
668
|
+
if (m[1])
|
|
669
|
+
names.push(m[1]);
|
|
670
|
+
}
|
|
671
|
+
return names;
|
|
672
|
+
}
|
|
493
673
|
function generateOpenAPI(options = {}, registryToUse = registry_default) {
|
|
494
674
|
const spec = {
|
|
495
675
|
openapi: "3.1.0",
|
|
@@ -504,12 +684,16 @@ function generateOpenAPI(options = {}, registryToUse = registry_default) {
|
|
|
504
684
|
}
|
|
505
685
|
};
|
|
506
686
|
const targets = registryToUse.getTargets();
|
|
687
|
+
const metaParamsMap = new Map;
|
|
507
688
|
for (const target of targets) {
|
|
508
689
|
for (const key of Object.getOwnPropertyNames(target)) {
|
|
509
690
|
const property = target[key];
|
|
510
691
|
if (property && property.isEndpoint) {
|
|
511
692
|
const path = property.path || "/unknown";
|
|
512
|
-
const method = property.method || "get";
|
|
693
|
+
const method = (property.method || "get").toLowerCase();
|
|
694
|
+
if (property.metadata?.parameters) {
|
|
695
|
+
metaParamsMap.set(`${path}|${method}`, property.metadata.parameters);
|
|
696
|
+
}
|
|
513
697
|
if (!spec.paths[path]) {
|
|
514
698
|
spec.paths[path] = {};
|
|
515
699
|
}
|
|
@@ -527,6 +711,36 @@ function generateOpenAPI(options = {}, registryToUse = registry_default) {
|
|
|
527
711
|
}
|
|
528
712
|
}
|
|
529
713
|
}
|
|
714
|
+
const rewrittenPaths = {};
|
|
715
|
+
for (const [rawPath, methods] of Object.entries(spec.paths)) {
|
|
716
|
+
const openApiPath = toOpenAPIPath(rawPath);
|
|
717
|
+
const pathParamNames = extractPathParams(rawPath);
|
|
718
|
+
const autoParams = pathParamNames.map((name) => ({
|
|
719
|
+
name,
|
|
720
|
+
in: "path",
|
|
721
|
+
required: true,
|
|
722
|
+
schema: { type: "string" }
|
|
723
|
+
}));
|
|
724
|
+
rewrittenPaths[openApiPath] ??= {};
|
|
725
|
+
for (const [method, operation] of Object.entries(methods)) {
|
|
726
|
+
const decoratorParams = metaParamsMap.get(`${rawPath}|${method}`) ?? [];
|
|
727
|
+
if (autoParams.length > 0 || decoratorParams.length > 0) {
|
|
728
|
+
operation.parameters = [...autoParams, ...decoratorParams];
|
|
729
|
+
}
|
|
730
|
+
rewrittenPaths[openApiPath][method] = operation;
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
spec.paths = rewrittenPaths;
|
|
734
|
+
const resolved = {};
|
|
735
|
+
for (const target of targets) {
|
|
736
|
+
const refs = target[SCHEMAS];
|
|
737
|
+
if (!refs)
|
|
738
|
+
continue;
|
|
739
|
+
for (const name of refs) {
|
|
740
|
+
resolveSchema(name, resolved, options.schemas || {});
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
spec.components.schemas = resolved;
|
|
530
744
|
return spec;
|
|
531
745
|
}
|
|
532
746
|
export {
|
|
@@ -534,6 +748,7 @@ export {
|
|
|
534
748
|
json,
|
|
535
749
|
generateOpenAPI,
|
|
536
750
|
TypedRouter,
|
|
751
|
+
SCHEMAS,
|
|
537
752
|
Registry,
|
|
538
753
|
Endpoint,
|
|
539
754
|
Controller
|