@rexeus/typeweaver-gen 0.7.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/README.md +58 -29
- package/dist/index.cjs +409 -153
- package/dist/index.d.cts +196 -149
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +196 -149
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +386 -139
- package/dist/index.mjs.map +1 -1
- package/package.json +6 -4
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
|
|
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(
|
|
50
|
-
return
|
|
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/
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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/
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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
|
-
|
|
127
|
-
|
|
128
|
-
|
|
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/
|
|
153
|
-
|
|
154
|
-
|
|
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
|
-
|
|
170
|
-
|
|
171
|
-
|
|
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
|
-
...
|
|
174
|
-
|
|
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
|
-
|
|
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
|
-
|
|
406
|
+
const template = fs.readFileSync(fullTemplatePath, "utf8");
|
|
407
|
+
return ejs.render(template, data);
|
|
188
408
|
},
|
|
189
409
|
addGeneratedFile: (relativePath) => {
|
|
190
|
-
|
|
410
|
+
generatedFiles.add(relativePath);
|
|
191
411
|
},
|
|
192
412
|
getGeneratedFiles: () => {
|
|
193
|
-
return Array.from(
|
|
413
|
+
return Array.from(generatedFiles);
|
|
194
414
|
}
|
|
195
415
|
};
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
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/
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
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,
|
|
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
|