@bepalo/spine 1.4.13 → 1.5.14

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,159 @@ 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
+ if (entry.openApi == null) {
666
+ entry.openApi = {};
667
+ }
668
+ entry.openApi.tags = tags;
669
+ const sortEntry = Object.freeze({
670
+ method: methodUpper,
671
+ path: entry.path,
672
+ parts: Object.freeze([...entry.pathParts]),
673
+ tags: Object.freeze(entry.openApi && tags && tags.length > 0
674
+ ? [...tags].sort()
675
+ : []),
676
+ });
677
+ // Apply pick filter
678
+ if (typeof pick === "function" && !pick(sortEntry)) {
679
+ continue;
500
680
  }
501
- pathMethods.set(method, entry);
681
+ routeGroupsSorter.push([sortEntry, entry]);
502
682
  }
503
683
  }
504
684
  }
505
685
  }
686
+ // Sort route groups
687
+ routeGroupsSorter.sort(([a], [b]) => routeSorter(a, b));
688
+ for (const [{ method, path, parts }, entry] of routeGroupsSorter) {
689
+ let pathMethods = routeGroups[entry.openApiPath];
690
+ if (!pathMethods) {
691
+ pathMethods = Object.create(null);
692
+ routeGroups[entry.openApiPath] = pathMethods;
693
+ }
694
+ pathMethods[method] = entry;
695
+ }
506
696
  // Build paths
