@rexeus/typeweaver-gen 0.8.0 → 0.9.0

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/index.mjs CHANGED
@@ -1,7 +1,237 @@
1
+ import { HttpMethod, HttpStatusCodeNameMap, isNamedResponseDefinition, validateUniqueResponseNames } from "@rexeus/typeweaver-core";
2
+ import { z } from "zod";
1
3
  import fs from "node:fs";
2
4
  import path from "node:path";
3
- import { render } from "ejs";
4
-
5
+ import ejs from "ejs";
6
+ //#region src/errors/DerivedResponseCycleError.ts
7
+ var DerivedResponseCycleError = class extends Error {
8
+ constructor(responseName) {
9
+ super(`Derived response '${responseName}' contains a cyclic lineage.`);
10
+ this.name = "DerivedResponseCycleError";
11
+ }
12
+ };
13
+ //#endregion
14
+ //#region src/errors/DuplicateOperationIdError.ts
15
+ var DuplicateOperationIdError = class extends Error {
16
+ constructor(operationId) {
17
+ super(`Operation ID '${operationId}' must be globally unique within a spec.`);
18
+ this.name = "DuplicateOperationIdError";
19
+ }
20
+ };
21
+ //#endregion
22
+ //#region src/errors/DuplicateRouteError.ts
23
+ var DuplicateRouteError = class extends Error {
24
+ constructor(method, path, normalizedPath) {
25
+ super(`Route '${method} ${path}' conflicts with an existing route using normalized path '${normalizedPath}'.`);
26
+ this.name = "DuplicateRouteError";
27
+ }
28
+ };
29
+ //#endregion
30
+ //#region src/errors/EmptyOperationResponsesError.ts
31
+ var EmptyOperationResponsesError = class extends Error {
32
+ constructor(operationId) {
33
+ super(`Operation '${operationId}' must declare at least one response.`);
34
+ this.name = "EmptyOperationResponsesError";
35
+ }
36
+ };
37
+ //#endregion
38
+ //#region src/errors/EmptyResourceOperationsError.ts
39
+ var EmptyResourceOperationsError = class extends Error {
40
+ constructor(resourceName) {
41
+ super(`Resource '${resourceName}' must contain at least one operation.`);
42
+ this.name = "EmptyResourceOperationsError";
43
+ }
44
+ };
45
+ //#endregion
46
+ //#region src/errors/EmptySpecResourcesError.ts
47
+ var EmptySpecResourcesError = class extends Error {
48
+ constructor() {
49
+ super("Spec definition must contain at least one resource.");
50
+ this.name = "EmptySpecResourcesError";
51
+ }
52
+ };
53
+ //#endregion
54
+ //#region src/errors/InvalidDerivedResponseError.ts
55
+ var InvalidDerivedResponseError = class extends Error {
56
+ constructor(responseName) {
57
+ super(`Derived response '${responseName}' contains invalid lineage metadata.`);
58
+ this.name = "InvalidDerivedResponseError";
59
+ }
60
+ };
61
+ //#endregion
62
+ //#region src/errors/InvalidRequestSchemaError.ts
63
+ var InvalidRequestSchemaError = class extends Error {
64
+ constructor(operationId, requestPart) {
65
+ super(`Operation '${operationId}' has an invalid request.${requestPart} schema definition.`);
66
+ this.name = "InvalidRequestSchemaError";
67
+ }
68
+ };
69
+ //#endregion
70
+ //#region src/errors/MissingDerivedResponseParentError.ts
71
+ var MissingDerivedResponseParentError = class extends Error {
72
+ constructor(responseName, parentName) {
73
+ super(`Derived response '${responseName}' references missing canonical parent '${parentName}'.`);
74
+ this.name = "MissingDerivedResponseParentError";
75
+ }
76
+ };
77
+ //#endregion
78
+ //#region src/errors/PathParameterMismatchError.ts
79
+ var PathParameterMismatchError = class extends Error {
80
+ constructor(operationId, path, pathParams, requestParams) {
81
+ super(`Operation '${operationId}' has mismatched path parameters for '${path}'. Path params: [${pathParams.join(", ")}], request.param keys: [${requestParams.join(", ")}].`);
82
+ this.name = "PathParameterMismatchError";
83
+ }
84
+ };
85
+ //#endregion
86
+ //#region src/helpers/routePath.ts
87
+ const PATH_PARAMETER_PATTERN = /:([A-Za-z0-9_]+)/g;
88
+ const normalizeRoutePath = (path) => {
89
+ const segments = path.split("/").filter(Boolean);
90
+ if (segments.length === 0) return "/";
91
+ return `/${segments.map((segment) => segment.startsWith(":") ? ":" : segment).join("/")}`;
92
+ };
93
+ const getPathParameterNames = (path) => {
94
+ return Array.from(path.matchAll(PATH_PARAMETER_PATTERN), (match) => match[1]);
95
+ };
96
+ //#endregion
97
+ //#region src/validation/derivedResponseValidation.ts
98
+ const validateDerivedResponseMetadata = (response) => {
99
+ const derived = response.derived;
100
+ if (derived === void 0) return;
101
+ if (derived.parentName === response.name) throw new DerivedResponseCycleError(response.name);
102
+ if (derived.lineage.length === 0) throw new InvalidDerivedResponseError(response.name);
103
+ if (derived.lineage.at(-1) !== response.name) throw new InvalidDerivedResponseError(response.name);
104
+ if (derived.lineage.length !== derived.depth) throw new InvalidDerivedResponseError(response.name);
105
+ if (new Set(derived.lineage).size !== derived.lineage.length) throw new DerivedResponseCycleError(response.name);
106
+ if (derived.depth > 1 && derived.lineage.at(-2) !== derived.parentName) throw new InvalidDerivedResponseError(response.name);
107
+ };
108
+ const collectCanonicalResponseDefinitions = (definition) => {
109
+ const canonicalResponses = /* @__PURE__ */ new Map();
110
+ for (const resource of Object.values(definition.resources)) for (const operation of resource.operations) for (const response of operation.responses) {
111
+ if (!isNamedResponseDefinition(response)) continue;
112
+ validateDerivedResponseMetadata(response);
113
+ canonicalResponses.set(response.name, response);
114
+ }
115
+ return canonicalResponses;
116
+ };
117
+ const getDerivedResponseChain = (response, canonicalResponses) => {
118
+ const chain = [response.name];
119
+ const visitedResponseNames = new Set(chain);
120
+ let parentName = response.derived?.parentName;
121
+ while (parentName !== void 0) {
122
+ if (visitedResponseNames.has(parentName)) throw new DerivedResponseCycleError(response.name);
123
+ const parentResponse = canonicalResponses.get(parentName);
124
+ if (parentResponse === void 0) throw new MissingDerivedResponseParentError(response.name, parentName);
125
+ chain.unshift(parentResponse.name);
126
+ visitedResponseNames.add(parentResponse.name);
127
+ parentName = parentResponse.derived?.parentName;
128
+ }
129
+ return chain;
130
+ };
131
+ const validateDerivedResponseGraph = (canonicalResponses) => {
132
+ for (const response of canonicalResponses.values()) {
133
+ if (response.derived === void 0) continue;
134
+ const materializedLineage = getDerivedResponseChain(response, canonicalResponses).slice(1);
135
+ if (response.derived.depth !== materializedLineage.length) throw new InvalidDerivedResponseError(response.name);
136
+ if (materializedLineage.length !== response.derived.lineage.length || materializedLineage.some((lineageEntry, index) => lineageEntry !== response.derived?.lineage[index])) throw new InvalidDerivedResponseError(response.name);
137
+ }
138
+ };
139
+ const normalizeResponseDefinition = (response) => {
140
+ return {
141
+ name: response.name,
142
+ statusCode: response.statusCode,
143
+ statusCodeName: HttpStatusCodeNameMap[response.statusCode],
144
+ description: response.description,
145
+ header: response.header,
146
+ body: response.body,
147
+ kind: response.derived === void 0 ? "response" : "derived-response",
148
+ derivedFrom: response.derived?.parentName,
149
+ lineage: response.derived?.lineage,
150
+ depth: response.derived?.depth
151
+ };
152
+ };
153
+ const collectCanonicalResponses = (definition) => {
154
+ const canonicalResponseDefinitions = collectCanonicalResponseDefinitions(definition);
155
+ validateDerivedResponseGraph(canonicalResponseDefinitions);
156
+ return new Map(Array.from(canonicalResponseDefinitions.entries(), ([responseName, response]) => [responseName, normalizeResponseDefinition(response)]));
157
+ };
158
+ //#endregion
159
+ //#region src/normalizeSpec.ts
160
+ const isZodType = (schema) => {
161
+ return schema instanceof z.ZodType;
162
+ };
163
+ const isZodObject = (schema) => {
164
+ return schema instanceof z.ZodObject;
165
+ };
166
+ const validateRequestSchema = (operationId, requestPart, schema) => {
167
+ if (!isZodType(schema)) throw new InvalidRequestSchemaError(operationId, requestPart);
168
+ if (requestPart === "param" && !isZodObject(schema)) throw new InvalidRequestSchemaError(operationId, requestPart);
169
+ };
170
+ const validateRequest = (operationId, path, request) => {
171
+ if (request.header !== void 0) validateRequestSchema(operationId, "header", request.header);
172
+ if (request.param !== void 0) validateRequestSchema(operationId, "param", request.param);
173
+ if (request.query !== void 0) validateRequestSchema(operationId, "query", request.query);
174
+ if (request.body !== void 0) validateRequestSchema(operationId, "body", request.body);
175
+ const pathParams = getPathParameterNames(path);
176
+ const requestParams = request.param === void 0 ? [] : Object.keys(request.param.shape);
177
+ if (pathParams.length !== requestParams.length || pathParams.some((pathParam) => !requestParams.includes(pathParam))) throw new PathParameterMismatchError(operationId, path, pathParams, requestParams);
178
+ if (request.header === void 0 && request.param === void 0 && request.query === void 0 && request.body === void 0) return;
179
+ return {
180
+ header: request.header,
181
+ param: request.param,
182
+ query: request.query,
183
+ body: request.body
184
+ };
185
+ };
186
+ const normalizeOperationResponses = (responses) => {
187
+ return responses.map((response) => {
188
+ if (isNamedResponseDefinition(response)) return {
189
+ responseName: response.name,
190
+ source: "canonical"
191
+ };
192
+ return {
193
+ responseName: response.name,
194
+ source: "inline",
195
+ response: normalizeResponseDefinition(response)
196
+ };
197
+ });
198
+ };
199
+ const normalizeOperation = (operationIds, routeKeys, operation) => {
200
+ if (operationIds.has(operation.operationId)) throw new DuplicateOperationIdError(operation.operationId);
201
+ operationIds.add(operation.operationId);
202
+ const normalizedPath = normalizeRoutePath(operation.path);
203
+ const routeKey = `${operation.method}:${normalizedPath}`;
204
+ if (routeKeys.has(routeKey)) throw new DuplicateRouteError(operation.method, operation.path, normalizedPath);
205
+ routeKeys.add(routeKey);
206
+ if (operation.responses.length === 0) throw new EmptyOperationResponsesError(operation.operationId);
207
+ return {
208
+ operationId: operation.operationId,
209
+ method: operation.method,
210
+ path: operation.path,
211
+ summary: operation.summary,
212
+ request: validateRequest(operation.operationId, operation.path, operation.request),
213
+ responses: normalizeOperationResponses(operation.responses)
214
+ };
215
+ };
216
+ const normalizeSpec = (definition) => {
217
+ const resourceEntries = Object.entries(definition.resources);
218
+ if (resourceEntries.length === 0) throw new EmptySpecResourcesError();
219
+ validateUniqueResponseNames(definition.resources);
220
+ const canonicalResponses = collectCanonicalResponses(definition);
221
+ const operationIds = /* @__PURE__ */ new Set();
222
+ const routeKeys = /* @__PURE__ */ new Set();
223
+ return {
224
+ resources: resourceEntries.map(([resourceName, resource]) => {
225
+ if (resource.operations.length === 0) throw new EmptyResourceOperationsError(resourceName);
226
+ return {
227
+ name: resourceName,
228
+ operations: resource.operations.map((operation) => normalizeOperation(operationIds, routeKeys, operation))
229
+ };
230
+ }),
231
+ responses: Array.from(canonicalResponses.values())
232
+ };
233
+ };
234
+ //#endregion
5
235
  //#region src/plugins/types.ts
