@bepalo/spine 1.4.13 → 1.5.15
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/cjs/generator.d.ts.map +1 -1
- package/dist/cjs/generator.js +39 -3
- package/dist/cjs/generator.js.map +1 -1
- package/dist/cjs/router.d.ts +91 -32
- package/dist/cjs/router.d.ts.map +1 -1
- package/dist/cjs/router.js +387 -92
- package/dist/cjs/router.js.map +1 -1
- package/dist/cjs/types.d.ts +4 -99
- package/dist/cjs/types.d.ts.map +1 -1
- package/dist/cjs/types.js +15 -0
- package/dist/cjs/types.js.map +1 -1
- package/dist/cjs/types.openapi.d.ts +238 -0
- package/dist/cjs/types.openapi.d.ts.map +1 -0
- package/dist/cjs/types.openapi.js +15 -0
- package/dist/cjs/types.openapi.js.map +1 -0
- package/dist/generator.d.ts.map +1 -1
- package/dist/generator.js +39 -3
- package/dist/generator.js.map +1 -1
- package/dist/router.d.ts +91 -32
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +387 -92
- package/dist/router.js.map +1 -1
- package/dist/types.d.ts +4 -99
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +15 -0
- package/dist/types.js.map +1 -1
- package/dist/types.openapi.d.ts +238 -0
- package/dist/types.openapi.d.ts.map +1 -0
- package/dist/types.openapi.js +15 -0
- package/dist/types.openapi.js.map +1 -0
- package/package.json +1 -1
package/dist/cjs/router.js
CHANGED
|
@@ -26,7 +26,7 @@ var __asyncValues = (this && this.__asyncValues) || function (o) {
|
|
|
26
26
|
function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }
|
|
27
27
|
function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
|
|
28
28
|
};
|
|
29
|
-
var _Router_instances, _Router_config, _Router_routes, _Router_processPath, _Router_getRouteEntries, _Router_InitEntries, _Router_initRoutes;
|
|
29
|
+
var _Router_instances, _Router_config, _Router_routes, _Router_generateUniqueOperationId, _Router_processPath, _Router_getRouteEntries, _Router_InitEntries, _Router_initRoutes;
|
|
30
30
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
31
31
|
exports.translateRouteFilePath = exports.Router = exports.HANDLER_TYPES = exports.HTTP_METHODS_UPPER = exports.CRUD_METHODS = exports.HTTP_METHODS = exports.REGISTER_PATH_REGEX = exports.PATH_PART_REGEX = void 0;
|
|
32
32
|
const status_ts_1 = require("./status.js");
|
|
@@ -465,19 +465,172 @@ class Router {
|
|
|
465
465
|
}
|
|
466
466
|
});
|
|
467
467
|
}
|
|
468
|
-
|
|
469
|
-
|
|
468
|
+
/**
|
|
469
|
+
* Generates an OpenAPI 3.0.0 specification document from the registered routes.
|
|
470
|
+
*
|
|
471
|
+
* This method scans all registered routes and builds a comprehensive OpenAPI specification
|
|
472
|
+
* that can be used with Swagger UI, Redoc, or any OpenAPI-compatible tooling.
|
|
473
|
+
*
|
|
474
|
+
* @param info - Configuration for the OpenAPI document
|
|
475
|
+
* @param info.title - API title (defaults to "API")
|
|
476
|
+
* @param info.version - API version (defaults to "1.0.0")
|
|
477
|
+
* @param info.description - API description
|
|
478
|
+
* @param info.servers - Array of server URLs and descriptions
|
|
479
|
+
* @param info.security - Global security requirements
|
|
480
|
+
* @param info.components - Reusable components (schemas, securitySchemes, parameters, responses, examples)
|
|
481
|
+
* @param info.termsOfService - URL to terms of service
|
|
482
|
+
* @param info.contact - Contact information for the API
|
|
483
|
+
* @param info.license - License information
|
|
484
|
+
* @param info.tags - Global tags for grouping operations
|
|
485
|
+
* @param info.externalDocs - External documentation reference
|
|
486
|
+
*
|
|
487
|
+
* @param options - Generation options
|
|
488
|
+
* @param {Map<HttpMethodUpper,number>} options.sortMethodPriorityMap - Map method to priority for the default sorter.
|
|
489
|
+
* @param {1|-1|undefined} options.sortPathnameOrder - Enable and define the pathname sort order for the default sorter.
|
|
490
|
+
* @param {1|-1|undefined} options.sortMethodOrder - Enable and define the method sort order for the default sorter.
|
|
491
|
+
* @param {1|-1|undefined} options.sortTagsOrder - Enable and define the tags sort order for the default sorter.
|
|
492
|
+
* @param options.pick - Filter function to selectively include routes
|
|
493
|
+
* - Receives one `GenerateOpenAPISortParam` parameter: `{ method, path, parts, tags }`
|
|
494
|
+
* - Return true to include, false to exclude
|
|
495
|
+
* @param options.routeSorter - Custom route sorter.
|
|
496
|
+
* - Recieves two `GenerateOpenAPISortParam` parameters: `{ method, path, parts, tags }`
|
|
497
|
+
* - Return -1, 0, 1
|
|
498
|
+
* @param options.includeOperationId - Whether to generate operationId for each operation (default: true)
|
|
499
|
+
* @param options.autoTag - Automatically tag operations based on path (default: true)
|
|
500
|
+
* - When `true`, uses the first path segment as the tag
|
|
501
|
+
* - When a function, allows custom tag generation
|
|
502
|
+
* @param options.commonParameters - Parameters to apply to all operations
|
|
503
|
+
* @param options.autoSummary - Generate summary from path when not provided (default: true)
|
|
504
|
+
* @param options.cleanOperationId - Remove special characters from operation IDs (default: true)
|
|
505
|
+
|
|
506
|
+
* @returns {Promise<GeneratedOpenApi>} A promise that resolves to the complete OpenAPI 3.0.0 specification
|
|
507
|
+
*
|
|
508
|
+
* @example
|
|
509
|
+
* ```typescript
|
|
510
|
+
* // Basic usage
|
|
511
|
+
* const openapi = await router.generateOpenAPI({
|
|
512
|
+
* title: "My API",
|
|
513
|
+
* version: "2.0.0",
|
|
514
|
+
* description: "My awesome API",
|
|
515
|
+
* });
|
|
516
|
+
*
|
|
517
|
+
* // With custom options
|
|
518
|
+
* const openapi = await router.generateOpenAPI(
|
|
519
|
+
* { title: "My API", version: "1.0.0" },
|
|
520
|
+
* {
|
|
521
|
+
* pick: ({ tags, path, parts, method }) => {
|
|
522
|
+
* return path.startsWith('/api');
|
|
523
|
+
* },
|
|
524
|
+
* autoTag: ({ path }) => {
|
|
525
|
+
* const parts = path.split('/').filter(p => p && p !== "*");
|
|
526
|
+
* return parts.length > 0 ? [parts[0]] : ['default'];
|
|
527
|
+
* },
|
|
528
|
+
* commonParameters: [
|
|
529
|
+
* {
|
|
530
|
+
* name: 'X-Request-ID',
|
|
531
|
+
* in: 'header',
|
|
532
|
+
* schema: { type: 'string' },
|
|
533
|
+
* description: 'Request ID for tracing'
|
|
534
|
+
* }
|
|
535
|
+
* ]
|
|
536
|
+
* }
|
|
537
|
+
* );
|
|
538
|
+
*
|
|
539
|
+
* // Write to file
|
|
540
|
+
* await Deno.writeTextFile('./openapi.json', JSON.stringify(openapi, null, 2));
|
|
541
|
+
* ```
|
|
542
|
+
*
|
|
543
|
+
* @remarks
|
|
544
|
+
* - Routes with `openApi: false` in their definition are excluded from generation
|
|
545
|
+
* - Super glob routes (`/**`) are automatically excluded
|
|
546
|
+
* - Unnamed glob routes (`/*`) are named by their index like so 'glob1'
|
|
547
|
+
* - Connect HTTP method is not supported
|
|
548
|
+
* - Tags are only included if they are actually used by at least one operation
|
|
549
|
+
* - Response schemas are only included when explicitly provided
|
|
550
|
+
* - Path parameters are automatically extracted from route definitions and forced to `required: true`
|
|
551
|
+
* - Operation IDs are guaranteed to be unique with collision detection
|
|
552
|
+
*
|
|
553
|
+
* @see {@link GenerateOpenApiInfo} for complete info options
|
|
554
|
+
* @see {@link GenerateOpenAPIOptions} for complete generation options
|
|
555
|
+
* @see {@link GeneratedOpenApi} for the return type structure
|
|
556
|
+
*/
|
|
557
|
+
generateOpenAPI(info, options) {
|
|
558
|
+
var _a;
|
|
559
|
+
const { title = "API", version = "1.0.0", description, servers = [{ url: "/", description: "Current server" }], security, components: globalComponents, termsOfService, contact, license, tags: globalTags, externalDocs, } = info !== null && info !== void 0 ? info : Object.create(null);
|
|
560
|
+
const sortMethodPriorityMap = (_a = options === null || options === void 0 ? void 0 : options.sortMethodPriorityMap) !== null && _a !== void 0 ? _a : types_ts_1.SORT_METHOD_PRIORITY_INDEX;
|
|
561
|
+
const sortPathnameOrder = (options === null || options === void 0 ? void 0 : options.sortPathnameOrder) && options.sortPathnameOrder < 0 ? -1 : 1;
|
|
562
|
+
const sortMethodOrder = (options === null || options === void 0 ? void 0 : options.sortMethodOrder) && options.sortMethodOrder < 0 ? -1 : 1;
|
|
563
|
+
const sortTagsOrder = (options === null || options === void 0 ? void 0 : options.sortTagsOrder) && options.sortTagsOrder < 0 ? -1 : 1;
|
|
564
|
+
const { pick, includeOperationId = true, autoTag = true, commonParameters = [], autoSummary = true, cleanOperationId = true, routeSorter = (a, b) => {
|
|
565
|
+
// sort by tags
|
|
566
|
+
if ((options === null || options === void 0 ? void 0 : options.sortTagsOrder) != null) {
|
|
567
|
+
const minTagsLen = Math.min(a.tags.length, b.tags.length);
|
|
568
|
+
for (let i = 0; i < minTagsLen; i++) {
|
|
569
|
+
const comp = sortTagsOrder * a.tags[i].localeCompare(b.tags[i]);
|
|
570
|
+
if (comp !== 0) {
|
|
571
|
+
return comp;
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
if (minTagsLen === 0 && a.tags.length !== b.tags.length) {
|
|
575
|
+
const comp = sortTagsOrder * (a.tags.length < b.tags.length ? -1 : 1);
|
|
576
|
+
if (comp !== 0) {
|
|
577
|
+
return comp;
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
// sort by pathname parts
|
|
582
|
+
if ((options === null || options === void 0 ? void 0 : options.sortPathnameOrder) != null) {
|
|
583
|
+
const minLen = Math.min(a.parts.length, b.parts.length);
|
|
584
|
+
for (let i = 0; i < minLen; i++) {
|
|
585
|
+
let comp = sortPathnameOrder * a.parts[i].localeCompare(b.parts[i]);
|
|
586
|
+
if (comp !== 0) {
|
|
587
|
+
return comp;
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
if (a.parts.length != b.parts.length) {
|
|
591
|
+
const comp = sortPathnameOrder * (a.parts.length < b.parts.length ? -1 : 1);
|
|
592
|
+
if (comp !== 0) {
|
|
593
|
+
return comp;
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
// sort by methods
|
|
598
|
+
if ((options === null || options === void 0 ? void 0 : options.sortMethodOrder) != null) {
|
|
599
|
+
if (a.method !== b.method) {
|
|
600
|
+
const ma = sortMethodPriorityMap.get(a.method);
|
|
601
|
+
const mb = sortMethodPriorityMap.get(b.method);
|
|
602
|
+
return ma === mb
|
|
603
|
+
? sortMethodOrder * a.method.localeCompare(b.method)
|
|
604
|
+
: sortMethodOrder * (ma - mb);
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
return 0;
|
|
608
|
+
}, } = options !== null && options !== void 0 ? options : Object.create(null);
|
|
470
609
|
return new Promise((resolve) => {
|
|
471
|
-
var _a, _b, _c;
|
|
610
|
+
var _a, _b, _c, _d;
|
|
472
611
|
const handlers = __classPrivateFieldGet(this, _Router_routes, "f").handler;
|
|
473
612
|
const paths = {};
|
|
474
613
|
const schemas = {};
|
|
475
614
|
const securitySchemes = {};
|
|
615
|
+
const parameters = {};
|
|
616
|
+
const responses = {};
|
|
617
|
+
const examples = {};
|
|
618
|
+
const usedTags = new Set();
|
|
619
|
+
const usedOperationIds = new Set();
|
|
620
|
+
const warnings = [];
|
|
476
621
|
// Group routes by path
|
|
477
|
-
const
|
|
622
|
+
const routeGroupsSorter = [];
|
|
623
|
+
const routeGroups = Object.create(null);
|
|
478
624
|
// Collect all routes
|
|
479
625
|
for (const method of Object.keys(handlers)) {
|
|
480
|
-
const
|
|
626
|
+
const methodUpper = method.toUpperCase();
|
|
627
|
+
// Check if method is supported by OpenAPI
|
|
628
|
+
if (methodUpper === "CONNECT") {
|
|
629
|
+
continue;
|
|
630
|
+
}
|
|
631
|
+
const methodHandlers = handlers[methodUpper];
|
|
632
|
+
if (!methodHandlers)
|
|
633
|
+
continue;
|
|
481
634
|
for (const entries of [
|
|
482
635
|
methodHandlers.entries,
|
|
483
636
|
methodHandlers.globs,
|
|
@@ -489,96 +642,144 @@ class Router {
|
|
|
489
642
|
for (const [, entry] of bucket) {
|
|
490
643
|
if (entry == null)
|
|
491
644
|
continue;
|
|
492
|
-
//
|
|
493
|
-
if (entry.
|
|
645
|
+
// Check openApi first
|
|
646
|
+
if (entry.openApi === false)
|
|
647
|
+
continue;
|
|
648
|
+
// Skip super glob routes
|
|
649
|
+
if (entry.path.endsWith("/**"))
|
|
494
650
|
continue;
|
|
651
|
+
let tags = (_a = entry.openApi) === null || _a === void 0 ? void 0 : _a.tags;
|
|
652
|
+
// Auto-tag based on path if no explicit tags and autoTag is enabled
|
|
653
|
+
if (autoTag && !tags) {
|
|
654
|
+
const pathParts = entry.pathParts.filter((p) => p && p !== "*");
|
|
655
|
+
if (pathParts.length > 0) {
|
|
656
|
+
if (typeof autoTag === "function") {
|
|
657
|
+
tags = autoTag(entry);
|
|
658
|
+
}
|
|
659
|
+
else {
|
|
660
|
+
const tag = pathParts[0] || "default";
|
|
661
|
+
tags = [tag];
|
|
662
|
+
}
|
|
663
|
+
}
|
|
495
664
|
}
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
665
|
+
const sortEntry = Object.freeze({
|
|
666
|
+
method: methodUpper,
|
|
667
|
+
path: entry.path,
|
|
668
|
+
parts: Object.freeze([...entry.pathParts]),
|
|
669
|
+
tags: entry.openApi && Array.isArray(tags) && tags.length > 0
|
|
670
|
+
? Object.freeze([...tags].sort())
|
|
671
|
+
: Object.freeze([]),
|
|
672
|
+
});
|
|
673
|
+
// Apply pick filter
|
|
674
|
+
if (typeof pick === "function" && !pick(sortEntry)) {
|
|
675
|
+
continue;
|
|
500
676
|
}
|
|
501
|
-
|
|
677
|
+
routeGroupsSorter.push([sortEntry, { entry, tags }]);
|
|
502
678
|
}
|
|
503
679
|
}
|
|
504
680
|
}
|
|
505
681
|
}
|
|
682
|
+
// Sort route groups
|
|
683
|
+
routeGroupsSorter.sort(([a], [b]) => routeSorter(a, b));
|
|
684
|
+
for (const [{ method, path, parts }, { entry, tags },] of routeGroupsSorter) {
|
|
685
|
+
let pathMethods = routeGroups[entry.openApiPath];
|
|
686
|
+
if (!pathMethods) {
|
|
687
|
+
pathMethods = Object.create(null);
|
|
688
|
+
routeGroups[entry.openApiPath] = pathMethods;
|
|
689
|
+
}
|
|
690
|
+
if (pathMethods[method] != null) {
|
|
691
|
+
console.warn(`Duplicate method found in ${entry.openApiPath}.${method}`);
|
|
692
|
+
}
|
|
693
|
+
pathMethods[method] = { entry, tags };
|
|
694
|
+
// Track all tags used by this operation
|
|
695
|
+
if (tags) {
|
|
696
|
+
for (const tag of tags) {
|
|
697
|
+
usedTags.add(tag);
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
}
|
|
506
701
|
// Build paths
|
|
507
|
-
for (const
|
|
702
|
+
for (const pathname of Object.keys(routeGroups)) {
|
|
703
|
+
const methods = routeGroups[pathname];
|
|
508
704
|
const pathItem = {};
|
|
509
705
|
paths[pathname] = pathItem;
|
|
510
|
-
for (const
|
|
511
|
-
const
|
|
706
|
+
for (const method of Object.keys(methods)) {
|
|
707
|
+
const { entry, tags } = methods[method];
|
|
708
|
+
const openApi = entry.openApi || {};
|
|
512
709
|
const methodLower = method.toLowerCase();
|
|
513
|
-
//
|
|
514
|
-
|
|
710
|
+
// Auto-summary from path - use the last meaningful part
|
|
711
|
+
let summary = openApi.summary;
|
|
712
|
+
if (autoSummary && !summary) {
|
|
713
|
+
const pathParts = pathname
|
|
714
|
+
.split("/")
|
|
715
|
+
.filter((p, idx) => p && entry.pathParts[idx] !== "*");
|
|
716
|
+
// Find the last non-parameter part or use the last part
|
|
717
|
+
let resource = pathParts[pathParts.length - 1] || pathParts[0] || "root";
|
|
718
|
+
// Remove OpenAPI parameter syntax for summary
|
|
719
|
+
resource = resource.replace(/[{}]/g, "");
|
|
720
|
+
const action = method.toLowerCase();
|
|
721
|
+
summary = `${action} ${resource}`;
|
|
722
|
+
}
|
|
723
|
+
// Build parameters - router-derived path parameters MUST be required: true
|
|
724
|
+
const entryParams = [];
|
|
725
|
+
let globIdx = 0;
|
|
726
|
+
for (let i = 1; i < entry.pathParts.length; i++) {
|
|
727
|
+
const part = entry.pathParts[i];
|
|
728
|
+
if (part === "*") {
|
|
729
|
+
const foundParam = (_b = entry.params) === null || _b === void 0 ? void 0 : _b.find(([idx, paramId]) => idx === i);
|
|
730
|
+
entryParams.push([
|
|
731
|
+
i,
|
|
732
|
+
foundParam ? foundParam[1] : `glob${++globIdx}`,
|
|
733
|
+
]);
|
|
734
|
+
}
|
|
735
|
+
}
|
|
515
736
|
const pathParams = [];
|
|
516
|
-
if (
|
|
517
|
-
for (const [idx, paramId] of
|
|
518
|
-
//
|
|
519
|
-
|
|
520
|
-
pathParams.push({
|
|
737
|
+
if (entryParams.length > 0) {
|
|
738
|
+
for (const [idx, paramId] of entryParams) {
|
|
739
|
+
// Start with the router-derived parameter
|
|
740
|
+
const baseParam = {
|
|
521
741
|
name: paramId,
|
|
522
742
|
in: "path",
|
|
523
743
|
required: true,
|
|
524
744
|
schema: { type: "string" },
|
|
525
|
-
}
|
|
745
|
+
};
|
|
746
|
+
// Check if user defined this parameter
|
|
747
|
+
const userParam = ((_c = openApi.parameters) !== null && _c !== void 0 ? _c : []).find((p) => p.name === paramId && p.in === "path");
|
|
748
|
+
if (userParam) {
|
|
749
|
+
// Merge user metadata while preserving required: true
|
|
750
|
+
pathParams.push(Object.assign(Object.assign(Object.assign({}, baseParam), userParam), { required: true, in: "path" }));
|
|
751
|
+
}
|
|
752
|
+
else {
|
|
753
|
+
pathParams.push(baseParam);
|
|
754
|
+
}
|
|
526
755
|
}
|
|
527
756
|
}
|
|
528
|
-
//
|
|
529
|
-
const userParams = (
|
|
530
|
-
|
|
757
|
+
// Get user-defined parameters (non-path params)
|
|
758
|
+
const userParams = ((_d = openApi.parameters) !== null && _d !== void 0 ? _d : []).filter((p) => p.in !== "path");
|
|
759
|
+
// Combine: common parameters + user params + path params
|
|
760
|
+
// Path params come last so they take precedence for required: true
|
|
761
|
+
const allParams = [...commonParameters, ...userParams, ...pathParams];
|
|
531
762
|
// Remove duplicates (by name + in combination)
|
|
532
763
|
const paramSet = new Set();
|
|
533
|
-
const
|
|
764
|
+
const finalParams = [];
|
|
534
765
|
for (const param of allParams) {
|
|
535
766
|
const key = `${param.name}:${param.in}`;
|
|
536
767
|
if (!paramSet.has(key)) {
|
|
537
768
|
paramSet.add(key);
|
|
538
|
-
|
|
769
|
+
finalParams.push(param);
|
|
539
770
|
}
|
|
540
771
|
}
|
|
541
772
|
// Build request body
|
|
542
773
|
const requestBody = openApi.requestBody;
|
|
543
|
-
// Build responses
|
|
544
|
-
const
|
|
774
|
+
// Build responses - only use provided responses, no inference
|
|
775
|
+
const responseObj = {};
|
|
545
776
|
if (openApi.responses) {
|
|
546
|
-
Object.assign(
|
|
777
|
+
Object.assign(responseObj, openApi.responses);
|
|
547
778
|
}
|
|
548
779
|
else {
|
|
549
|
-
//
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
description: "Successful response",
|
|
553
|
-
content: {
|
|
554
|
-
"application/json": {
|
|
555
|
-
schema: {
|
|
556
|
-
type: "object",
|
|
557
|
-
properties: {
|
|
558
|
-
data: { type: "object" },
|
|
559
|
-
message: { type: "string" },
|
|
560
|
-
},
|
|
561
|
-
},
|
|
562
|
-
},
|
|
563
|
-
},
|
|
564
|
-
};
|
|
565
|
-
}
|
|
566
|
-
else if (method === "DELETE") {
|
|
567
|
-
responses["204"] = {
|
|
568
|
-
description: "Resource deleted successfully",
|
|
569
|
-
};
|
|
570
|
-
}
|
|
571
|
-
// Add common error responses
|
|
572
|
-
if ((_c = entry.params) === null || _c === void 0 ? void 0 : _c.length) {
|
|
573
|
-
responses["404"] = {
|
|
574
|
-
description: "Resource not found",
|
|
575
|
-
};
|
|
576
|
-
}
|
|
577
|
-
responses["400"] = {
|
|
578
|
-
description: "Bad request",
|
|
579
|
-
};
|
|
580
|
-
responses["500"] = {
|
|
581
|
-
description: "Internal server error",
|
|
780
|
+
// Minimal default response - just a description
|
|
781
|
+
responseObj["200"] = {
|
|
782
|
+
description: "Successful response",
|
|
582
783
|
};
|
|
583
784
|
}
|
|
584
785
|
// Build security
|
|
@@ -586,16 +787,25 @@ class Router {
|
|
|
586
787
|
if (!operationSecurity && security) {
|
|
587
788
|
operationSecurity = security;
|
|
588
789
|
}
|
|
589
|
-
// Generate
|
|
590
|
-
|
|
790
|
+
// Generate operation ID with uniqueness guarantee
|
|
791
|
+
let operationId = openApi.operationId;
|
|
792
|
+
if (operationId) {
|
|
793
|
+
if (usedOperationIds.has(operationId)) {
|
|
794
|
+
console.warn(`Duplicate OpenApi operationId '${operationId}' in ${entry.openApiPath}.${method}`);
|
|
795
|
+
}
|
|
796
|
+
usedOperationIds.add(operationId);
|
|
797
|
+
}
|
|
798
|
+
else if (includeOperationId) {
|
|
799
|
+
operationId = __classPrivateFieldGet(this, _Router_instances, "m", _Router_generateUniqueOperationId).call(this, method, pathname, cleanOperationId, usedOperationIds);
|
|
800
|
+
}
|
|
591
801
|
// Build operation object
|
|
592
802
|
const operation = {
|
|
593
|
-
summary:
|
|
803
|
+
summary: summary,
|
|
594
804
|
description: openApi.description,
|
|
595
|
-
tags:
|
|
596
|
-
parameters:
|
|
805
|
+
tags: tags,
|
|
806
|
+
parameters: finalParams.length > 0 ? finalParams : undefined,
|
|
597
807
|
requestBody,
|
|
598
|
-
responses,
|
|
808
|
+
responses: responseObj,
|
|
599
809
|
security: operationSecurity,
|
|
600
810
|
operationId,
|
|
601
811
|
};
|
|
@@ -604,33 +814,86 @@ class Router {
|
|
|
604
814
|
pathItem[methodLower] = cleanedOperation;
|
|
605
815
|
}
|
|
606
816
|
}
|
|
607
|
-
//
|
|
817
|
+
// Build components
|
|
608
818
|
const components = {};
|
|
819
|
+
// Merge global and collected schemas
|
|
609
820
|
if (globalComponents === null || globalComponents === void 0 ? void 0 : globalComponents.schemas) {
|
|
610
821
|
Object.assign(schemas, globalComponents.schemas);
|
|
611
822
|
}
|
|
612
823
|
if (Object.keys(schemas).length > 0) {
|
|
613
824
|
components.schemas = schemas;
|
|
614
825
|
}
|
|
826
|
+
// Merge global and collected security schemes
|
|
615
827
|
if (globalComponents === null || globalComponents === void 0 ? void 0 : globalComponents.securitySchemes) {
|
|
616
828
|
Object.assign(securitySchemes, globalComponents.securitySchemes);
|
|
617
829
|
}
|
|
618
830
|
if (Object.keys(securitySchemes).length > 0) {
|
|
619
831
|
components.securitySchemes = securitySchemes;
|
|
620
832
|
}
|
|
833
|
+
// Add global parameters
|
|
834
|
+
if (globalComponents === null || globalComponents === void 0 ? void 0 : globalComponents.parameters) {
|
|
835
|
+
Object.assign(parameters, globalComponents.parameters);
|
|
836
|
+
}
|
|
837
|
+
if (Object.keys(parameters).length > 0) {
|
|
838
|
+
components.parameters = parameters;
|
|
839
|
+
}
|
|
840
|
+
// Add global responses
|
|
841
|
+
if (globalComponents === null || globalComponents === void 0 ? void 0 : globalComponents.responses) {
|
|
842
|
+
Object.assign(responses, globalComponents.responses);
|
|
843
|
+
}
|
|
844
|
+
if (Object.keys(responses).length > 0) {
|
|
845
|
+
components.responses = responses;
|
|
846
|
+
}
|
|
847
|
+
// Add global examples
|
|
848
|
+
if (globalComponents === null || globalComponents === void 0 ? void 0 : globalComponents.examples) {
|
|
849
|
+
Object.assign(examples, globalComponents.examples);
|
|
850
|
+
}
|
|
851
|
+
if (Object.keys(examples).length > 0) {
|
|
852
|
+
components.examples = examples;
|
|
853
|
+
}
|
|
854
|
+
// Build final result
|
|
621
855
|
const result = {
|
|
622
856
|
openapi: "3.0.0",
|
|
623
|
-
info: Object.assign({ title,
|
|
624
|
-
version }, (description && { description })),
|
|
857
|
+
info: Object.assign(Object.assign(Object.assign(Object.assign({ title,
|
|
858
|
+
version }, (description && { description })), (termsOfService && { termsOfService })), (contact && { contact })), (license && { license })),
|
|
625
859
|
servers,
|
|
626
860
|
paths,
|
|
627
861
|
};
|
|
862
|
+
// Add tags - ONLY include tags that are actually used by operations
|
|
863
|
+
const allTags = [];
|
|
864
|
+
// Start with global tags that are actually used
|
|
865
|
+
if (globalTags) {
|
|
866
|
+
for (const tag of globalTags) {
|
|
867
|
+
if (usedTags.has(tag.name)) {
|
|
868
|
+
allTags.push(tag);
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
// Add auto-generated tags that aren't already in global tags
|
|
873
|
+
for (const tag of usedTags) {
|
|
874
|
+
if (!allTags.some((t) => t.name === tag)) {
|
|
875
|
+
allTags.push({ name: tag });
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
if (allTags.length > 0) {
|
|
879
|
+
result.tags = allTags;
|
|
880
|
+
}
|
|
881
|
+
// Add external docs
|
|
882
|
+
if (externalDocs) {
|
|
883
|
+
result.externalDocs = externalDocs;
|
|
884
|
+
}
|
|
885
|
+
// Add components
|
|
628
886
|
if (Object.keys(components).length > 0) {
|
|
629
887
|
result.components = components;
|
|
630
888
|
}
|
|
889
|
+
// Add security
|
|
631
890
|
if (security && security.length > 0) {
|
|
632
891
|
result.security = security;
|
|
633
892
|
}
|
|
893
|
+
// Log warnings if any
|
|
894
|
+
if (warnings.length > 0 && typeof console !== "undefined") {
|
|
895
|
+
console.warn("OpenAPI Generation Warnings:", warnings.join("\n "));
|
|
896
|
+
}
|
|
634
897
|
resolve(result);
|
|
635
898
|
});
|
|
636
899
|
}
|
|
@@ -1049,16 +1312,24 @@ class Router {
|
|
|
1049
1312
|
const { params, paths } = processedPaths;
|
|
1050
1313
|
const paramsMap = params ? new Map(params) : undefined;
|
|
1051
1314
|
for (const path of paths) {
|
|
1052
|
-
const
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1315
|
+
const parts = path.split("/", __classPrivateFieldGet(this, _Router_config, "f").maxPath + 1);
|
|
1316
|
+
const containsParams = params || parts.some((p) => p === "*");
|
|
1317
|
+
const standardPath = containsParams
|
|
1318
|
+
? parts
|
|
1319
|
+
.map((p, idx) => p === "*"
|
|
1320
|
+
? (paramsMap === null || paramsMap === void 0 ? void 0 : paramsMap.has(idx))
|
|
1321
|
+
? `:${paramsMap.get(idx)}`
|
|
1322
|
+
: "*"
|
|
1323
|
+
: p)
|
|
1056
1324
|
.join("/")
|
|
1057
1325
|
: path;
|
|
1058
|
-
|
|
1326
|
+
let globIdx = 0;
|
|
1327
|
+
const openApiPath = containsParams
|
|
1059
1328
|
? path
|
|
1060
1329
|
.split("/")
|
|
1061
|
-
.map((p, idx) =>
|
|
1330
|
+
.map((p, idx) => p === "*"
|
|
1331
|
+
? `{${(paramsMap === null || paramsMap === void 0 ? void 0 : paramsMap.get(idx)) || `glob${++globIdx}`}}`
|
|
1332
|
+
: p)
|
|
1062
1333
|
.join("/")
|
|
1063
1334
|
: path;
|
|
1064
1335
|
const upperMethod = method.toUpperCase();
|
|
@@ -1069,7 +1340,6 @@ class Router {
|
|
|
1069
1340
|
if (!exports.REGISTER_PATH_REGEX.test(path)) {
|
|
1070
1341
|
throw new types_ts_1.RouterError(`Invalid path for (${method} ${originalPath} -> ${path})`);
|
|
1071
1342
|
}
|
|
1072
|
-
const parts = path.split("/", __classPrivateFieldGet(this, _Router_config, "f").maxPath + 1);
|
|
1073
1343
|
if (parts.length - 1 > __classPrivateFieldGet(this, _Router_config, "f").maxPath) {
|
|
1074
1344
|
throw new types_ts_1.RouterError(`Path parts length limit exceeded ${__classPrivateFieldGet(this, _Router_config, "f").maxPath}`);
|
|
1075
1345
|
}
|
|
@@ -1098,7 +1368,8 @@ class Router {
|
|
|
1098
1368
|
if (!overwrite &&
|
|
1099
1369
|
superGlobEntries &&
|
|
1100
1370
|
superGlobEntries.has(basePath)) {
|
|
1101
|
-
|
|
1371
|
+
const superGlobEntry = superGlobEntries.get(basePath);
|
|
1372
|
+
throw new types_ts_1.RouterError(`SuperGlob route collision for (${method} ${originalPath} -> ${path} with ${superGlobEntry.originalPath} at ${superGlobEntry.pathParts.join("/")})`);
|
|
1102
1373
|
}
|
|
1103
1374
|
if (superGlobEntries == null) {
|
|
1104
1375
|
superGlobEntries = new Map();
|
|
@@ -1111,7 +1382,8 @@ class Router {
|
|
|
1111
1382
|
let globEntries = routes.globs[parts.length];
|
|
1112
1383
|
if (globEntries) {
|
|
1113
1384
|
if (!overwrite && globEntries && globEntries.has(path)) {
|
|
1114
|
-
|
|
1385
|
+
const globEntry = globEntries.get(path);
|
|
1386
|
+
throw new types_ts_1.RouterError(`Glob route collision for (${method} ${originalPath} -> ${path} with ${globEntry.originalPath} at ${globEntry.pathParts.join("/")})`);
|
|
1115
1387
|
}
|
|
1116
1388
|
// check for collision
|
|
1117
1389
|
for (const globEntry of globEntries.values()) {
|
|
@@ -1181,7 +1453,18 @@ class Router {
|
|
|
1181
1453
|
}
|
|
1182
1454
|
}
|
|
1183
1455
|
exports.Router = Router;
|
|
1184
|
-
_Router_config = new WeakMap(), _Router_routes = new WeakMap(), _Router_instances = new WeakSet(),
|
|
1456
|
+
_Router_config = new WeakMap(), _Router_routes = new WeakMap(), _Router_instances = new WeakSet(), _Router_generateUniqueOperationId = function _Router_generateUniqueOperationId(method, path, clean, usedIds) {
|
|
1457
|
+
let baseId = generateOperationId(method, path, clean);
|
|
1458
|
+
let operationId = baseId;
|
|
1459
|
+
let counter = 1;
|
|
1460
|
+
// Ensure uniqueness
|
|
1461
|
+
while (usedIds.has(operationId)) {
|
|
1462
|
+
operationId = `${baseId}${counter}`;
|
|
1463
|
+
counter++;
|
|
1464
|
+
}
|
|
1465
|
+
usedIds.add(operationId);
|
|
1466
|
+
return operationId;
|
|
1467
|
+
}, _Router_processPath = function _Router_processPath(path) {
|
|
1185
1468
|
const processedPaths = {
|
|
1186
1469
|
paths: [""],
|
|
1187
1470
|
};
|
|
@@ -1502,17 +1785,29 @@ const translateRouteFilePath = (pathname, maxPath = 64) => {
|
|
|
1502
1785
|
return parts.join("/");
|
|
1503
1786
|
};
|
|
1504
1787
|
exports.translateRouteFilePath = translateRouteFilePath;
|
|
1505
|
-
const generateOperationId = (method, path) => {
|
|
1506
|
-
//
|
|
1507
|
-
|
|
1508
|
-
.replace(/[{}]/g, "")
|
|
1509
|
-
.replace(/\*/g, "")
|
|
1510
|
-
.replace(/[^a-zA-Z0-9\/]/g, "")
|
|
1788
|
+
const generateOperationId = (method, path, clean = true) => {
|
|
1789
|
+
// Clean path
|
|
1790
|
+
let cleanPath = path
|
|
1511
1791
|
.split("/")
|
|
1512
1792
|
.filter(Boolean)
|
|
1513
|
-
.map((part) =>
|
|
1793
|
+
.map((part) => {
|
|
1794
|
+
// Remove braces and special chars
|
|
1795
|
+
let cleaned = part
|
|
1796
|
+
.replace(/[{}]/g, "")
|
|
1797
|
+
.replace(/[\-\.@]/g, (m) => m === "-" ? "_" : m === "." ? "__" : "_at_");
|
|
1798
|
+
if (clean) {
|
|
1799
|
+
// Remove other special chars
|
|
1800
|
+
cleaned = cleaned.replace(/[^a-zA-Z0-9_]/g, "");
|
|
1801
|
+
}
|
|
1802
|
+
// Convert to PascalCase
|
|
1803
|
+
return cleaned.charAt(0).toUpperCase() + cleaned.slice(1);
|
|
1804
|
+
})
|
|
1514
1805
|
.join("");
|
|
1515
|
-
|
|
1806
|
+
if (!cleanPath) {
|
|
1807
|
+
cleanPath = "Root";
|
|
1808
|
+
}
|
|
1809
|
+
const methodPrefix = method.toLowerCase();
|
|
1810
|
+
return `${methodPrefix}${cleanPath}`;
|
|
1516
1811
|
};
|
|
1517
1812
|
exports.default = Router;
|
|
1518
1813
|
//# sourceMappingURL=router.js.map
|