507
- for (const [pathname, methods] of routeGroups) {
697
+ for (const pathname of Object.keys(routeGroups)) {
698
+ const methods = routeGroups[pathname];
508
699
  const pathItem = {};
509
700
  paths[pathname] = pathItem;
510
- for (const [method, entry] of methods) {
511
- const openApi = (_a = entry.openApi) !== null && _a !== void 0 ? _a : {};
701
+ for (const method of Object.keys(methods)) {
702
+ const entry = methods[method];
703
+ const openApi = entry.openApi || {};
512
704
  const methodLower = method.toLowerCase();
513
- // Build parameters - only include path params that are actually in the path
514
- // console.log(entry);
705
+ // Determine tags for this operation
706
+ let tags = openApi.tags;
707
+ // // Auto-tag based on path if no explicit tags and autoTag is enabled
708
+ // if (autoTag && !tags) {
709
+ // const pathParts = pathname.split("/").filter(Boolean);
710
+ // if (pathParts.length > 0) {
711
+ // if (typeof autoTag === "function") {
712
+ // tags = autoTag(entry);
713
+ // } else {
714
+ // const tag = pathParts[0] || "default";
715
+ // tags = [tag];
716
+ // }
717
+ // }
718
+ // }
719
+ // Track all tags used by this operation
720
+ if (tags) {
721
+ for (const tag of tags) {
722
+ usedTags.add(tag);
723
+ }
724
+ }
725
+ // Auto-summary from path - use the last meaningful part
726
+ let summary = openApi.summary;
727
+ if (autoSummary && !summary) {
728
+ const pathParts = pathname
729
+ .split("/")
730
+ .filter((p, idx) => p && entry.pathParts[idx] !== "*");
731
+ // Find the last non-parameter part or use the last part
732
+ let resource = pathParts[pathParts.length - 1] || pathParts[0] || "root";
733
+ // Remove OpenAPI parameter syntax for summary
734
+ resource = resource.replace(/[{}]/g, "");
735
+ const action = method.toLowerCase();
736
+ summary = `${action} ${resource}`;
737
+ }
738
+ // Build parameters - router-derived path parameters MUST be required: true
739
+ const entryParams = [];
740
+ let globIdx = 0;
741
+ for (let i = 1; i < entry.pathParts.length; i++) {
742
+ const part = entry.pathParts[i];
743
+ if (part === "*") {
744
+ const foundParam = (_b = entry.params) === null || _b === void 0 ? void 0 : _b.find(([idx, paramId]) => idx === i);
745
+ entryParams.push([
746
+ i,
747
+ foundParam ? foundParam[1] : `glob${++globIdx}`,
748
+ ]);
749
+ }
750
+ }
515
751
  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({
752
+ if (entryParams.length > 0) {
753
+ for (const [idx, paramId] of entryParams) {
754
+ // Start with the router-derived parameter
755
+ const baseParam = {
521
756
  name: paramId,
522
757
  in: "path",
523
758
  required: true,
524
759
  schema: { type: "string" },
525
- });
760
+ };
761
+ // Check if user defined this parameter
762
+ const userParam = ((_c = openApi.parameters) !== null && _c !== void 0 ? _c : []).find((p) => p.name === paramId && p.in === "path");
763
+ if (userParam) {
764
+ // Merge user metadata while preserving required: true
765
+ pathParams.push(Object.assign(Object.assign(Object.assign({}, baseParam), userParam), { required: true, in: "path" }));
766
+ }
767
+ else {
768
+ pathParams.push(baseParam);
769
+ }
526
770
  }
527
771
  }
528
- // Combine with user-defined parameters
529
- const userParams = (_b = openApi.parameters) !== null && _b !== void 0 ? _b : [];
530
- const allParams = [...userParams, ...pathParams];
772
+ // Get user-defined parameters (non-path params)
773
+ const userParams = ((_d = openApi.parameters) !== null && _d !== void 0 ? _d : []).filter((p) => p.in !== "path");
774
+ // Combine: common parameters + user params + path params
775
+ // Path params come last so they take precedence for required: true
776
+ const allParams = [...commonParameters, ...userParams, ...pathParams];
531
777
  // Remove duplicates (by name + in combination)
532
778
  const paramSet = new Set();
533
- const parameters = [];
779
+ const finalParams = [];
534
780
  for (const param of allParams) {
535
781
  const key = `${param.name}:${param.in}`;
536
782
  if (!paramSet.has(key)) {
537
783
  paramSet.add(key);
538
- parameters.push(param);
784
+ finalParams.push(param);
539
785
  }
540
786
  }
541
787
  // Build request body
542
788
  const requestBody = openApi.requestBody;
543
- // Build responses
544
- const responses = {};
789
+ // Build responses - only use provided responses, no inference
790
+ const responseObj = {};
545
791
  if (openApi.responses) {
546
- Object.assign(responses, openApi.responses);
792
+ Object.assign(responseObj, openApi.responses);
547
793
  }
548
794
  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",
795
+ // Minimal default response - just a description
796
+ responseObj["200"] = {
797
+ description: "Successful response",
582
798
  };
583
799
  }
584
800
  // Build security
@@ -586,16 +802,19 @@ class Router {
586
802
  if (!operationSecurity && security) {
587
803
  operationSecurity = security;
588
804
  }
589
- // Generate clean operation ID
590
- const operationId = openApi.operationId || generateOperationId(method, pathname);
805
+ // Generate operation ID with uniqueness guarantee
806
+ let operationId = openApi.operationId;
807
+ if (!operationId && includeOperationId) {
808
+ operationId = __classPrivateFieldGet(this, _Router_instances, "m", _Router_generateUniqueOperationId).call(this, method, pathname, cleanOperationId, usedOperationIds);
809
+ }
591
810
  // Build operation object
592
811
  const operation = {
593
- summary: openApi.summary,
812
+ summary: summary,
594
813
  description: openApi.description,
595
- tags: openApi.tags,
596
- parameters: parameters.length > 0 ? parameters : undefined,
814
+ tags: tags,
815
+ parameters: finalParams.length > 0 ? finalParams : undefined,
597
816
  requestBody,
598
- responses,
817
+ responses: responseObj,
599
818
  security: operationSecurity,
600
819
  operationId,
601
820
  };
@@ -604,33 +823,86 @@ class Router {
604
823
  pathItem[methodLower] = cleanedOperation;
605
824
  }
606
825
  }
607
- // Merge global components with any collected schemas
826
+ // Build components
608
827
  const components = {};
828
+ // Merge global and collected schemas
609
829
  if (globalComponents === null || globalComponents === void 0 ? void 0 : globalComponents.schemas) {
610
830
  Object.assign(schemas, globalComponents.schemas);
611
831
  }
612
832
  if (Object.keys(schemas).length > 0) {
613
833
  components.schemas = schemas;
614
834
  }
835
+ // Merge global and collected security schemes
615
836
  if (globalComponents === null || globalComponents === void 0 ? void 0 : globalComponents.securitySchemes) {
616
837
  Object.assign(securitySchemes, globalComponents.securitySchemes);
617
838
  }
618
839
  if (Object.keys(securitySchemes).length > 0) {
619
840
  components.securitySchemes = securitySchemes;
620
841
  }
842
+ // Add global parameters
843
+ if (globalComponents === null || globalComponents === void 0 ? void 0 : globalComponents.parameters) {
844
+ Object.assign(parameters, globalComponents.parameters);
845
+ }
846
+ if (Object.keys(parameters).length > 0) {
847
+ components.parameters = parameters;
848
+ }
849
+ // Add global responses
850
+ if (globalComponents === null || globalComponents === void 0 ? void 0 : globalComponents.responses) {
851
+ Object.assign(responses, globalComponents.responses);
852
+ }
853
+ if (Object.keys(responses).length > 0) {
854
+ components.responses = responses;
855
+ }
856
+ // Add global examples
857
+ if (globalComponents === null || globalComponents === void 0 ? void 0 : globalComponents.examples) {
858
+ Object.assign(examples, globalComponents.examples);
859
+ }
860
+ if (Object.keys(examples).length > 0) {
861
+ components.examples = examples;
862
+ }
863
+ // Build final result
621
864
  const result = {
622
865
  openapi: "3.0.0",
623
- info: Object.assign({ title,
624
- version }, (description && { description })),
866
+ info: Object.assign(Object.assign(Object.assign(Object.assign({ title,
867
+ version }, (description && { description })), (termsOfService && { termsOfService })), (contact && { contact })), (license && { license })),
625
868
  servers,
626
869
  paths,
627
870
  };
871
+ // Add tags - ONLY include tags that are actually used by operations
872
+ const allTags = [];
873
+ // Start with global tags that are actually used
874
+ if (globalTags) {
875
+ for (const tag of globalTags) {
876
+ if (usedTags.has(tag.name)) {
877
+ allTags.push(tag);
878
+ }
879
+ }
880
+ }
881
+ // Add auto-generated tags that aren't already in global tags
882
+ for (const tag of usedTags) {
883
+ if (!allTags.some((t) => t.name === tag)) {
884
+ allTags.push({ name: tag });
885
+ }
886
+ }
887
+ if (allTags.length > 0) {
888
+ result.tags = allTags;
889
+ }
890
+ // Add external docs
891
+ if (externalDocs) {
892
+ result.externalDocs = externalDocs;
893
+ }
894
+ // Add components
628
895
  if (Object.keys(components).length > 0) {
629
896
  result.components = components;
630
897
  }
898
+ // Add security
631
899
  if (security && security.length > 0) {
632
900
  result.security = security;
633
901
  }
902
+ // Log warnings if any
903
+ if (warnings.length > 0 && typeof console !== "undefined") {
904
+ console.warn("OpenAPI Generation Warnings:", warnings.join("\n "));
905
+ }
634
906
  resolve(result);
635
907
  });
636
908
  }