6
236
  /**
7
237
  * Plugin loading error
@@ -24,7 +254,6 @@ var PluginDependencyError = class extends Error {
24
254
  this.name = "PluginDependencyError";
25
255
  }
26
256
  };
27
-
28
257
  //#endregion
29
258
  //#region src/plugins/BasePlugin.ts
30
259
  /**
@@ -46,8 +275,8 @@ var BasePlugin = class {
46
275
  /**
47
276
  * Default implementation - override in subclasses if needed
48
277
  */
49
- collectResources(resources) {
50
- return resources;
278
+ collectResources(normalizedSpec) {
279
+ return normalizedSpec;
51
280
  }
52
281
  /**
53
282
  * Default implementation - override in subclasses if needed
@@ -61,169 +290,187 @@ var BasePlugin = class {
61
290
  if (fs.existsSync(path.join(libDir, "index.ts"))) context.addGeneratedFile(libIndexPath);
62
291
  }
63
292
  };
64
-
65
293
  //#endregion
66
- //#region src/plugins/BaseTemplatePlugin.ts
67
- /**
68
- * Base class for template-based generator plugins
69
- * Provides utilities for working with EJS templates
70
- */
71
- var BaseTemplatePlugin = class extends BasePlugin {
72
- /**
73
- * Render an EJS template with the given data
74
- */
75
- renderTemplate(templatePath, data) {
76
- return render(fs.readFileSync(templatePath, "utf8"), data);
77
- }
78
- /**
79
- * Write a file relative to the output directory
80
- */
81
- writeFile(context, relativePath, content) {
82
- context.writeFile(relativePath, content);
83
- }
84
- /**
85
- * Ensure a directory exists
86
- */
87
- ensureDir(context, relativePath) {
88
- const fullPath = path.join(context.outputDir, relativePath);
89
- fs.mkdirSync(fullPath, { recursive: true });
90
- }
91
- /**
92
- * Get the template path for this plugin
93
- */
94
- getTemplatePath(context, templateName) {
95
- return path.join(context.templateDir, templateName);
96
- }
97
- };
98
-
294
+ //#region src/plugins/pluginRegistry.ts
295
+ function createPluginRegistry() {
296
+ const plugins = /* @__PURE__ */ new Map();
297
+ return {
298
+ register: (plugin, config) => {
299
+ if (plugins.has(plugin.name)) {
300
+ console.info(`Skipping duplicate registration of required plugin: ${plugin.name}`);
301
+ return;
302
+ }
303
+ const registration = {
304
+ name: plugin.name,
305
+ plugin,
306
+ config
307
+ };
308
+ plugins.set(plugin.name, registration);
309
+ console.info(`Registered plugin: ${plugin.name}`);
310
+ },
311
+ get: (name) => plugins.get(name),
312
+ getAll: () => Array.from(plugins.values()),
313
+ has: (name) => plugins.has(name),
314
+ clear: () => plugins.clear()
315
+ };
316
+ }
99
317
  //#endregion
100
- //#region src/plugins/PluginRegistry.ts
101
- /**
102
- * Registry for managing typeweaver plugins
103
- */
104
- var PluginRegistry = class {
105
- plugins;
106
- constructor() {
107
- this.plugins = /* @__PURE__ */ new Map();
108
- }
109
- /**
110
- * Register a plugin
111
- */
112
- register(plugin, config) {
113
- if (this.plugins.has(plugin.name)) {
114
- console.info(`Skipping duplicate registration of required plugin: ${plugin.name}`);
115
- return;
116
- }
117
- const registration = {
118
- name: plugin.name,
119
- plugin,
120
- config
121
- };
122
- this.plugins.set(plugin.name, registration);
123
- console.info(`Registered plugin: ${plugin.name}`);
318
+ //#region src/helpers/path.ts
319
+ function relative(from, to) {
320
+ const relativePath = path.relative(from, to);
321
+ if (relativePath.includes("node_modules")) {
322
+ const parts = relativePath.split(path.sep);
323
+ const index = parts.indexOf("node_modules");
324
+ return parts.slice(index + 1).join("/");
124
325
  }
125
- /**
126
- * Get a registered plugin
127
- */
128
- get(name) {
129
- return this.plugins.get(name);
130
- }
131
- /**
132
- * Get all registered plugins
133
- */
134
- getAll() {
135
- return Array.from(this.plugins.values());
136
- }
137
- /**
138
- * Check if a plugin is registered
139
- */
140
- has(name) {
141
- return this.plugins.has(name);
142
- }
143
- /**
144
- * Clear all registered plugins (except required ones)
145
- */
146
- clear() {
147
- this.plugins.clear();
148
- }
149
- };
150
-
326
+ const posixPath = relativePath.split(path.sep).join("/");
327
+ if (!posixPath.startsWith("./") && !posixPath.startsWith("../")) return `./${posixPath}`;
328
+ return posixPath;
329
+ }
151
330
  //#endregion
152
- //#region src/plugins/PluginContext.ts
153
- /**
154
- * Builder for plugin contexts
155
- */
156
- var PluginContextBuilder = class {
157
- generatedFiles = /* @__PURE__ */ new Set();
158
- /**
159
- * Create a basic plugin context
160
- */
161
- createPluginContext(params) {
331
+ //#region src/plugins/pluginContext.ts
332
+ function createPluginContextBuilder() {
333
+ const generatedFiles = /* @__PURE__ */ new Set();
334
+ const createPluginContext = (params) => {
162
335
  return {
163
336
  outputDir: params.outputDir,
164
337
  inputDir: params.inputDir,
165
338
  config: params.config
166
339
  };
167
- }
168
- /**
169
- * Create a generator context with utilities
170
- */
171
- createGeneratorContext(params) {
340
+ };
341
+ const createGeneratorContext = (params) => {
342
+ const pluginContext = createPluginContext(params);
343
+ const canonicalResponsesByName = new Map(params.normalizedSpec.responses.map((response) => [response.name, response]));
344
+ const getResourceOutputDir = (resourceName) => {
345
+ return path.join(params.outputDir, resourceName);
346
+ };
347
+ const getOperationOutputPaths = (config) => {
348
+ const outputDir = getResourceOutputDir(config.resourceName);
349
+ const requestFileName = `${config.operationId}Request.ts`;
350
+ const responseFileName = `${config.operationId}Response.ts`;
351
+ const requestValidationFileName = `${config.operationId}RequestValidator.ts`;
352
+ const responseValidationFileName = `${config.operationId}ResponseValidator.ts`;
353
+ const clientFileName = `${config.operationId}Client.ts`;
354
+ return {
355
+ outputDir,
356
+ requestFile: path.join(outputDir, requestFileName),
357
+ requestFileName,
358
+ responseFile: path.join(outputDir, responseFileName),
359
+ responseFileName,
360
+ requestValidationFile: path.join(outputDir, requestValidationFileName),
361
+ requestValidationFileName,
362
+ responseValidationFile: path.join(outputDir, responseValidationFileName),
363
+ responseValidationFileName,
364
+ clientFile: path.join(outputDir, clientFileName),
365
+ clientFileName
366
+ };
367
+ };
368
+ const getCanonicalResponse = (responseName) => {
369
+ const response = canonicalResponsesByName.get(responseName);
370
+ if (response === void 0) throw new Error(`Missing canonical response '${responseName}'.`);
371
+ return response;
372
+ };
373
+ const getCanonicalResponseOutputFile = (responseName) => {
374
+ return path.join(params.responsesOutputDir, `${responseName}Response.ts`);
375
+ };
172
376
  return {
173
- ...this.createPluginContext(params),
174
- resources: params.resources,
377
+ ...pluginContext,
378
+ normalizedSpec: params.normalizedSpec,
175
379
  templateDir: params.templateDir,
176
380
  coreDir: params.coreDir,
381
+ responsesOutputDir: params.responsesOutputDir,
382
+ specOutputDir: params.specOutputDir,
383
+ getCanonicalResponse,
384
+ getCanonicalResponseOutputFile,
385
+ getCanonicalResponseImportPath: (config) => {
386
+ return relative(config.importerDir, getCanonicalResponseOutputFile(config.responseName).replace(/\.ts$/, ""));
387
+ },
388
+ getSpecImportPath: (config) => {
389
+ return relative(config.importerDir, path.join(params.specOutputDir, "spec").replace(/\.ts$/, ""));
390
+ },
391
+ getOperationDefinitionAccessor: (config) => {
392
+ return `getOperationDefinition(spec, ${JSON.stringify(config.resourceName)}, ${JSON.stringify(config.operationId)})`;
393
+ },
394
+ getOperationOutputPaths,
395
+ getResourceOutputDir,
177
396
  writeFile: (relativePath, content) => {
178
397
  const fullPath = path.join(params.outputDir, relativePath);
179
398
  const dir = path.dirname(fullPath);
180
399
  fs.mkdirSync(dir, { recursive: true });
181
400
  fs.writeFileSync(fullPath, content);
182
- this.generatedFiles.add(relativePath);
401
+ generatedFiles.add(relativePath);
183
402
  console.info(`Generated: ${relativePath}`);
184
403
  },
185
404
  renderTemplate: (templatePath, data) => {
186
405
  const fullTemplatePath = path.isAbsolute(templatePath) ? templatePath : path.join(params.templateDir, templatePath);
187
- return render(fs.readFileSync(fullTemplatePath, "utf8"), data);
406
+ const template = fs.readFileSync(fullTemplatePath, "utf8");
407
+ return ejs.render(template, data);
188
408
  },
189
409
  addGeneratedFile: (relativePath) => {
190
- this.generatedFiles.add(relativePath);
410
+ generatedFiles.add(relativePath);
191
411
  },
192
412
  getGeneratedFiles: () => {
193
- return Array.from(this.generatedFiles);
413
+ return Array.from(generatedFiles);
194
414
  }
195
415
  };
196
- }
197
- /**
198
- * Get all generated files
199
- */
200
- getGeneratedFiles() {
201
- return Array.from(this.generatedFiles);
202
- }
203
- /**
204
- * Clear generated files tracking
205
- */
206
- clearGeneratedFiles() {
207
- this.generatedFiles.clear();
208
- }
209
- };
210
-
416
+ };
417
+ return {
418
+ createPluginContext,
419
+ createGeneratorContext,
420
+ getGeneratedFiles: () => Array.from(generatedFiles),
421
+ clearGeneratedFiles: () => generatedFiles.clear()
422
+ };
423
+ }
211
424
  //#endregion
212
- //#region src/helpers/Path.ts
213
- var Path = class {
214
- static relative(from, to) {
215
- const relativePath = path.relative(from, to);
216
- if (relativePath.includes("node_modules")) {
217
- const parts = relativePath.split(path.sep);
218
- const index = parts.indexOf("node_modules");
219
- return parts.slice(index + 1).join("/");
220
- }
221
- const posixPath = relativePath.split(path.sep).join("/");
222
- if (!posixPath.startsWith("./") && !posixPath.startsWith("../")) return `./${posixPath}`;
223
- return posixPath;
425
+ //#region src/helpers/routeSort.ts
426
+ /**
427
+ * HTTP method priority for route ordering.
428
+ * Lower numbers = higher priority (sorted first).
429
+ */
430
+ const METHOD_PRIORITY = {
431
+ [HttpMethod.GET]: 1,
432
+ [HttpMethod.POST]: 2,
433
+ [HttpMethod.PUT]: 3,
434
+ [HttpMethod.PATCH]: 4,
435
+ [HttpMethod.DELETE]: 5,
436
+ [HttpMethod.OPTIONS]: 6,
437
+ [HttpMethod.HEAD]: 7
438
+ };
439
+ /**
440
+ * Returns the sort priority for an HTTP method.
441
+ * Unrecognized methods default to priority 999.
442
+ */
443
+ const getMethodPriority = (method) => METHOD_PRIORITY[method] ?? 999;
444
+ /**
445
+ * Compares two path segments for route ordering.
446
+ * Returns negative if a should come before b, positive if after.
447
+ *
448
+ * Order: static segments before parameters, then alphabetically.
449
+ */
450
+ const comparePathSegments = (a, b) => {
451
+ const aIsParam = a.startsWith(":");
452
+ if (aIsParam !== b.startsWith(":")) return aIsParam ? 1 : -1;
453
+ return a.localeCompare(b);
454
+ };
455
+ /**
456
+ * Compares two routes for ordering.
457
+ * Routes are sorted by:
458
+ * 1. Path depth (shallow to deep)
459
+ * 2. Static segments before parameters
460
+ * 3. Alphabetical within same segment type
461
+ * 4. HTTP method priority
462
+ */
463
+ const compareRoutes = (a, b) => {
464
+ const aSegments = a.path.split("/").filter(Boolean);
465
+ const bSegments = b.path.split("/").filter(Boolean);
466
+ if (aSegments.length !== bSegments.length) return aSegments.length - bSegments.length;
467
+ for (let i = 0; i < aSegments.length; i++) {
468
+ const cmp = comparePathSegments(aSegments[i], bSegments[i]);
469
+ if (cmp !== 0) return cmp;
224
470
  }
471
+ return getMethodPriority(a.method) - getMethodPriority(b.method);
225
472
  };
226
-
227
473
  //#endregion
228
- export { BasePlugin, BaseTemplatePlugin, Path, PluginContextBuilder, PluginDependencyError, PluginLoadError, PluginRegistry };
474
+ export { BasePlugin, DerivedResponseCycleError, DuplicateOperationIdError, DuplicateRouteError, EmptyOperationResponsesError, EmptyResourceOperationsError, EmptySpecResourcesError, InvalidDerivedResponseError, InvalidRequestSchemaError, MissingDerivedResponseParentError, PathParameterMismatchError, PluginDependencyError, PluginLoadError, compareRoutes, createPluginContextBuilder, createPluginRegistry, getMethodPriority, getPathParameterNames, normalizeRoutePath, normalizeSpec, relative };
475
+
229
476
  //# sourceMappingURL=index.mjs.map