zenko 0.1.6 → 0.1.7-beta.1
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/cli.cjs +430 -189
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.mjs +430 -189
- package/dist/cli.mjs.map +1 -1
- package/dist/index.cjs +426 -188
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +7 -10
- package/dist/index.d.ts +7 -10
- package/dist/index.mjs +426 -188
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -153,6 +153,14 @@ function formatPropertyName(name) {
|
|
|
153
153
|
return isValidJSIdentifier(name) ? name : `"${name}"`;
|
|
154
154
|
}
|
|
155
155
|
|
|
156
|
+
// src/utils/string-utils.ts
|
|
157
|
+
function toCamelCase(str) {
|
|
158
|
+
return str.replace(/-([a-zA-Z])/g, (_, letter) => letter.toUpperCase()).replace(/-+$/, "");
|
|
159
|
+
}
|
|
160
|
+
function capitalize(str) {
|
|
161
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
162
|
+
}
|
|
163
|
+
|
|
156
164
|
// src/utils/http-status.ts
|
|
157
165
|
var statusNameMap = {
|
|
158
166
|
"400": "badRequest",
|
|
@@ -212,14 +220,6 @@ function mapStatusToIdentifier(status) {
|
|
|
212
220
|
if (!candidate) return "unknownError";
|
|
213
221
|
return /^[a-zA-Z_$]/.test(candidate) ? candidate : `status${candidate.charAt(0).toUpperCase()}${candidate.slice(1)}`;
|
|
214
222
|
}
|
|
215
|
-
function getStatusCategory(status) {
|
|
216
|
-
if (status === "default") return "default";
|
|
217
|
-
const code = Number(status);
|
|
218
|
-
if (!Number.isInteger(code)) return "unknown";
|
|
219
|
-
if (code >= 400 && code <= 499) return "client";
|
|
220
|
-
if (code >= 500 && code <= 599) return "server";
|
|
221
|
-
return "unknown";
|
|
222
|
-
}
|
|
223
223
|
function isErrorStatus(status) {
|
|
224
224
|
if (status === "default") return true;
|
|
225
225
|
const code = Number(status);
|
|
@@ -227,6 +227,169 @@ function isErrorStatus(status) {
|
|
|
227
227
|
return code >= 400;
|
|
228
228
|
}
|
|
229
229
|
|
|
230
|
+
// src/utils/tree-shaking.ts
|
|
231
|
+
function analyzeZenkoUsage(operations) {
|
|
232
|
+
const usage = {
|
|
233
|
+
usesHeaderFn: false,
|
|
234
|
+
usesOperationDefinition: false,
|
|
235
|
+
usesOperationErrors: false
|
|
236
|
+
};
|
|
237
|
+
if (operations.length > 0) {
|
|
238
|
+
usage.usesOperationDefinition = true;
|
|
239
|
+
}
|
|
240
|
+
for (const op of operations) {
|
|
241
|
+
if (op.requestHeaders && op.requestHeaders.length > 0) {
|
|
242
|
+
usage.usesHeaderFn = true;
|
|
243
|
+
}
|
|
244
|
+
if (op.errors && hasAnyErrors(op.errors)) {
|
|
245
|
+
usage.usesOperationErrors = true;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
if (operations.length > 0 && !usage.usesOperationErrors) {
|
|
249
|
+
const hasDefaultErrors = operations.some(
|
|
250
|
+
(op) => !op.errors || !hasAnyErrors(op.errors)
|
|
251
|
+
);
|
|
252
|
+
if (hasDefaultErrors) {
|
|
253
|
+
usage.usesOperationErrors = true;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
return usage;
|
|
257
|
+
}
|
|
258
|
+
function generateZenkoImport(usage, mode, helpersOutput) {
|
|
259
|
+
const types = [];
|
|
260
|
+
if (usage.usesHeaderFn) types.push("HeaderFn");
|
|
261
|
+
if (usage.usesOperationDefinition) types.push("OperationDefinition");
|
|
262
|
+
if (usage.usesOperationErrors) types.push("OperationErrors");
|
|
263
|
+
if (types.length === 0) {
|
|
264
|
+
return "";
|
|
265
|
+
}
|
|
266
|
+
const importSource = mode === "package" ? '"zenko"' : `"${helpersOutput}"`;
|
|
267
|
+
return `import type { ${types.join(", ")} } from ${importSource};`;
|
|
268
|
+
}
|
|
269
|
+
function hasAnyErrors(errors) {
|
|
270
|
+
return Boolean(errors && Object.keys(errors).length > 0);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// src/utils/collect-inline-types.ts
|
|
274
|
+
function collectInlineRequestTypes(operations, spec) {
|
|
275
|
+
const requestTypesToGenerate = /* @__PURE__ */ new Map();
|
|
276
|
+
const operationLookup = /* @__PURE__ */ new Map();
|
|
277
|
+
for (const [, pathItem] of Object.entries(spec.paths || {})) {
|
|
278
|
+
for (const [, operation] of Object.entries(pathItem)) {
|
|
279
|
+
const op = operation;
|
|
280
|
+
if (op.operationId) {
|
|
281
|
+
operationLookup.set(op.operationId, op);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
for (const [, pathItem] of Object.entries(spec.webhooks || {})) {
|
|
286
|
+
for (const [, operation] of Object.entries(pathItem)) {
|
|
287
|
+
const op = operation;
|
|
288
|
+
if (op.operationId) {
|
|
289
|
+
operationLookup.set(op.operationId, op);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
for (const op of operations) {
|
|
294
|
+
const operation = operationLookup.get(op.operationId);
|
|
295
|
+
if (!operation) continue;
|
|
296
|
+
const requestBody = operation.requestBody;
|
|
297
|
+
if (requestBody && requestBody.content) {
|
|
298
|
+
const content = requestBody.content;
|
|
299
|
+
const jsonContent = content["application/json"];
|
|
300
|
+
if (jsonContent && jsonContent.schema) {
|
|
301
|
+
const schema = jsonContent.schema;
|
|
302
|
+
const typeName = `${capitalize(toCamelCase(op.operationId))}Request`;
|
|
303
|
+
if (!schema.$ref || schema.allOf || schema.oneOf || schema.anyOf) {
|
|
304
|
+
requestTypesToGenerate.set(typeName, schema);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
return requestTypesToGenerate;
|
|
310
|
+
}
|
|
311
|
+
function collectInlineResponseTypes(operations, spec) {
|
|
312
|
+
const responseTypesToGenerate = /* @__PURE__ */ new Map();
|
|
313
|
+
const operationLookup = /* @__PURE__ */ new Map();
|
|
314
|
+
for (const [, pathItem] of Object.entries(spec.paths || {})) {
|
|
315
|
+
for (const [, operation] of Object.entries(pathItem)) {
|
|
316
|
+
const op = operation;
|
|
317
|
+
if (op.operationId) {
|
|
318
|
+
operationLookup.set(op.operationId, op);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
for (const [, pathItem] of Object.entries(spec.webhooks || {})) {
|
|
323
|
+
for (const [, operation] of Object.entries(pathItem)) {
|
|
324
|
+
const op = operation;
|
|
325
|
+
if (op.operationId) {
|
|
326
|
+
operationLookup.set(op.operationId, op);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
for (const op of operations) {
|
|
331
|
+
const operation = operationLookup.get(op.operationId);
|
|
332
|
+
if (!operation) continue;
|
|
333
|
+
const responses = operation.responses || {};
|
|
334
|
+
for (const [statusCode, response] of Object.entries(responses)) {
|
|
335
|
+
if (/^2\d\d$/.test(statusCode) && response.content) {
|
|
336
|
+
const content = response.content;
|
|
337
|
+
const jsonContent = content["application/json"];
|
|
338
|
+
if (jsonContent && jsonContent.schema) {
|
|
339
|
+
const schema = jsonContent.schema;
|
|
340
|
+
const typeName = `${capitalize(toCamelCase(op.operationId))}Response`;
|
|
341
|
+
if (!schema.$ref || schema.allOf || schema.oneOf || schema.anyOf) {
|
|
342
|
+
responseTypesToGenerate.set(typeName, schema);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
return responseTypesToGenerate;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// src/utils/generate-helper-file.ts
|
|
352
|
+
function generateHelperFile() {
|
|
353
|
+
const output = [];
|
|
354
|
+
output.push("// Generated helper types for Zenko");
|
|
355
|
+
output.push(
|
|
356
|
+
"// This file provides type definitions for operation objects and path functions"
|
|
357
|
+
);
|
|
358
|
+
output.push("");
|
|
359
|
+
output.push(
|
|
360
|
+
"export type PathFn<TArgs extends unknown[] = []> = (...args: TArgs) => string"
|
|
361
|
+
);
|
|
362
|
+
output.push("");
|
|
363
|
+
output.push(
|
|
364
|
+
'export type RequestMethod = "get" | "put" | "post" | "delete" | "options" | "head" | "patch" | "trace"'
|
|
365
|
+
);
|
|
366
|
+
output.push("");
|
|
367
|
+
output.push(
|
|
368
|
+
"export type HeaderFn<TArgs extends unknown[] = [], TResult = Record<string, unknown> | Record<string, never>> = (...args: TArgs) => TResult"
|
|
369
|
+
);
|
|
370
|
+
output.push("");
|
|
371
|
+
output.push(
|
|
372
|
+
"export type AnyHeaderFn = HeaderFn<any, unknown> | (() => unknown)"
|
|
373
|
+
);
|
|
374
|
+
output.push("");
|
|
375
|
+
output.push(
|
|
376
|
+
"export type OperationErrors<TError = unknown> = TError extends Record<string, unknown> ? TError : Record<string, TError>;"
|
|
377
|
+
);
|
|
378
|
+
output.push("");
|
|
379
|
+
output.push(
|
|
380
|
+
"export type OperationDefinition<TMethod extends RequestMethod, TPath extends (...args: any[]) => string, TRequest = undefined, TResponse = undefined, THeaders extends AnyHeaderFn | undefined = undefined, TErrors extends OperationErrors | undefined = undefined> = {"
|
|
381
|
+
);
|
|
382
|
+
output.push(" method: TMethod");
|
|
383
|
+
output.push(" path: TPath");
|
|
384
|
+
output.push(" request?: TRequest");
|
|
385
|
+
output.push(" response?: TResponse");
|
|
386
|
+
output.push(" headers?: THeaders");
|
|
387
|
+
output.push(" errors?: TErrors");
|
|
388
|
+
output.push("}");
|
|
389
|
+
output.push("");
|
|
390
|
+
return output.join("\n");
|
|
391
|
+
}
|
|
392
|
+
|
|
230
393
|
// src/zenko.ts
|
|
231
394
|
function generateWithMetadata(spec, options = {}) {
|
|
232
395
|
const output = [];
|
|
@@ -238,7 +401,14 @@ function generateWithMetadata(spec, options = {}) {
|
|
|
238
401
|
strictNumeric
|
|
239
402
|
};
|
|
240
403
|
output.push('import { z } from "zod";');
|
|
241
|
-
|
|
404
|
+
const nameMap = /* @__PURE__ */ new Map();
|
|
405
|
+
if (spec.components?.schemas) {
|
|
406
|
+
for (const name of Object.keys(spec.components.schemas)) {
|
|
407
|
+
nameMap.set(name, toCamelCase(name));
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
const operations = parseOperations(spec, nameMap);
|
|
411
|
+
appendHelperTypesImport(output, typesConfig, operations);
|
|
242
412
|
output.push("");
|
|
243
413
|
if (spec.components?.schemas) {
|
|
244
414
|
output.push("// Generated Zod Schemas");
|
|
@@ -246,15 +416,23 @@ function generateWithMetadata(spec, options = {}) {
|
|
|
246
416
|
const sortedSchemas = topologicalSort(spec.components.schemas);
|
|
247
417
|
for (const name of sortedSchemas) {
|
|
248
418
|
const schema = spec.components.schemas[name];
|
|
419
|
+
const sanitizedName = nameMap.get(name);
|
|
249
420
|
output.push(
|
|
250
|
-
generateZodSchema(
|
|
421
|
+
generateZodSchema(
|
|
422
|
+
sanitizedName,
|
|
423
|
+
schema,
|
|
424
|
+
generatedTypes,
|
|
425
|
+
schemaOptions,
|
|
426
|
+
nameMap
|
|
427
|
+
)
|
|
251
428
|
);
|
|
252
429
|
output.push("");
|
|
253
|
-
output.push(
|
|
430
|
+
output.push(
|
|
431
|
+
`export type ${sanitizedName} = z.infer<typeof ${sanitizedName}>;`
|
|
432
|
+
);
|
|
254
433
|
output.push("");
|
|
255
434
|
}
|
|
256
435
|
}
|
|
257
|
-
const operations = parseOperations(spec);
|
|
258
436
|
output.push("// Path Functions");
|
|
259
437
|
output.push("export const paths = {");
|
|
260
438
|
for (const op of operations) {
|
|
@@ -268,24 +446,37 @@ function generateWithMetadata(spec, options = {}) {
|
|
|
268
446
|
);
|
|
269
447
|
continue;
|
|
270
448
|
}
|
|
271
|
-
const
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
449
|
+
const alias = (n) => {
|
|
450
|
+
if (isValidJSIdentifier(n)) return n;
|
|
451
|
+
let aliased = toCamelCase(n);
|
|
452
|
+
if (!isValidJSIdentifier(aliased)) {
|
|
453
|
+
aliased = `_${aliased}`;
|
|
454
|
+
}
|
|
455
|
+
return aliased;
|
|
456
|
+
};
|
|
457
|
+
const destructPieces = [];
|
|
458
|
+
const typePieces = [];
|
|
276
459
|
for (const param of op.pathParams) {
|
|
277
|
-
|
|
460
|
+
destructPieces.push(
|
|
461
|
+
isValidJSIdentifier(param.name) ? param.name : `${formatPropertyName(param.name)}: ${alias(param.name)}`
|
|
462
|
+
);
|
|
463
|
+
typePieces.push(`${formatPropertyName(param.name)}: string`);
|
|
278
464
|
}
|
|
279
465
|
for (const param of op.queryParams) {
|
|
280
|
-
|
|
281
|
-
|
|
466
|
+
destructPieces.push(
|
|
467
|
+
isValidJSIdentifier(param.name) ? param.name : `${formatPropertyName(param.name)}: ${alias(param.name)}`
|
|
468
|
+
);
|
|
469
|
+
typePieces.push(
|
|
470
|
+
`${formatPropertyName(param.name)}${param.required ? "" : "?"}: ${mapQueryType(param)}`
|
|
282
471
|
);
|
|
283
472
|
}
|
|
284
|
-
const signatureParams = signaturePieces.join(", ");
|
|
285
473
|
const needsDefaultObject = !hasPathParams && hasQueryParams && op.queryParams.every((param) => !param.required);
|
|
286
|
-
const signatureArgs =
|
|
287
|
-
const signature = `${signatureArgs}: { ${
|
|
288
|
-
const pathWithParams = op.path.replace(
|
|
474
|
+
const signatureArgs = destructPieces.length ? `{ ${destructPieces.join(", ")} }` : "{}";
|
|
475
|
+
const signature = `${signatureArgs}: { ${typePieces.join(", ")} }${needsDefaultObject ? " = {}" : ""}`;
|
|
476
|
+
const pathWithParams = op.path.replace(
|
|
477
|
+
/{([^}]+)}/g,
|
|
478
|
+
(_m, n) => `\${${alias(n)}}`
|
|
479
|
+
);
|
|
289
480
|
if (!hasQueryParams) {
|
|
290
481
|
output.push(
|
|
291
482
|
` ${formatPropertyName(camelCaseOperationId)}: (${signature}) => \`${pathWithParams}\`,`
|
|
@@ -297,8 +488,7 @@ function generateWithMetadata(spec, options = {}) {
|
|
|
297
488
|
);
|
|
298
489
|
output.push(" const params = new URLSearchParams()");
|
|
299
490
|
for (const param of op.queryParams) {
|
|
300
|
-
const
|
|
301
|
-
const accessor = isValidJSIdentifier(param.name) ? param.name : propertyKey;
|
|
491
|
+
const accessor = isValidJSIdentifier(param.name) ? param.name : alias(toCamelCase(param.name));
|
|
302
492
|
const schema = param.schema ?? {};
|
|
303
493
|
if (schema?.type === "array") {
|
|
304
494
|
const itemValueExpression = convertQueryParamValue(
|
|
@@ -380,6 +570,8 @@ function generateWithMetadata(spec, options = {}) {
|
|
|
380
570
|
}
|
|
381
571
|
output.push("} as const;");
|
|
382
572
|
output.push("");
|
|
573
|
+
generateRequestTypes(output, operations, spec, nameMap, schemaOptions);
|
|
574
|
+
generateResponseTypes(output, operations, spec, nameMap, schemaOptions);
|
|
383
575
|
generateOperationTypes(output, operations, typesConfig);
|
|
384
576
|
output.push("// Operation Objects");
|
|
385
577
|
for (const op of operations) {
|
|
@@ -393,13 +585,8 @@ function generateWithMetadata(spec, options = {}) {
|
|
|
393
585
|
if (op.requestHeaders && op.requestHeaders.length > 0) {
|
|
394
586
|
output.push(` headers: headers.${camelCaseOperationId},`);
|
|
395
587
|
}
|
|
396
|
-
if (op.errors &&
|
|
397
|
-
output
|
|
398
|
-
appendErrorGroup(output, "clientErrors", op.errors.clientErrors);
|
|
399
|
-
appendErrorGroup(output, "serverErrors", op.errors.serverErrors);
|
|
400
|
-
appendErrorGroup(output, "defaultErrors", op.errors.defaultErrors);
|
|
401
|
-
appendErrorGroup(output, "otherErrors", op.errors.otherErrors);
|
|
402
|
-
output.push(" },");
|
|
588
|
+
if (op.errors && hasAnyErrors2(op.errors)) {
|
|
589
|
+
appendErrorGroup(output, "errors", op.errors);
|
|
403
590
|
}
|
|
404
591
|
output.push("} as const;");
|
|
405
592
|
output.push("");
|
|
@@ -415,6 +602,46 @@ function generateWithMetadata(spec, options = {}) {
|
|
|
415
602
|
}
|
|
416
603
|
return result;
|
|
417
604
|
}
|
|
605
|
+
function generateRequestTypes(output, operations, spec, nameMap, schemaOptions) {
|
|
606
|
+
const requestTypesToGenerate = collectInlineRequestTypes(operations, spec);
|
|
607
|
+
if (requestTypesToGenerate.size > 0) {
|
|
608
|
+
output.push("// Generated Request Types");
|
|
609
|
+
output.push("");
|
|
610
|
+
for (const [typeName, schema] of requestTypesToGenerate) {
|
|
611
|
+
const generatedSchema = generateZodSchema(
|
|
612
|
+
typeName,
|
|
613
|
+
schema,
|
|
614
|
+
/* @__PURE__ */ new Set(),
|
|
615
|
+
schemaOptions,
|
|
616
|
+
nameMap
|
|
617
|
+
);
|
|
618
|
+
output.push(generatedSchema);
|
|
619
|
+
output.push("");
|
|
620
|
+
output.push(`export type ${typeName} = z.infer<typeof ${typeName}>;`);
|
|
621
|
+
output.push("");
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
function generateResponseTypes(output, operations, spec, nameMap, schemaOptions) {
|
|
626
|
+
const responseTypesToGenerate = collectInlineResponseTypes(operations, spec);
|
|
627
|
+
if (responseTypesToGenerate.size > 0) {
|
|
628
|
+
output.push("// Generated Response Types");
|
|
629
|
+
output.push("");
|
|
630
|
+
for (const [typeName, schema] of responseTypesToGenerate) {
|
|
631
|
+
const generatedSchema = generateZodSchema(
|
|
632
|
+
typeName,
|
|
633
|
+
schema,
|
|
634
|
+
/* @__PURE__ */ new Set(),
|
|
635
|
+
schemaOptions,
|
|
636
|
+
nameMap
|
|
637
|
+
);
|
|
638
|
+
output.push(generatedSchema);
|
|
639
|
+
output.push("");
|
|
640
|
+
output.push(`export type ${typeName} = z.infer<typeof ${typeName}>;`);
|
|
641
|
+
output.push("");
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
}
|
|
418
645
|
function generate(spec, options = {}) {
|
|
419
646
|
return generateWithMetadata(spec, options).output;
|
|
420
647
|
}
|
|
@@ -430,13 +657,8 @@ function appendErrorGroup(buffer, label, errors) {
|
|
|
430
657
|
}
|
|
431
658
|
buffer.push(" },");
|
|
432
659
|
}
|
|
433
|
-
function
|
|
434
|
-
return
|
|
435
|
-
group.clientErrors,
|
|
436
|
-
group.serverErrors,
|
|
437
|
-
group.defaultErrors,
|
|
438
|
-
group.otherErrors
|
|
439
|
-
].some((bucket) => bucket && Object.keys(bucket).length > 0);
|
|
660
|
+
function hasAnyErrors2(group) {
|
|
661
|
+
return Boolean(group && Object.keys(group).length > 0);
|
|
440
662
|
}
|
|
441
663
|
function isRequestMethod(method) {
|
|
442
664
|
switch (method) {
|
|
@@ -483,33 +705,70 @@ function inferResponseType(contentType, statusCode) {
|
|
|
483
705
|
}
|
|
484
706
|
return "unknown";
|
|
485
707
|
}
|
|
486
|
-
function parseOperations(spec) {
|
|
708
|
+
function parseOperations(spec, nameMap) {
|
|
487
709
|
const operations = [];
|
|
488
|
-
|
|
489
|
-
for (const [
|
|
490
|
-
const
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
710
|
+
if (spec.paths) {
|
|
711
|
+
for (const [path, pathItem] of Object.entries(spec.paths)) {
|
|
712
|
+
for (const [method, operation] of Object.entries(pathItem)) {
|
|
713
|
+
const normalizedMethod = method.toLowerCase();
|
|
714
|
+
if (!isRequestMethod(normalizedMethod)) continue;
|
|
715
|
+
if (!operation.operationId) continue;
|
|
716
|
+
const pathParams = extractPathParams(path);
|
|
717
|
+
const requestType = getRequestType(operation);
|
|
718
|
+
const { successResponse, errors } = getResponseTypes(
|
|
719
|
+
operation,
|
|
720
|
+
operation.operationId,
|
|
721
|
+
nameMap
|
|
722
|
+
);
|
|
723
|
+
const resolvedParameters = collectParameters(pathItem, operation, spec);
|
|
724
|
+
const requestHeaders = getRequestHeaders(resolvedParameters);
|
|
725
|
+
const queryParams = getQueryParams(resolvedParameters);
|
|
726
|
+
operations.push({
|
|
727
|
+
operationId: operation.operationId,
|
|
728
|
+
path,
|
|
729
|
+
method: normalizedMethod,
|
|
730
|
+
pathParams,
|
|
731
|
+
queryParams,
|
|
732
|
+
requestType,
|
|
733
|
+
responseType: successResponse,
|
|
734
|
+
requestHeaders,
|
|
735
|
+
errors
|
|
736
|
+
});
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
if (spec.webhooks) {
|
|
741
|
+
for (const [webhookName, webhookItem] of Object.entries(spec.webhooks)) {
|
|
742
|
+
for (const [method, operation] of Object.entries(webhookItem)) {
|
|
743
|
+
const normalizedMethod = method.toLowerCase();
|
|
744
|
+
if (!isRequestMethod(normalizedMethod)) continue;
|
|
745
|
+
if (!operation.operationId) continue;
|
|
746
|
+
const path = webhookName;
|
|
747
|
+
const pathParams = extractPathParams(path);
|
|
748
|
+
const requestType = getRequestType(operation);
|
|
749
|
+
const { successResponse, errors } = getResponseTypes(
|
|
750
|
+
operation,
|
|
751
|
+
operation.operationId
|
|
752
|
+
);
|
|
753
|
+
const resolvedParameters = collectParameters(
|
|
754
|
+
webhookItem,
|
|
755
|
+
operation,
|
|
756
|
+
spec
|
|
757
|
+
);
|
|
758
|
+
const requestHeaders = getRequestHeaders(resolvedParameters);
|
|
759
|
+
const queryParams = getQueryParams(resolvedParameters);
|
|
760
|
+
operations.push({
|
|
761
|
+
operationId: operation.operationId,
|
|
762
|
+
path,
|
|
763
|
+
method: normalizedMethod,
|
|
764
|
+
pathParams,
|
|
765
|
+
queryParams,
|
|
766
|
+
requestType,
|
|
767
|
+
responseType: successResponse,
|
|
768
|
+
requestHeaders,
|
|
769
|
+
errors
|
|
770
|
+
});
|
|
771
|
+
}
|
|
513
772
|
}
|
|
514
773
|
}
|
|
515
774
|
return operations;
|
|
@@ -518,21 +777,42 @@ function normalizeTypesConfig(config) {
|
|
|
518
777
|
return {
|
|
519
778
|
emit: config?.emit ?? true,
|
|
520
779
|
helpers: config?.helpers ?? "package",
|
|
521
|
-
helpersOutput: config?.helpersOutput ?? "./zenko-types"
|
|
780
|
+
helpersOutput: config?.helpersOutput ?? "./zenko-types",
|
|
781
|
+
treeShake: config?.treeShake ?? true
|
|
522
782
|
};
|
|
523
783
|
}
|
|
524
|
-
function appendHelperTypesImport(buffer, config) {
|
|
784
|
+
function appendHelperTypesImport(buffer, config, operations) {
|
|
525
785
|
if (!config.emit) return;
|
|
526
786
|
switch (config.helpers) {
|
|
527
787
|
case "package":
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
788
|
+
if (config.treeShake) {
|
|
789
|
+
const usage = analyzeZenkoUsage(operations);
|
|
790
|
+
const importStatement = generateZenkoImport(usage, "package");
|
|
791
|
+
if (importStatement) {
|
|
792
|
+
buffer.push(importStatement);
|
|
793
|
+
}
|
|
794
|
+
} else {
|
|
795
|
+
buffer.push(
|
|
796
|
+
'import type { PathFn, HeaderFn, OperationDefinition, OperationErrors } from "zenko";'
|
|
797
|
+
);
|
|
798
|
+
}
|
|
531
799
|
return;
|
|
532
800
|
case "file":
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
801
|
+
if (config.treeShake) {
|
|
802
|
+
const usage = analyzeZenkoUsage(operations);
|
|
803
|
+
const importStatement = generateZenkoImport(
|
|
804
|
+
usage,
|
|
805
|
+
"file",
|
|
806
|
+
config.helpersOutput
|
|
807
|
+
);
|
|
808
|
+
if (importStatement) {
|
|
809
|
+
buffer.push(importStatement);
|
|
810
|
+
}
|
|
811
|
+
} else {
|
|
812
|
+
buffer.push(
|
|
813
|
+
`import type { PathFn, HeaderFn, OperationDefinition, OperationErrors } from "${config.helpersOutput}";`
|
|
814
|
+
);
|
|
815
|
+
}
|
|
536
816
|
return;
|
|
537
817
|
case "inline":
|
|
538
818
|
buffer.push(
|
|
@@ -548,24 +828,19 @@ function appendHelperTypesImport(buffer, config) {
|
|
|
548
828
|
"type AnyHeaderFn = HeaderFn<any, unknown> | (() => unknown);"
|
|
549
829
|
);
|
|
550
830
|
buffer.push(
|
|
551
|
-
"type OperationErrors<
|
|
831
|
+
"type OperationErrors<TError = unknown> = TError extends Record<string, unknown> ? TError : Record<string, TError>;"
|
|
552
832
|
);
|
|
553
|
-
buffer.push(" clientErrors?: TClient;");
|
|
554
|
-
buffer.push(" serverErrors?: TServer;");
|
|
555
|
-
buffer.push(" defaultErrors?: TDefault;");
|
|
556
|
-
buffer.push(" otherErrors?: TOther;");
|
|
557
|
-
buffer.push("};");
|
|
558
833
|
buffer.push(
|
|
559
834
|
"type OperationDefinition<TMethod extends RequestMethod, TPath extends (...args: any[]) => string, TRequest = undefined, TResponse = undefined, THeaders extends AnyHeaderFn | undefined = undefined, TErrors extends OperationErrors | undefined = undefined> = {"
|
|
560
835
|
);
|
|
561
|
-
buffer.push(" method: TMethod
|
|
562
|
-
buffer.push(" path: TPath
|
|
563
|
-
buffer.push(" request?: TRequest
|
|
564
|
-
buffer.push(" response?: TResponse
|
|
565
|
-
buffer.push(" headers?: THeaders
|
|
566
|
-
buffer.push(" errors?: TErrors
|
|
567
|
-
buffer.push("}
|
|
568
|
-
|
|
836
|
+
buffer.push(" method: TMethod");
|
|
837
|
+
buffer.push(" path: TPath");
|
|
838
|
+
buffer.push(" request?: TRequest");
|
|
839
|
+
buffer.push(" response?: TResponse");
|
|
840
|
+
buffer.push(" headers?: THeaders");
|
|
841
|
+
buffer.push(" errors?: TErrors");
|
|
842
|
+
buffer.push("}");
|
|
843
|
+
buffer.push("");
|
|
569
844
|
}
|
|
570
845
|
}
|
|
571
846
|
function generateOperationTypes(buffer, operations, config) {
|
|
@@ -593,14 +868,11 @@ function generateOperationTypes(buffer, operations, config) {
|
|
|
593
868
|
}
|
|
594
869
|
}
|
|
595
870
|
function buildOperationErrorsType(errors) {
|
|
596
|
-
if (!errors || !
|
|
871
|
+
if (!errors || !hasAnyErrors2(errors)) {
|
|
597
872
|
return "OperationErrors";
|
|
598
873
|
}
|
|
599
|
-
const
|
|
600
|
-
|
|
601
|
-
const fallback = buildErrorBucket(errors.defaultErrors);
|
|
602
|
-
const other = buildErrorBucket(errors.otherErrors);
|
|
603
|
-
return `OperationErrors<${client}, ${server}, ${fallback}, ${other}>`;
|
|
874
|
+
const errorBucket = buildErrorBucket(errors);
|
|
875
|
+
return `OperationErrors<${errorBucket}>`;
|
|
604
876
|
}
|
|
605
877
|
function buildErrorBucket(bucket) {
|
|
606
878
|
if (!bucket || Object.keys(bucket).length === 0) {
|
|
@@ -707,10 +979,10 @@ function getRequestType(operation) {
|
|
|
707
979
|
if (requestBody.$ref) {
|
|
708
980
|
return extractRefName(requestBody.$ref);
|
|
709
981
|
}
|
|
710
|
-
const typeName = `${capitalize(operation.operationId)}Request`;
|
|
982
|
+
const typeName = `${capitalize(toCamelCase(operation.operationId))}Request`;
|
|
711
983
|
return typeName;
|
|
712
984
|
}
|
|
713
|
-
function getResponseTypes(operation, operationId) {
|
|
985
|
+
function getResponseTypes(operation, operationId, nameMap) {
|
|
714
986
|
const responses = operation.responses ?? {};
|
|
715
987
|
const successCodes = /* @__PURE__ */ new Map();
|
|
716
988
|
const errorEntries = [];
|
|
@@ -751,11 +1023,15 @@ function getResponseTypes(operation, operationId) {
|
|
|
751
1023
|
successCodes.set(statusCode, resolvedSchema);
|
|
752
1024
|
}
|
|
753
1025
|
}
|
|
754
|
-
const successResponse = selectSuccessResponse(
|
|
755
|
-
|
|
1026
|
+
const successResponse = selectSuccessResponse(
|
|
1027
|
+
successCodes,
|
|
1028
|
+
operationId,
|
|
1029
|
+
nameMap
|
|
1030
|
+
);
|
|
1031
|
+
const errors = buildErrorGroups(errorEntries, operationId, nameMap);
|
|
756
1032
|
return { successResponse, errors };
|
|
757
1033
|
}
|
|
758
|
-
function selectSuccessResponse(responses, operationId) {
|
|
1034
|
+
function selectSuccessResponse(responses, operationId, nameMap) {
|
|
759
1035
|
if (responses.size === 0) return void 0;
|
|
760
1036
|
const preferredOrder = ["200", "201", "204"];
|
|
761
1037
|
for (const code of preferredOrder) {
|
|
@@ -766,61 +1042,51 @@ function selectSuccessResponse(responses, operationId) {
|
|
|
766
1042
|
}
|
|
767
1043
|
return resolveResponseType(
|
|
768
1044
|
schema,
|
|
769
|
-
`${capitalize(operationId)}Response
|
|
1045
|
+
`${capitalize(toCamelCase(operationId))}Response`,
|
|
1046
|
+
nameMap
|
|
770
1047
|
);
|
|
771
1048
|
}
|
|
772
1049
|
}
|
|
773
|
-
const [
|
|
1050
|
+
const [, firstSchema] = responses.entries().next().value ?? [];
|
|
774
1051
|
if (!firstSchema) return void 0;
|
|
775
1052
|
if (typeof firstSchema === "string") {
|
|
776
1053
|
return firstSchema;
|
|
777
1054
|
}
|
|
778
1055
|
return resolveResponseType(
|
|
779
1056
|
firstSchema,
|
|
780
|
-
`${capitalize(operationId)}Response
|
|
1057
|
+
`${capitalize(toCamelCase(operationId))}Response`,
|
|
1058
|
+
nameMap
|
|
781
1059
|
);
|
|
782
1060
|
}
|
|
783
|
-
function buildErrorGroups(errors = [], operationId) {
|
|
1061
|
+
function buildErrorGroups(errors = [], operationId, nameMap) {
|
|
784
1062
|
if (!errors.length) return void 0;
|
|
785
1063
|
const group = {};
|
|
786
1064
|
for (const { code, schema } of errors) {
|
|
787
|
-
const category = getStatusCategory(code);
|
|
788
1065
|
const identifier = mapStatusToIdentifier(code);
|
|
789
1066
|
const typeName = resolveResponseType(
|
|
790
1067
|
schema,
|
|
791
|
-
`${capitalize(operationId)}${capitalize(identifier)}
|
|
1068
|
+
`${capitalize(toCamelCase(operationId))}${capitalize(identifier)}`,
|
|
1069
|
+
nameMap
|
|
792
1070
|
);
|
|
793
|
-
|
|
794
|
-
case "client":
|
|
795
|
-
group.clientErrors ??= {};
|
|
796
|
-
group.clientErrors[identifier] = typeName;
|
|
797
|
-
break;
|
|
798
|
-
case "server":
|
|
799
|
-
group.serverErrors ??= {};
|
|
800
|
-
group.serverErrors[identifier] = typeName;
|
|
801
|
-
break;
|
|
802
|
-
case "default":
|
|
803
|
-
group.defaultErrors ??= {};
|
|
804
|
-
group.defaultErrors[identifier] = typeName;
|
|
805
|
-
break;
|
|
806
|
-
default:
|
|
807
|
-
group.otherErrors ??= {};
|
|
808
|
-
group.otherErrors[identifier] = typeName;
|
|
809
|
-
break;
|
|
810
|
-
}
|
|
1071
|
+
group[identifier] = typeName;
|
|
811
1072
|
}
|
|
812
1073
|
return group;
|
|
813
1074
|
}
|
|
814
|
-
function resolveResponseType(schema, fallbackName) {
|
|
1075
|
+
function resolveResponseType(schema, fallbackName, nameMap) {
|
|
815
1076
|
if (typeof schema === "string") {
|
|
816
1077
|
return schema;
|
|
817
1078
|
}
|
|
818
1079
|
if (schema.$ref) {
|
|
819
|
-
|
|
1080
|
+
const refName = extractRefName(schema.$ref);
|
|
1081
|
+
return nameMap?.get(refName) || refName;
|
|
820
1082
|
}
|
|
821
1083
|
if (schema.type === "array" && schema.items?.$ref) {
|
|
822
1084
|
const itemRef = extractRefName(schema.items.$ref);
|
|
823
|
-
|
|
1085
|
+
const sanitizedItemRef = nameMap?.get(itemRef) || itemRef;
|
|
1086
|
+
return `z.array(${sanitizedItemRef})`;
|
|
1087
|
+
}
|
|
1088
|
+
if (schema.allOf && Array.isArray(schema.allOf)) {
|
|
1089
|
+
return fallbackName;
|
|
824
1090
|
}
|
|
825
1091
|
return fallbackName;
|
|
826
1092
|
}
|
|
@@ -903,19 +1169,30 @@ function convertQueryParamValue(schema, accessor) {
|
|
|
903
1169
|
return `String(${accessor})`;
|
|
904
1170
|
}
|
|
905
1171
|
}
|
|
906
|
-
function generateZodSchema(name, schema, generatedTypes, options) {
|
|
1172
|
+
function generateZodSchema(name, schema, generatedTypes, options, nameMap) {
|
|
907
1173
|
if (generatedTypes.has(name)) return "";
|
|
908
1174
|
generatedTypes.add(name);
|
|
909
1175
|
if (schema.enum) {
|
|
910
1176
|
const enumValues = schema.enum.map((v) => `"${v}"`).join(", ");
|
|
911
1177
|
return `export const ${name} = z.enum([${enumValues}]);`;
|
|
912
1178
|
}
|
|
1179
|
+
if (schema.allOf && Array.isArray(schema.allOf)) {
|
|
1180
|
+
const allOfParts = schema.allOf.map(
|
|
1181
|
+
(part) => getZodTypeFromSchema(part, options, nameMap)
|
|
1182
|
+
);
|
|
1183
|
+
if (allOfParts.length === 0) return `export const ${name} = z.object({});`;
|
|
1184
|
+
if (allOfParts.length === 1)
|
|
1185
|
+
return `export const ${name} = ${allOfParts[0]};`;
|
|
1186
|
+
const first = allOfParts[0];
|
|
1187
|
+
const rest = allOfParts.slice(1).map((part) => `.and(${part})`).join("");
|
|
1188
|
+
return `export const ${name} = ${first}${rest};`;
|
|
1189
|
+
}
|
|
913
1190
|
if (schema.type === "object" || schema.properties) {
|
|
914
|
-
return `export const ${name} = ${buildZodObject(schema, options)};`;
|
|
1191
|
+
return `export const ${name} = ${buildZodObject(schema, options, nameMap)};`;
|
|
915
1192
|
}
|
|
916
1193
|
if (schema.type === "array") {
|
|
917
1194
|
const itemSchema = schema.items ?? { type: "unknown" };
|
|
918
|
-
const itemType = getZodTypeFromSchema(itemSchema, options);
|
|
1195
|
+
const itemType = getZodTypeFromSchema(itemSchema, options, nameMap);
|
|
919
1196
|
const builder = applyStrictArrayBounds(
|
|
920
1197
|
schema,
|
|
921
1198
|
`z.array(${itemType})`,
|
|
@@ -924,18 +1201,29 @@ function generateZodSchema(name, schema, generatedTypes, options) {
|
|
|
924
1201
|
);
|
|
925
1202
|
return `export const ${name} = ${builder};`;
|
|
926
1203
|
}
|
|
927
|
-
return `export const ${name} = ${getZodTypeFromSchema(schema, options)};`;
|
|
1204
|
+
return `export const ${name} = ${getZodTypeFromSchema(schema, options, nameMap)};`;
|
|
928
1205
|
}
|
|
929
|
-
function getZodTypeFromSchema(schema, options) {
|
|
1206
|
+
function getZodTypeFromSchema(schema, options, nameMap) {
|
|
930
1207
|
if (schema.$ref) {
|
|
931
|
-
|
|
1208
|
+
const refName = extractRefName(schema.$ref);
|
|
1209
|
+
return nameMap?.get(refName) || refName;
|
|
932
1210
|
}
|
|
933
1211
|
if (schema.enum) {
|
|
934
1212
|
const enumValues = schema.enum.map((v) => `"${v}"`).join(", ");
|
|
935
1213
|
return `z.enum([${enumValues}])`;
|
|
936
1214
|
}
|
|
937
|
-
if (schema.
|
|
938
|
-
|
|
1215
|
+
if (schema.allOf && Array.isArray(schema.allOf)) {
|
|
1216
|
+
const allOfParts = schema.allOf.map(
|
|
1217
|
+
(part) => getZodTypeFromSchema(part, options, nameMap)
|
|
1218
|
+
);
|
|
1219
|
+
if (allOfParts.length === 0) return "z.object({})";
|
|
1220
|
+
if (allOfParts.length === 1) return allOfParts[0];
|
|
1221
|
+
const first = allOfParts[0];
|
|
1222
|
+
const rest = allOfParts.slice(1).map((part) => `.and(${part})`).join("");
|
|
1223
|
+
return `${first}${rest}`;
|
|
1224
|
+
}
|
|
1225
|
+
if (schema.type === "object" || schema.properties || schema.allOf || schema.oneOf || schema.anyOf) {
|
|
1226
|
+
return buildZodObject(schema, options, nameMap);
|
|
939
1227
|
}
|
|
940
1228
|
switch (schema.type) {
|
|
941
1229
|
case "string":
|
|
@@ -945,7 +1233,8 @@ function getZodTypeFromSchema(schema, options) {
|
|
|
945
1233
|
case "array":
|
|
946
1234
|
return `z.array(${getZodTypeFromSchema(
|
|
947
1235
|
schema.items ?? { type: "unknown" },
|
|
948
|
-
options
|
|
1236
|
+
options,
|
|
1237
|
+
nameMap
|
|
949
1238
|
)})`;
|
|
950
1239
|
case "null":
|
|
951
1240
|
return "z.null()";
|
|
@@ -957,13 +1246,13 @@ function getZodTypeFromSchema(schema, options) {
|
|
|
957
1246
|
return "z.unknown()";
|
|
958
1247
|
}
|
|
959
1248
|
}
|
|
960
|
-
function buildZodObject(schema, options) {
|
|
1249
|
+
function buildZodObject(schema, options, nameMap) {
|
|
961
1250
|
const properties = [];
|
|
962
1251
|
for (const [propName, propSchema] of Object.entries(
|
|
963
1252
|
schema.properties || {}
|
|
964
1253
|
)) {
|
|
965
1254
|
const isRequired = schema.required?.includes(propName) ?? false;
|
|
966
|
-
const zodType = getZodTypeFromSchema(propSchema, options);
|
|
1255
|
+
const zodType = getZodTypeFromSchema(propSchema, options, nameMap);
|
|
967
1256
|
const finalType = isRequired ? zodType : `${zodType}.optional()`;
|
|
968
1257
|
properties.push(` ${formatPropertyName(propName)}: ${finalType},`);
|
|
969
1258
|
}
|
|
@@ -1071,57 +1360,6 @@ function applyNumericBounds(schema, builder) {
|
|
|
1071
1360
|
}
|
|
1072
1361
|
return builder;
|
|
1073
1362
|
}
|
|
1074
|
-
function generateHelperFile() {
|
|
1075
|
-
const output = [];
|
|
1076
|
-
output.push("// Generated helper types for Zenko");
|
|
1077
|
-
output.push(
|
|
1078
|
-
"// This file provides type definitions for operation objects and path functions"
|
|
1079
|
-
);
|
|
1080
|
-
output.push("");
|
|
1081
|
-
output.push(
|
|
1082
|
-
"export type PathFn<TArgs extends unknown[] = []> = (...args: TArgs) => string"
|
|
1083
|
-
);
|
|
1084
|
-
output.push("");
|
|
1085
|
-
output.push(
|
|
1086
|
-
'export type RequestMethod = "get" | "put" | "post" | "delete" | "options" | "head" | "patch" | "trace"'
|
|
1087
|
-
);
|
|
1088
|
-
output.push("");
|
|
1089
|
-
output.push(
|
|
1090
|
-
"export type HeaderFn<TArgs extends unknown[] = [], TResult = Record<string, unknown> | Record<string, never>> = (...args: TArgs) => TResult"
|
|
1091
|
-
);
|
|
1092
|
-
output.push("");
|
|
1093
|
-
output.push(
|
|
1094
|
-
"export type AnyHeaderFn = HeaderFn<any, unknown> | (() => unknown)"
|
|
1095
|
-
);
|
|
1096
|
-
output.push("");
|
|
1097
|
-
output.push(
|
|
1098
|
-
"export type OperationErrors<TClient = unknown, TServer = unknown, TDefault = unknown, TOther = unknown> = {"
|
|
1099
|
-
);
|
|
1100
|
-
output.push(" clientErrors?: TClient");
|
|
1101
|
-
output.push(" serverErrors?: TServer");
|
|
1102
|
-
output.push(" defaultErrors?: TDefault");
|
|
1103
|
-
output.push(" otherErrors?: TOther");
|
|
1104
|
-
output.push("}");
|
|
1105
|
-
output.push("");
|
|
1106
|
-
output.push(
|
|
1107
|
-
"export type OperationDefinition<TMethod extends RequestMethod, TPath extends (...args: any[]) => string, TRequest = undefined, TResponse = undefined, THeaders extends AnyHeaderFn | undefined = undefined, TErrors extends OperationErrors | undefined = undefined> = {"
|
|
1108
|
-
);
|
|
1109
|
-
output.push(" method: TMethod");
|
|
1110
|
-
output.push(" path: TPath");
|
|
1111
|
-
output.push(" request?: TRequest");
|
|
1112
|
-
output.push(" response?: TResponse");
|
|
1113
|
-
output.push(" headers?: THeaders");
|
|
1114
|
-
output.push(" errors?: TErrors");
|
|
1115
|
-
output.push("}");
|
|
1116
|
-
output.push("");
|
|
1117
|
-
return output.join("\n");
|
|
1118
|
-
}
|
|
1119
|
-
function toCamelCase(str) {
|
|
1120
|
-
return str.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
|
|
1121
|
-
}
|
|
1122
|
-
function capitalize(str) {
|
|
1123
|
-
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
1124
|
-
}
|
|
1125
1363
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1126
1364
|
0 && (module.exports = {
|
|
1127
1365
|
generate
|