@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/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
- generateOpenAPI(info) {
469
- const { title = "API", version = "1.0.0", description, servers = [{ url: "/", description: "Current server" }], security, components: globalComponents, } = info !== null && info !== void 0 ? info : {};
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 routeGroups = new Map();
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 methodHandlers = handlers[method];
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
- // Skip super glob routes for OpenAPI (they're catch-alls)
493
- if (entry.path.endsWith("/**")) {
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
- let pathMethods = routeGroups.get(entry.openApiPath);
497
- if (!pathMethods) {
498
- pathMethods = new Map();
499
- routeGroups.set(entry.openApiPath, pathMethods);
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
- pathMethods.set(method, entry);
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 [pathname, methods] of routeGroups) {
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 [method, entry] of methods) {
511
- const openApi = (_a = entry.openApi) !== null && _a !== void 0 ? _a : {};
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
- // Build parameters - only include path params that are actually in the path
514
- // console.log(entry);
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 (entry.params != null) {
517
- for (const [idx, paramId] of entry.params) {
518
- // if(entry.pathParts[idx])
519
- console.log([idx, paramId, entry.pathParts[idx]]);
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
- // Combine with user-defined parameters
529
- const userParams = (_b = openApi.parameters) !== null && _b !== void 0 ? _b : [];
530
- const allParams = [...userParams, ...pathParams];
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 parameters = [];
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
- parameters.push(param);
769
+ finalParams.push(param);
539
770
  }
540
771
  }
541
772
  // Build request body
542
773
  const requestBody = openApi.requestBody;
543
- // Build responses
544
- const responses = {};
774
+ // Build responses - only use provided responses, no inference
775
+ const responseObj = {};
545
776
  if (openApi.responses) {
546
- Object.assign(responses, openApi.responses);
777
+ Object.assign(responseObj, openApi.responses);
547
778
  }
548
779
  else {
549
- // Infer responses from HTTP method
550
- if (method !== "DELETE" && method !== "HEAD") {
551
- responses["200"] = {
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 clean operation ID
590
- const operationId = openApi.operationId || generateOperationId(method, pathname);
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: openApi.summary,
803
+ summary: summary,
594
804
  description: openApi.description,
595
- tags: openApi.tags,
596
- parameters: parameters.length > 0 ? parameters : undefined,
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
- // Merge global components with any collected schemas
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 standardPath = params
1053
- ? path
1054
- .split("/")
1055
- .map((p, idx) => (p === "*" ? `:${paramsMap === null || paramsMap === void 0 ? void 0 : paramsMap.get(idx)}` : p))
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
- const openApiPath = params
1326
+ let globIdx = 0;
1327
+ const openApiPath = containsParams
1059
1328
  ? path
1060
1329
  .split("/")
1061
- .map((p, idx) => (p === "*" ? `{${paramsMap === null || paramsMap === void 0 ? void 0 : paramsMap.get(idx)}}` : p))
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
- throw new types_ts_1.RouterError(`SuperGlob route already set for (${method} ${originalPath} -> ${path})`);
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
- throw new types_ts_1.RouterError(`Glob route already set for (${method} ${originalPath} -> ${path})`);
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(), _Router_processPath = function _Router_processPath(path) {
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
- // Remove all special characters and format properly
1507
- const cleanPath = path
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) => part.charAt(0).toUpperCase() + part.slice(1))
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
- return `${method.toLowerCase()}${cleanPath}`;
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