@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/dist/src/cli.js CHANGED
@@ -18,7 +18,610 @@ var GLOBAL_KEY = Symbol.for("@di-framework/http-registry");
18
18
  var registry = globalThis[GLOBAL_KEY] ?? (globalThis[GLOBAL_KEY] = new Registry);
19
19
  var registry_default = registry;
20
20
 
21
+ // ../di-framework/dist/container.js
22
+ var INJECT_METADATA_KEY = "di:inject";
23
+ var DESIGN_PARAM_TYPES_KEY = "design:paramtypes";
24
+ var TELEMETRY_METADATA_KEY = "di:telemetry";
25
+ var TELEMETRY_LISTENER_METADATA_KEY = "di:telemetry-listener";
26
+ var PUBLISHER_METADATA_KEY = "di:publisher";
27
+ var SUBSCRIBER_METADATA_KEY = "di:subscriber";
28
+ var CRON_METADATA_KEY = "di:cron";
29
+ var metadataStore = new Map;
30
+ function defineMetadata(key, value, target) {
31
+ if (!metadataStore.has(target)) {
32
+ metadataStore.set(target, new Map);
33
+ }
34
+ metadataStore.get(target).set(key, value);
35
+ }
36
+ function getMetadata(key, target) {
37
+ return metadataStore.get(target)?.get(key);
38
+ }
39
+ function getOwnMetadata(key, target) {
40
+ return getMetadata(key, target);
41
+ }
42
+ function parseCronField(field, min, max) {
43
+ if (field === "*") {
44
+ const out = [];
45
+ for (let i = min;i <= max; i++)
46
+ out.push(i);
47
+ return out;
48
+ }
49
+ if (field.startsWith("*/")) {
50
+ const step = parseInt(field.slice(2), 10);
51
+ const out = [];
52
+ for (let i = min;i <= max; i++) {
53
+ if (i % step === 0)
54
+ out.push(i);
55
+ }
56
+ return out;
57
+ }
58
+ if (field.includes(",")) {
59
+ return field.split(",").map((s) => parseInt(s.trim(), 10));
60
+ }
61
+ if (field.includes("-")) {
62
+ const [lo = 0, hi = 0] = field.split("-").map((s) => parseInt(s.trim(), 10));
63
+ const out = [];
64
+ for (let i = lo;i <= hi; i++)
65
+ out.push(i);
66
+ return out;
67
+ }
68
+ return [parseInt(field, 10)];
69
+ }
70
+ function parseCronExpression(expr) {
71
+ const parts = expr.trim().split(/\s+/);
72
+ if (parts.length !== 5)
73
+ throw new Error(`Invalid cron expression "${expr}": expected 5 fields (minute hour dayOfMonth month dayOfWeek)`);
74
+ return {
75
+ minute: parseCronField(parts[0], 0, 59),
76
+ hour: parseCronField(parts[1], 0, 23),
77
+ dayOfMonth: parseCronField(parts[2], 1, 31),
78
+ month: parseCronField(parts[3], 1, 12),
79
+ dayOfWeek: parseCronField(parts[4], 0, 6)
80
+ };
81
+ }
82
+ function getNextCronTime(fields, from) {
83
+ const next = new Date(from);
84
+ next.setSeconds(0, 0);
85
+ next.setMinutes(next.getMinutes() + 1);
86
+ for (let i = 0;i < 1051920; i++) {
87
+ 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())) {
88
+ return next;
89
+ }
90
+ next.setMinutes(next.getMinutes() + 1);
91
+ }
92
+ throw new Error(`No matching cron time found for expression within 2 years`);
93
+ }
94
+
95
+ class Container {
96
+ services = new Map;
97
+ resolutionStack = new Set;
98
+ listeners = new Map;
99
+ cronJobs = [];
100
+ register(serviceClass, options = { singleton: true }) {
101
+ const name = serviceClass.name;
102
+ this.services.set(name, {
103
+ type: serviceClass,
104
+ singleton: options.singleton ?? true
105
+ });
106
+ this.services.set(serviceClass, {
107
+ type: serviceClass,
108
+ singleton: options.singleton ?? true
109
+ });
110
+ this.emit("registered", {
111
+ key: serviceClass,
112
+ singleton: options.singleton ?? true,
113
+ kind: "class"
114
+ });
115
+ return this;
116
+ }
117
+ registerFactory(name, factory, options = { singleton: true }) {
118
+ this.services.set(name, {
119
+ type: factory,
120
+ singleton: options.singleton ?? true
121
+ });
122
+ this.emit("registered", {
123
+ key: name,
124
+ singleton: options.singleton ?? true,
125
+ kind: "factory"
126
+ });
127
+ return this;
128
+ }
129
+ resolve(serviceClass) {
130
+ const key = typeof serviceClass === "string" ? serviceClass : serviceClass;
131
+ const keyStr = typeof serviceClass === "string" ? serviceClass : serviceClass.name;
132
+ if (this.resolutionStack.has(key)) {
133
+ throw new Error(`Circular dependency detected while resolving ${keyStr}. Stack: ${Array.from(this.resolutionStack).join(" -> ")} -> ${keyStr}`);
134
+ }
135
+ const definition = this.services.get(key);
136
+ if (!definition) {
137
+ throw new Error(`Service '${keyStr}' is not registered in the DI container`);
138
+ }
139
+ const wasCached = definition.singleton && !!definition.instance;
140
+ if (definition.singleton && definition.instance) {
141
+ this.emit("resolved", {
142
+ key,
143
+ instance: definition.instance,
144
+ singleton: true,
145
+ fromCache: true
146
+ });
147
+ return definition.instance;
148
+ }
149
+ this.resolutionStack.add(key);
150
+ try {
151
+ const instance = this.instantiate(definition.type);
152
+ if (definition.singleton) {
153
+ definition.instance = instance;
154
+ }
155
+ this.emit("resolved", {
156
+ key,
157
+ instance,
158
+ singleton: definition.singleton,
159
+ fromCache: wasCached
160
+ });
161
+ return instance;
162
+ } finally {
163
+ this.resolutionStack.delete(key);
164
+ }
165
+ }
166
+ construct(serviceClass, overrides = {}) {
167
+ const keyStr = serviceClass.name;
168
+ if (this.resolutionStack.has(serviceClass)) {
169
+ throw new Error(`Circular dependency detected while constructing ${keyStr}. Stack: ${Array.from(this.resolutionStack).join(" -> ")} -> ${keyStr}`);
170
+ }
171
+ this.resolutionStack.add(serviceClass);
172
+ try {
173
+ const instance = this.instantiate(serviceClass, overrides);
174
+ this.emit("constructed", { key: serviceClass, instance, overrides });
175
+ return instance;
176
+ } finally {
177
+ this.resolutionStack.delete(serviceClass);
178
+ }
179
+ }
180
+ has(serviceClass) {
181
+ return this.services.has(serviceClass);
182
+ }
183
+ clear() {
184
+ const count = this.services.size;
185
+ this.stopCronJobs();
186
+ this.services.clear();
187
+ this.emit("cleared", { count });
188
+ }
189
+ stopCronJobs() {
190
+ for (const job of this.cronJobs) {
191
+ job.stop();
192
+ }
193
+ this.cronJobs = [];
194
+ }
195
+ getServiceNames() {
196
+ const names = new Set;
197
+ this.services.forEach((_, key) => {
198
+ if (typeof key === "string") {
199
+ names.add(key);
200
+ }
201
+ });
202
+ return Array.from(names);
203
+ }
204
+ fork(options = {}) {
205
+ const clone = new Container;
206
+ this.services.forEach((def, key) => {
207
+ clone.services.set(key, {
208
+ ...def,
209
+ instance: options.carrySingletons ? def.instance : undefined
210
+ });
211
+ });
212
+ return clone;
213
+ }
214
+ on(event, listener) {
215
+ if (!this.listeners.has(event)) {
216
+ this.listeners.set(event, new Set);
217
+ }
218
+ this.listeners.get(event).add(listener);
219
+ return () => this.off(event, listener);
220
+ }
221
+ off(event, listener) {
222
+ this.listeners.get(event)?.delete(listener);
223
+ }
224
+ emit(event, payload) {
225
+ const listeners = this.listeners.get(event);
226
+ if (!listeners || listeners.size === 0)
227
+ return;
228
+ listeners.forEach((listener) => {
229
+ try {
230
+ listener(payload);
231
+ } catch (err) {
232
+ console.error(`[Container] listener for '${String(event)}' threw`, err);
233
+ }
234
+ });
235
+ }
236
+ applyEvents(instance, constructor) {
237
+ const className = constructor.name;
238
+ const subscriberMap = getMetadata(SUBSCRIBER_METADATA_KEY, constructor.prototype) || {};
239
+ Object.entries(subscriberMap).forEach(([event, methods]) => {
240
+ methods.forEach((methodName) => {
241
+ const method = instance[methodName];
242
+ if (typeof method === "function") {
243
+ this.on(event, (payload) => {
244
+ try {
245
+ method.call(instance, payload);
246
+ } catch (err) {
247
+ console.error(`[Container] Subscriber '${className}.${methodName}' for event '${event}' threw`, err);
248
+ }
249
+ });
250
+ }
251
+ });
252
+ });
253
+ const publisherMethods = getMetadata(PUBLISHER_METADATA_KEY, constructor.prototype) || {};
254
+ Object.entries(publisherMethods).forEach(([methodName, options]) => {
255
+ const originalMethod = instance[methodName];
256
+ if (typeof originalMethod === "function") {
257
+ const self = this;
258
+ const phase = options.phase ?? "after";
259
+ instance[methodName] = function(...args) {
260
+ const startTime = Date.now();
261
+ const emit = (result, error) => {
262
+ const payload = {
263
+ className,
264
+ methodName,
265
+ args,
266
+ startTime,
267
+ endTime: Date.now(),
268
+ result,
269
+ error
270
+ };
271
+ if (options.logging) {
272
+ const duration = payload.endTime - payload.startTime;
273
+ const status = error ? `ERROR: ${error && error.message ? error.message : String(error)}` : "SUCCESS";
274
+ console.log(`[Publisher] ${className}.${methodName} -> '${options.event}' - ${status} (${duration}ms)`);
275
+ }
276
+ self.emit(options.event, payload);
277
+ };
278
+ try {
279
+ if (phase === "before" || phase === "both") {
280
+ emit(undefined, undefined);
281
+ }
282
+ const result = originalMethod.apply(this, args);
283
+ if (result instanceof Promise) {
284
+ return result.then((val) => {
285
+ if (phase === "after" || phase === "both") {
286
+ emit(val, undefined);
287
+ }
288
+ return val;
289
+ }).catch((err) => {
290
+ emit(undefined, err);
291
+ throw err;
292
+ });
293
+ }
294
+ if (phase === "after" || phase === "both") {
295
+ emit(result, undefined);
296
+ }
297
+ return result;
298
+ } catch (err) {
299
+ emit(undefined, err);
300
+ throw err;
301
+ }
302
+ };
303
+ }
304
+ });
305
+ }
306
+ applyCron(instance, constructor) {
307
+ const cronMethods = getMetadata(CRON_METADATA_KEY, constructor.prototype) || {};
308
+ Object.entries(cronMethods).forEach(([methodName, schedule]) => {
309
+ const method = instance[methodName];
310
+ if (typeof method !== "function")
311
+ return;
312
+ if (typeof schedule === "number") {
313
+ const timer = setInterval(() => {
314
+ try {
315
+ method.call(instance);
316
+ } catch (err) {
317
+ console.error(`[Cron] ${constructor.name}.${methodName} threw`, err);
318
+ }
319
+ }, schedule);
320
+ this.cronJobs.push({ stop: () => clearInterval(timer) });
321
+ } else {
322
+ const fields = parseCronExpression(schedule);
323
+ let stopped = false;
324
+ const scheduleNext = () => {
325
+ if (stopped)
326
+ return;
327
+ const now = new Date;
328
+ const next = getNextCronTime(fields, now);
329
+ const delay = next.getTime() - now.getTime();
330
+ const timer = setTimeout(() => {
331
+ if (stopped)
332
+ return;
333
+ try {
334
+ method.call(instance);
335
+ } catch (err) {
336
+ console.error(`[Cron] ${constructor.name}.${methodName} threw`, err);
337
+ }
338
+ scheduleNext();
339
+ }, delay);
340
+ job.stop = () => {
341
+ stopped = true;
342
+ clearTimeout(timer);
343
+ };
344
+ };
345
+ const job = {
346
+ stop: () => {
347
+ stopped = true;
348
+ }
349
+ };
350
+ this.cronJobs.push(job);
351
+ scheduleNext();
352
+ }
353
+ });
354
+ }
355
+ instantiate(type, overrides = {}) {
356
+ if (typeof type !== "function") {
357
+ throw new Error("Service type must be a constructor or factory function");
358
+ }
359
+ if (!this.isClass(type)) {
360
+ return type();
361
+ }
362
+ const paramTypes = getMetadata(DESIGN_PARAM_TYPES_KEY, type) || [];
363
+ const paramNames = this.getConstructorParamNames(type);
364
+ const dependencies = [];
365
+ const injectMetadata = getOwnMetadata(INJECT_METADATA_KEY, type) || {};
366
+ const paramCount = Math.max(paramTypes.length, paramNames.length);
367
+ for (let i = 0;i < paramCount; i++) {
368
+ if (Object.prototype.hasOwnProperty.call(overrides, i)) {
369
+ dependencies.push(overrides[i]);
370
+ continue;
371
+ }
372
+ const paramType = paramTypes[i];
373
+ const paramName = paramNames[i];
374
+ const paramInjectTarget = injectMetadata[`param_${i}`];
375
+ if (paramInjectTarget) {
376
+ dependencies.push(this.resolve(paramInjectTarget));
377
+ } else if (paramType && paramType !== Object) {
378
+ if (this.has(paramType)) {
379
+ dependencies.push(this.resolve(paramType));
380
+ } else if (this.has(paramType.name)) {
381
+ dependencies.push(this.resolve(paramType.name));
382
+ } else {
383
+ throw new Error(`Cannot resolve dependency of type ${paramType.name} for parameter '${paramName}' in ${type.name}`);
384
+ }
385
+ } else {}
386
+ }
387
+ const instance = new type(...dependencies);
388
+ this.applyTelemetry(instance, type);
389
+ this.applyEvents(instance, type);
390
+ this.applyCron(instance, type);
391
+ const injectProperties = getMetadata(INJECT_METADATA_KEY, type) || {};
392
+ const protoInjectProperties = getMetadata(INJECT_METADATA_KEY, type.prototype) || {};
393
+ const allInjectProperties = {
394
+ ...injectProperties,
395
+ ...protoInjectProperties
396
+ };
397
+ Object.entries(allInjectProperties).forEach(([propName, targetType]) => {
398
+ if (!propName.startsWith("param_") && targetType) {
399
+ try {
400
+ instance[propName] = this.resolve(targetType);
401
+ } catch (error) {
402
+ console.warn(`Failed to inject property '${propName}' on ${type.name}:`, error);
403
+ }
404
+ }
405
+ });
406
+ return instance;
407
+ }
408
+ applyTelemetry(instance, constructor) {
409
+ const className = constructor.name;
410
+ const listenerMethods = getMetadata(TELEMETRY_LISTENER_METADATA_KEY, constructor.prototype) || [];
411
+ listenerMethods.forEach((methodName) => {
412
+ const method = instance[methodName];
413
+ if (typeof method === "function") {
414
+ this.on("telemetry", (payload) => {
415
+ try {
416
+ method.call(instance, payload);
417
+ } catch (err) {
418
+ console.error(`[Container] TelemetryListener '${className}.${methodName}' threw`, err);
419
+ }
420
+ });
421
+ }
422
+ });
423
+ const telemetryMethods = getMetadata(TELEMETRY_METADATA_KEY, constructor.prototype) || {};
424
+ Object.entries(telemetryMethods).forEach(([methodName, options]) => {
425
+ const originalMethod = instance[methodName];
426
+ if (typeof originalMethod === "function") {
427
+ const self = this;
428
+ instance[methodName] = function(...args) {
429
+ const startTime = Date.now();
430
+ const emit = (result, error) => {
431
+ const payload = {
432
+ className,
433
+ methodName,
434
+ args,
435
+ startTime,
436
+ endTime: Date.now(),
437
+ result,
438
+ error
439
+ };
440
+ if (options.logging) {
441
+ const duration = payload.endTime - payload.startTime;
442
+ const status = error ? `ERROR: ${error.message || error}` : "SUCCESS";
443
+ console.log(`[Telemetry] ${className}.${methodName} - ${status} (${duration}ms)`);
444
+ }
445
+ self.emit("telemetry", payload);
446
+ };
447
+ try {
448
+ const result = originalMethod.apply(this, args);
449
+ if (result instanceof Promise) {
450
+ return result.then((val) => {
451
+ emit(val);
452
+ return val;
453
+ }).catch((err) => {
454
+ emit(undefined, err);
455
+ throw err;
456
+ });
457
+ }
458
+ emit(result);
459
+ return result;
460
+ } catch (err) {
461
+ emit(undefined, err);
462
+ throw err;
463
+ }
464
+ };
465
+ }
466
+ });
467
+ }
468
+ isClass(func) {
469
+ return typeof func === "function" && func.prototype && func.prototype.constructor === func;
470
+ }
471
+ getConstructorParamNames(target) {
472
+ const funcStr = target.toString();
473
+ const match = funcStr.match(/constructor\s*\(([^)]*)\)/);
474
+ if (!match || !match[1])
475
+ return [];
476
+ const paramsStr = match[1];
477
+ return paramsStr.split(",").map((param) => {
478
+ const trimmed = param.trim();
479
+ const withoutDefault = trimmed.split("=")[0] || "";
480
+ const withoutType = withoutDefault.split(":")[0] || "";
481
+ return withoutType.trim();
482
+ }).filter((param) => param);
483
+ }
484
+ extractParamTypesFromSource(target) {
485
+ const funcStr = target.toString();
486
+ const decoratorMatch = funcStr.match(/__decorate\(\[\s*(?:\w+\s*\([^)]*\),?\s*)*__param\((\d+),\s*(\w+)\([^)]*\)\)/g);
487
+ if (decoratorMatch) {
488
+ return [];
489
+ }
490
+ return [];
491
+ }
492
+ }
493
+ var container = new Container;
494
+ function useContainer() {
495
+ return container;
496
+ }
497
+
498
+ // ../di-framework/dist/decorators.js
499
+ var INJECTABLE_METADATA_KEY = "di:injectable";
500
+ function Container2(options = {}) {
501
+ return function(constructor) {
502
+ const container2 = options.container ?? useContainer();
503
+ const singleton = options.singleton ?? true;
504
+ defineMetadata(INJECTABLE_METADATA_KEY, true, constructor);
505
+ container2.register(constructor, { singleton });
506
+ return constructor;
507
+ };
508
+ }
509
+
510
+ // src/decorators.ts
511
+ var INJECT_METADATA_KEY2 = "di:inject";
512
+ var SCHEMAS = Symbol.for("proseva:component-schemas");
513
+ function isRecord(value) {
514
+ return typeof value === "object" && value !== null && !Array.isArray(value);
515
+ }
516
+ function Controller(options = {}) {
517
+ const containerDecorator = Container2(options);
518
+ return function(target) {
519
+ target.isController = true;
520
+ registry_default.addTarget(target);
521
+ containerDecorator(target);
522
+ const container2 = options.container ?? useContainer();
523
+ const rawMetadata = getOwnMetadata(INJECT_METADATA_KEY2, target);
524
+ const injectMetadata = isRecord(rawMetadata) ? rawMetadata : {};
525
+ for (const [propName, targetType] of Object.entries(injectMetadata)) {
526
+ if (!propName.startsWith("param_") && targetType) {
527
+ target[propName] = container2.resolve(targetType);
528
+ }
529
+ }
530
+ };
531
+ }
532
+ function extractSchemaRefs(obj, out) {
533
+ if (typeof obj !== "object" || obj === null)
534
+ return;
535
+ if (Array.isArray(obj)) {
536
+ for (const item of obj)
537
+ extractSchemaRefs(item, out);
538
+ return;
539
+ }
540
+ for (const [key, value] of Object.entries(obj)) {
541
+ if (key === "$ref" && typeof value === "string") {
542
+ const match = /^#\/components\/schemas\/(.+)$/.exec(value);
543
+ if (match?.[1])
544
+ out.add(match[1]);
545
+ } else {
546
+ extractSchemaRefs(value, out);
547
+ }
548
+ }
549
+ }
550
+ function Endpoint(metadata) {
551
+ return function(target, propertyKey) {
552
+ if (propertyKey) {
553
+ const property = target[propertyKey];
554
+ const constructor = typeof target === "function" ? target : target.constructor;
555
+ registry_default.addTarget(constructor);
556
+ if (property) {
557
+ property.isEndpoint = true;
558
+ if (metadata) {
559
+ property.metadata = metadata;
560
+ }
561
+ }
562
+ if (metadata) {
563
+ const existing = constructor[SCHEMAS] ?? new Set;
564
+ extractSchemaRefs(metadata, existing);
565
+ constructor[SCHEMAS] = existing;
566
+ }
567
+ } else {
568
+ target.isEndpoint = true;
569
+ if (metadata) {
570
+ target.metadata = metadata;
571
+ const existing = target[SCHEMAS] ?? new Set;
572
+ extractSchemaRefs(metadata, existing);
573
+ target[SCHEMAS] = existing;
574
+ }
575
+ registry_default.addTarget(target);
576
+ }
577
+ };
578
+ }
579
+
21
580
  // src/openapi.ts