@@ -1049,16 +1321,24 @@ class Router {
1049
1321
  const { params, paths } = processedPaths;
1050
1322
  const paramsMap = params ? new Map(params) : undefined;
1051
1323
  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))
1324
+ const parts = path.split("/", __classPrivateFieldGet(this, _Router_config, "f").maxPath + 1);
1325
+ const containsParams = params || parts.some((p) => p === "*");
1326
+ const standardPath = containsParams
1327
+ ? parts
1328
+ .map((p, idx) => p === "*"
1329
+ ? (paramsMap === null || paramsMap === void 0 ? void 0 : paramsMap.has(idx))
1330
+ ? `:${paramsMap.get(idx)}`
1331
+ : "*"
1332
+ : p)
1056
1333
  .join("/")
1057
1334
  : path;
1058
- const openApiPath = params
1335
+ let globIdx = 0;
1336
+ const openApiPath = containsParams
1059
1337
  ? path
1060
1338
  .split("/")
1061
- .map((p, idx) => (p === "*" ? `{${paramsMap === null || paramsMap === void 0 ? void 0 : paramsMap.get(idx)}}` : p))
1339
+ .map((p, idx) => p === "*"
1340
+ ? `{${(paramsMap === null || paramsMap === void 0 ? void 0 : paramsMap.get(idx)) || `glob${++globIdx}`}}`
1341
+ : p)
1062
1342
  .join("/")
1063
1343
  : path;
1064
1344
  const upperMethod = method.toUpperCase();
@@ -1069,7 +1349,6 @@ class Router {
1069
1349
  if (!exports.REGISTER_PATH_REGEX.test(path)) {
1070
1350
  throw new types_ts_1.RouterError(`Invalid path for (${method} ${originalPath} -> ${path})`);
1071
1351
  }
1072
- const parts = path.split("/", __classPrivateFieldGet(this, _Router_config, "f").maxPath + 1);
1073
1352
  if (parts.length - 1 > __classPrivateFieldGet(this, _Router_config, "f").maxPath) {
1074
1353
  throw new types_ts_1.RouterError(`Path parts length limit exceeded ${__classPrivateFieldGet(this, _Router_config, "f").maxPath}`);
1075
1354
  }
@@ -1098,7 +1377,8 @@ class Router {
1098
1377
  if (!overwrite &&
1099
1378
  superGlobEntries &&
1100
1379
  superGlobEntries.has(basePath)) {
1101
- throw new types_ts_1.RouterError(`SuperGlob route already set for (${method} ${originalPath} -> ${path})`);
1380
+ const superGlobEntry = superGlobEntries.get(basePath);
1381
+ throw new types_ts_1.RouterError(`SuperGlob route collision for (${method} ${originalPath} -> ${path} with ${superGlobEntry.originalPath} at ${superGlobEntry.pathParts.join("/")})`);
1102
1382
  }
1103
1383
  if (superGlobEntries == null) {
1104
1384
  superGlobEntries = new Map();
@@ -1111,7 +1391,8 @@ class Router {
1111
1391
  let globEntries = routes.globs[parts.length];
1112
1392
  if (globEntries) {
1113
1393
  if (!overwrite && globEntries && globEntries.has(path)) {
1114
- throw new types_ts_1.RouterError(`Glob route already set for (${method} ${originalPath} -> ${path})`);
1394
+ const globEntry = globEntries.get(path);
1395
+ throw new types_ts_1.RouterError(`Glob route collision for (${method} ${originalPath} -> ${path} with ${globEntry.originalPath} at ${globEntry.pathParts.join("/")})`);
1115
1396
  }
1116
1397
  // check for collision
1117
1398
  for (const globEntry of globEntries.values()) {
@@ -1181,7 +1462,18 @@ class Router {
1181
1462
  }
1182
1463
  }
1183
1464
  exports.Router = Router;
1184
- _Router_config = new WeakMap(), _Router_routes = new WeakMap(), _Router_instances = new WeakSet(), _Router_processPath = function _Router_processPath(path) {
1465
+ _Router_config = new WeakMap(), _Router_routes = new WeakMap(), _Router_instances = new WeakSet(), _Router_generateUniqueOperationId = function _Router_generateUniqueOperationId(method, path, clean, usedIds) {
1466
+ let baseId = generateOperationId(method, path, clean);
1467
+ let operationId = baseId;
1468
+ let counter = 1;
1469
+ // Ensure uniqueness
1470
+ while (usedIds.has(operationId)) {
1471
+ operationId = `${baseId}${counter}`;
1472
+ counter++;
1473
+ }
1474
+ usedIds.add(operationId);
1475
+ return operationId;
1476
+ }, _Router_processPath = function _Router_processPath(path) {
1185
1477
  const processedPaths = {
1186
1478
  paths: [""],
1187
1479
  };
@@ -1502,17 +1794,29 @@ const translateRouteFilePath = (pathname, maxPath = 64) => {
1502
1794
  return parts.join("/");
1503
1795
  };
1504
1796
  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, "")
1797
+ const generateOperationId = (method, path, clean = true) => {
1798
+ // Clean path
1799
+ let cleanPath = path
1511
1800
  .split("/")
1512
1801
  .filter(Boolean)
1513
- .map((part) => part.charAt(0).toUpperCase() + part.slice(1))
1802
+ .map((part) => {
1803
+ // Remove braces and special chars
1804
+ let cleaned = part
1805
+ .replace(/[{}]/g, "")
1806
+ .replace(/[\-\.@]/g, (m) => m === "-" ? "_" : m === "." ? "__" : "_at_");
1807
+ if (clean) {
1808
+ // Remove other special chars
1809
+ cleaned = cleaned.replace(/[^a-zA-Z0-9_]/g, "");
1810
+ }
1811
+ // Convert to PascalCase
1812
+ return cleaned.charAt(0).toUpperCase() + cleaned.slice(1);
1813
+ })
1514
1814
  .join("");
1515
- return `${method.toLowerCase()}${cleanPath}`;
1815
+ if (!cleanPath) {
1816
+ cleanPath = "Root";
1817
+ }
1818
+ const methodPrefix = method.toLowerCase();
1819
+ return `${methodPrefix}${cleanPath}`;
1516
1820
  };
1517
1821
  exports.default = Router;
1518
1822
  //# sourceMappingURL=router.js.map