581
+ function collectRefs(obj, out) {
582
+ if (typeof obj !== "object" || obj === null)
583
+ return;
584
+ if (Array.isArray(obj)) {
585
+ for (const item of obj)
586
+ collectRefs(item, out);
587
+ return;
588
+ }
589
+ for (const [key, value] of Object.entries(obj)) {
590
+ if (key === "$ref" && typeof value === "string") {
591
+ const match = /^#\/components\/schemas\/(.+)$/.exec(value);
592
+ if (match?.[1])
593
+ out.add(match[1]);
594
+ } else {
595
+ collectRefs(value, out);
596
+ }
597
+ }
598
+ }
599
+ function resolveSchema(name, resolved, schemas) {
600
+ if (name in resolved)
601
+ return;
602
+ const schema = schemas[name];
603
+ if (!schema)
604
+ return;
605
+ resolved[name] = schema;
606
+ const transitive = new Set;
607
+ collectRefs(schema, transitive);
608
+ for (const dep of transitive) {
609
+ resolveSchema(dep, resolved, schemas);
610
+ }
611
+ }
612
+ function toOpenAPIPath(path) {
613
+ return path.replace(/:([a-zA-Z_]\w*)/g, "{$1}");
614
+ }
615
+ function extractPathParams(path) {
616
+ const names = [];
617
+ const re = /:([a-zA-Z_]\w*)/g;
618
+ let m;
619
+ while ((m = re.exec(path)) !== null) {
620
+ if (m[1])
621
+ names.push(m[1]);
622
+ }
623
+ return names;
624
+ }
22
625
  function generateOpenAPI(options = {}, registryToUse = registry_default) {
23
626
  const spec = {
24
627
  openapi: "3.1.0",
@@ -33,12 +636,16 @@ function generateOpenAPI(options = {}, registryToUse = registry_default) {
33
636
  }
34
637
  };
35
638
  const targets = registryToUse.getTargets();
639
+ const metaParamsMap = new Map;
36
640
  for (const target of targets) {
37
641
  for (const key of Object.getOwnPropertyNames(target)) {
38
642
  const property = target[key];
39
643
  if (property && property.isEndpoint) {
40
644
  const path = property.path || "/unknown";
41
- const method = property.method || "get";
645
+ const method = (property.method || "get").toLowerCase();
646
+ if (property.metadata?.parameters) {
647
+ metaParamsMap.set(`${path}|${method}`, property.metadata.parameters);
648
+ }
42
649
  if (!spec.paths[path]) {
43
650
  spec.paths[path] = {};
44
651
  }
@@ -56,6 +663,36 @@ function generateOpenAPI(options = {}, registryToUse = registry_default) {
56
663
  }
57
664
  }
58
665
  }
666
+ const rewrittenPaths = {};
667
+ for (const [rawPath, methods] of Object.entries(spec.paths)) {
668
+ const openApiPath = toOpenAPIPath(rawPath);
669
+ const pathParamNames = extractPathParams(rawPath);
670
+ const autoParams = pathParamNames.map((name) => ({
671
+ name,
672
+ in: "path",
673
+ required: true,
674
+ schema: { type: "string" }
675
+ }));
676
+ rewrittenPaths[openApiPath] ??= {};
677
+ for (const [method, operation] of Object.entries(methods)) {
678
+ const decoratorParams = metaParamsMap.get(`${rawPath}|${method}`) ?? [];
679
+ if (autoParams.length > 0 || decoratorParams.length > 0) {
680
+ operation.parameters = [...autoParams, ...decoratorParams];
681
+ }
682
+ rewrittenPaths[openApiPath][method] = operation;
683
+ }
684
+ }
685
+ spec.paths = rewrittenPaths;
686
+ const resolved = {};
687
+ for (const target of targets) {
688
+ const refs = target[SCHEMAS];
689
+ if (!refs)
690
+ continue;
691
+ for (const name of refs) {
692
+ resolveSchema(name, resolved, options.schemas || {});
693
+ }
694
+ }
695
+ spec.components.schemas = resolved;
59
696
  return spec;
60
697
  }
61
698
 
@@ -1,3 +1,4 @@
1
+ export declare const SCHEMAS: unique symbol;
1
2
  export declare function Controller(options?: {
2
3
  singleton?: boolean;
3
4
  container?: any;
@@ -5,6 +6,7 @@ export declare function Controller(options?: {
5
6
  export declare function Endpoint(metadata?: {
6
7
  summary?: string;
7
8
  description?: string;
9
+ parameters?: unknown[];
8
10
  requestBody?: any;
9
11
  responses?: Record<string, any>;
10
12
  }): (target: any, propertyKey?: string) => void;
@@ -3,5 +3,6 @@ export type OpenAPIOptions = {
3
3
  version?: string;
4
4
  description?: string;
5
5
  outputPath?: string;
6
+ schemas?: Record<string, unknown>;
6
7
  };
7
8
  export declare function generateOpenAPI(options?: OpenAPIOptions, registryToUse?: import("./registry.ts").Registry): any;
@@ -1,11 +1,11 @@
1
- import { Router, type IRequest } from "itty-router";
1
+ import { Router, type IRequest } from 'itty-router';
2
2
  /** Marker for body "shape + content-type" */
3
3
  export type Json<T> = {
4
- readonly __kind: "json";
4
+ readonly __kind: 'json';
5
5
  readonly __type?: T;
6
6
  };
7
7
  export type Multipart<T> = {
8
- readonly __kind: "multipart";
8
+ readonly __kind: 'multipart';
9
9
  readonly __type?: T;
10
10
  };
11
11
  export type PathParams<T> = {
@@ -26,7 +26,7 @@ type ContentOf<BodySpec> = BodySpec extends Json<infer T> ? T : BodySpec extends
26
26
  type PathParamsOf<BodySpec> = BodySpec extends PathParams<infer T> ? T : Record<string, string>;
27
27
  type QueryParamsOf<BodySpec> = BodySpec extends QueryParams<infer T> ? T : Record<string, string | string[] | undefined>;
28
28
  /** The actual request type your handlers receive */
29
- export type TypedRequest<ReqSpec> = Omit<IRequest, "params" | "query"> & {
29
+ export type TypedRequest<ReqSpec> = Omit<IRequest, 'params' | 'query'> & {
30
30
  content: ContentOf<ReqSpec extends RequestSpec<infer B> ? B : never>;
31
31
  params: PathParamsOf<ReqSpec extends RequestSpec<infer B> ? B : never>;
32
32
  query: QueryParamsOf<ReqSpec extends RequestSpec<infer B> ? B : never>;