@kubb/plugin-fetch 5.0.0-beta.77 → 5.0.0-beta.80
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.cjs +276 -88
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +276 -88
- package/dist/index.js.map +1 -1
- package/package.json +7 -7
- package/src/generators/clientGenerator.tsx +24 -11
- package/src/plugin.ts +19 -5
- package/src/templates.ts +9 -4
- package/templates/fetch.ts +300 -174
- package/templates/serializers.ts +424 -0
- package/templates/standardSchema.ts +54 -0
package/dist/index.cjs
CHANGED
|
@@ -103,7 +103,7 @@ function toFilePath(name, caseLast = camelCase) {
|
|
|
103
103
|
* JavaScript and Java reserved words.
|
|
104
104
|
* @link https://github.com/jonschlinkert/reserved/blob/master/index.js
|
|
105
105
|
*/
|
|
106
|
-
const reservedWords = new Set([
|
|
106
|
+
const reservedWords = /* @__PURE__ */ new Set([
|
|
107
107
|
"abstract",
|
|
108
108
|
"arguments",
|
|
109
109
|
"boolean",
|
|
@@ -352,6 +352,23 @@ function caseParams(params, casing) {
|
|
|
352
352
|
caseParamsCache.set(params, result);
|
|
353
353
|
return result;
|
|
354
354
|
}
|
|
355
|
+
/**
|
|
356
|
+
* Drops parameters that collapse to the same property identity once camelCased, keeping the first.
|
|
357
|
+
*
|
|
358
|
+
* Some specs declare the same parameter twice under different casings (for example AWS S3 lists both
|
|
359
|
+
* `max-uploads` and `MaxUploads`). Both resolve to one output property, so emitting both would yield
|
|
360
|
+
* an object type with a duplicate member, which TypeScript rejects. De-duplicate by the camelCased
|
|
361
|
+
* identity so the resulting group is collision-free regardless of the names each caller carries.
|
|
362
|
+
*/
|
|
363
|
+
function dedupeByCasedName(params) {
|
|
364
|
+
const seen = /* @__PURE__ */ new Set();
|
|
365
|
+
return params.filter((param) => {
|
|
366
|
+
const key = camelCase(param.name);
|
|
367
|
+
if (seen.has(key)) return false;
|
|
368
|
+
seen.add(key);
|
|
369
|
+
return true;
|
|
370
|
+
});
|
|
371
|
+
}
|
|
355
372
|
//#endregion
|
|
356
373
|
//#region ../../internals/shared/src/operation.ts
|
|
357
374
|
/**
|
|
@@ -390,6 +407,86 @@ function getContentTypeInfo(node) {
|
|
|
390
407
|
hasFormData: contentTypes.some((ct) => ct === "multipart/form-data")
|
|
391
408
|
};
|
|
392
409
|
}
|
|
410
|
+
/**
|
|
411
|
+
* The request-body counterpart for the primary success response: the content types it documents and
|
|
412
|
+
* whether several are present, so the client can let a caller pick which one to accept.
|
|
413
|
+
*/
|
|
414
|
+
function getResponseContentTypeInfo(node) {
|
|
415
|
+
const contentTypes = getPrimarySuccessResponse(node)?.content?.map((e) => e.contentType) ?? [];
|
|
416
|
+
const isMultipleContentTypes = contentTypes.length > 1;
|
|
417
|
+
return {
|
|
418
|
+
contentTypes,
|
|
419
|
+
isMultipleContentTypes,
|
|
420
|
+
contentTypeUnion: isMultipleContentTypes ? contentTypes.map((ct) => JSON.stringify(ct)).join(" | ") : "",
|
|
421
|
+
defaultContentType: contentTypes[0] ?? "application/json",
|
|
422
|
+
hasFormData: contentTypes.some((ct) => ct === "multipart/form-data")
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
/**
|
|
426
|
+
* Reads the single base content type of an operation's primary success response, lowercased and
|
|
427
|
+
* stripped of any `; charset=...` suffix. Returns `undefined` when the response declares zero or
|
|
428
|
+
* more than one content type, since neither case has a single type to act on.
|
|
429
|
+
*/
|
|
430
|
+
function getPrimarySuccessContentType(node) {
|
|
431
|
+
const contentTypes = getPrimarySuccessResponse(node)?.content?.map((entry) => entry.contentType) ?? [];
|
|
432
|
+
if (contentTypes.length !== 1) return void 0;
|
|
433
|
+
return contentTypes[0].split(";")[0].trim().toLowerCase();
|
|
434
|
+
}
|
|
435
|
+
/**
|
|
436
|
+
* Whether an operation streams its primary success response as Server-Sent Events
|
|
437
|
+
* (`text/event-stream`). The client generator uses this to return a typed event stream instead of a
|
|
438
|
+
* one-shot `RequestResult`.
|
|
439
|
+
*/
|
|
440
|
+
function isEventStream(node) {
|
|
441
|
+
return getPrimarySuccessContentType(node) === "text/event-stream";
|
|
442
|
+
}
|
|
443
|
+
/**
|
|
444
|
+
* Derives the default `responseType` for an operation from its primary success response.
|
|
445
|
+
*
|
|
446
|
+
* Returns a value only when that response declares a single non-JSON content type. `text/event-stream`
|
|
447
|
+
* and other binary types (`application/octet-stream`, `application/pdf`, `image/*`, `audio/*`,
|
|
448
|
+
* `video/*`) map to a stream or `'blob'`, and other `text/*` maps to `'text'`. Otherwise `undefined`,
|
|
449
|
+
* leaving the runtime client's `Content-Type` auto-detection in charge.
|
|
450
|
+
*/
|
|
451
|
+
function getResponseType(node) {
|
|
452
|
+
const baseType = getPrimarySuccessContentType(node);
|
|
453
|
+
if (!baseType) return void 0;
|
|
454
|
+
if (baseType === "application/json" || baseType.endsWith("+json") || baseType === "text/json") return void 0;
|
|
455
|
+
if (baseType === "text/event-stream") return "stream";
|
|
456
|
+
if (baseType.startsWith("text/")) return "text";
|
|
457
|
+
if (baseType === "application/octet-stream" || baseType === "application/pdf" || /^(image|audio|video)\//.test(baseType)) return "blob";
|
|
458
|
+
}
|
|
459
|
+
/**
|
|
460
|
+
* Which of the grouped request options an operation carries.
|
|
461
|
+
*/
|
|
462
|
+
function getRequestGroups(node) {
|
|
463
|
+
const { path, query, header } = getOperationParameters(node);
|
|
464
|
+
return {
|
|
465
|
+
path: path.length > 0,
|
|
466
|
+
query: query.length > 0,
|
|
467
|
+
body: Boolean(node.requestBody?.content?.[0]?.schema),
|
|
468
|
+
headers: header.length > 0
|
|
469
|
+
};
|
|
470
|
+
}
|
|
471
|
+
/**
|
|
472
|
+
* Resolves which grouped request options an operation carries together with whether each group
|
|
473
|
+
* holds a required member. The grouped parameter stays optional only when nothing inside it is
|
|
474
|
+
* required, matching the generated `RequestConfig` type.
|
|
475
|
+
*/
|
|
476
|
+
function getRequestGroupOptionality(node) {
|
|
477
|
+
const groups = getRequestGroups(node);
|
|
478
|
+
const { path, query, header } = getOperationParameters(node);
|
|
479
|
+
const hasRequiredPath = path.some((param) => param.required);
|
|
480
|
+
const hasRequiredQuery = query.some((param) => param.required);
|
|
481
|
+
const hasRequiredHeader = header.some((param) => param.required);
|
|
482
|
+
return {
|
|
483
|
+
groups,
|
|
484
|
+
hasRequiredPath,
|
|
485
|
+
hasRequiredQuery,
|
|
486
|
+
hasRequiredHeader,
|
|
487
|
+
isOptional: !hasRequiredPath && !hasRequiredQuery && !hasRequiredHeader && !groups.body
|
|
488
|
+
};
|
|
489
|
+
}
|
|
393
490
|
function buildOperationComments(node, options = {}) {
|
|
394
491
|
const { link = "pathTemplate", linkPosition = "afterDeprecated", splitLines = false } = options;
|
|
395
492
|
const linkComment = getOperationLink(node, link);
|
|
@@ -410,10 +507,10 @@ function buildOperationComments(node, options = {}) {
|
|
|
410
507
|
function getOperationParameters(node, options = {}) {
|
|
411
508
|
const params = caseParams(node.parameters, options.paramsCasing === "original" ? void 0 : "camelcase");
|
|
412
509
|
return {
|
|
413
|
-
path: params.filter((param) => param.in === "path"),
|
|
414
|
-
query: params.filter((param) => param.in === "query"),
|
|
415
|
-
header: params.filter((param) => param.in === "header"),
|
|
416
|
-
cookie: params.filter((param) => param.in === "cookie")
|
|
510
|
+
path: dedupeByCasedName(params.filter((param) => param.in === "path")),
|
|
511
|
+
query: dedupeByCasedName(params.filter((param) => param.in === "query")),
|
|
512
|
+
header: dedupeByCasedName(params.filter((param) => param.in === "header")),
|
|
513
|
+
cookie: dedupeByCasedName(params.filter((param) => param.in === "cookie"))
|
|
417
514
|
};
|
|
418
515
|
}
|
|
419
516
|
function getStatusCodeNumber(statusCode) {
|
|
@@ -424,6 +521,15 @@ function isSuccessStatusCode(statusCode) {
|
|
|
424
521
|
const code = getStatusCodeNumber(statusCode);
|
|
425
522
|
return code !== null && code >= 200 && code < 300;
|
|
426
523
|
}
|
|
524
|
+
function getSuccessResponses(responses) {
|
|
525
|
+
return responses.filter((response) => isSuccessStatusCode(response.statusCode));
|
|
526
|
+
}
|
|
527
|
+
function getOperationSuccessResponses(node) {
|
|
528
|
+
return getSuccessResponses(node.responses);
|
|
529
|
+
}
|
|
530
|
+
function getPrimarySuccessResponse(node) {
|
|
531
|
+
return getOperationSuccessResponses(node)[0] ?? null;
|
|
532
|
+
}
|
|
427
533
|
//#endregion
|
|
428
534
|
//#region ../../internals/shared/src/group.ts
|
|
429
535
|
/**
|
|
@@ -456,39 +562,39 @@ function createGroupConfig(group) {
|
|
|
456
562
|
};
|
|
457
563
|
}
|
|
458
564
|
//#endregion
|
|
459
|
-
//#region ../../internals/client/src/builders/
|
|
565
|
+
//#region ../../internals/client/src/builders/validatorOptions.ts
|
|
460
566
|
/**
|
|
461
|
-
* Returns `true` when any direction of the
|
|
567
|
+
* Returns `true` when any direction of the validator uses zod (used for dependency checks).
|
|
462
568
|
*/
|
|
463
|
-
function
|
|
464
|
-
if (!
|
|
465
|
-
if (
|
|
466
|
-
return Boolean(
|
|
569
|
+
function isValidatorEnabled(validator) {
|
|
570
|
+
if (!validator) return false;
|
|
571
|
+
if (validator === "zod") return true;
|
|
572
|
+
return Boolean(validator.request || validator.response);
|
|
467
573
|
}
|
|
468
574
|
/**
|
|
469
575
|
* Returns `'zod'` when request body parsing is enabled, `null` otherwise. The string shorthand
|
|
470
576
|
* `'zod'` validates the response only, so it does not enable request parsing.
|
|
471
577
|
*/
|
|
472
|
-
function
|
|
473
|
-
if (!
|
|
474
|
-
return
|
|
578
|
+
function resolveRequestValidator(validator) {
|
|
579
|
+
if (!validator || validator === "zod") return null;
|
|
580
|
+
return validator.request ?? null;
|
|
475
581
|
}
|
|
476
582
|
/**
|
|
477
583
|
* Returns `'zod'` when query-parameters parsing is enabled, `null` otherwise. Only the object form
|
|
478
584
|
* `{ request: 'zod' }` enables it.
|
|
479
585
|
*/
|
|
480
|
-
function
|
|
481
|
-
if (!
|
|
482
|
-
return
|
|
586
|
+
function resolveQueryParamsValidator(validator) {
|
|
587
|
+
if (!validator || validator === "zod") return null;
|
|
588
|
+
return validator.request ?? null;
|
|
483
589
|
}
|
|
484
590
|
/**
|
|
485
591
|
* Returns `'zod'` when response parsing is enabled, `null` otherwise. The string shorthand `'zod'`
|
|
486
592
|
* maps to response parsing.
|
|
487
593
|
*/
|
|
488
|
-
function
|
|
489
|
-
if (!
|
|
490
|
-
if (
|
|
491
|
-
return
|
|
594
|
+
function resolveResponseValidator(validator) {
|
|
595
|
+
if (!validator) return null;
|
|
596
|
+
if (validator === "zod") return "zod";
|
|
597
|
+
return validator.response ?? null;
|
|
492
598
|
}
|
|
493
599
|
/**
|
|
494
600
|
* Resolves the zod expression a generated client validates a success response with. Only success
|
|
@@ -630,11 +736,13 @@ function buildGroupedOptionsSignature({ node, tsResolver }) {
|
|
|
630
736
|
node,
|
|
631
737
|
tsResolver
|
|
632
738
|
});
|
|
739
|
+
const { isOptional } = getRequestGroupOptionality(node);
|
|
633
740
|
return {
|
|
634
741
|
dataTypeName: requestConfigName,
|
|
635
742
|
paramsSignature: declarationPrinter.print((0, _kubb_plugin_ts.createFunctionParameters)({ params: [(0, _kubb_plugin_ts.createFunctionParameter)({
|
|
636
743
|
name: "options",
|
|
637
|
-
type: `Options<${requestConfigName}, ThrowOnError
|
|
744
|
+
type: `Options<${requestConfigName}, ThrowOnError>`,
|
|
745
|
+
...isOptional ? { default: "{}" } : {}
|
|
638
746
|
})] })) ?? "",
|
|
639
747
|
returnType: `Promise<RequestResult<${resultGenerics}>>`,
|
|
640
748
|
generics: ["ThrowOnError extends boolean = true"],
|
|
@@ -642,23 +750,74 @@ function buildGroupedOptionsSignature({ node, tsResolver }) {
|
|
|
642
750
|
};
|
|
643
751
|
}
|
|
644
752
|
//#endregion
|
|
753
|
+
//#region ../../internals/client/src/builders/styles.ts
|
|
754
|
+
/**
|
|
755
|
+
* Renders a parameter name as an object-literal key, quoting it when the camelCased name is not a
|
|
756
|
+
* bare identifier (for example a name that starts with a digit) so the emitted literal stays valid.
|
|
757
|
+
*/
|
|
758
|
+
function toKey(name) {
|
|
759
|
+
const cased = camelCase(name);
|
|
760
|
+
return isValidVarName(cased) ? cased : JSON.stringify(cased);
|
|
761
|
+
}
|
|
762
|
+
/**
|
|
763
|
+
* Serializes one parameter's metadata into a `{ style, explode }` literal, or `null` when the
|
|
764
|
+
* parameter carries neither. Path and query carry the serialization `style`; header and cookie use a
|
|
765
|
+
* fixed style (`simple` and `form`), so only `explode` is emitted for them.
|
|
766
|
+
*/
|
|
767
|
+
function serializeParameter(parameter) {
|
|
768
|
+
const parts = [];
|
|
769
|
+
if ((parameter.in === "path" || parameter.in === "query") && parameter.style) parts.push(`style: '${parameter.style}'`);
|
|
770
|
+
if (parameter.explode !== void 0) parts.push(`explode: ${parameter.explode}`);
|
|
771
|
+
return parts.length > 0 ? `{ ${parts.join(", ")} }` : null;
|
|
772
|
+
}
|
|
773
|
+
/**
|
|
774
|
+
* Builds the per-operation `styles` literal from the operation's parameters, grouped by location and
|
|
775
|
+
* keyed by the camelCased parameter name to match the generated `path` / `query` / `headers` keys.
|
|
776
|
+
* Only parameters whose source defines `style` or `explode` are emitted, so calls without
|
|
777
|
+
* serialization metadata keep the runtime defaults and existing output is unchanged. Returns `null`
|
|
778
|
+
* when no parameter carries metadata.
|
|
779
|
+
*
|
|
780
|
+
* @example
|
|
781
|
+
* ```ts
|
|
782
|
+
* // a path param with { style: 'matrix', explode: true } and a query param with { explode: false }
|
|
783
|
+
* buildStyles({ node }) // "{ path: { id: { style: 'matrix', explode: true } }, query: { tags: { explode: false } } }"
|
|
784
|
+
* ```
|
|
785
|
+
*/
|
|
786
|
+
function buildStyles({ node }) {
|
|
787
|
+
if (!_kubb_core.ast.isHttpOperationNode(node)) return null;
|
|
788
|
+
const groups = {
|
|
789
|
+
path: [],
|
|
790
|
+
query: [],
|
|
791
|
+
header: [],
|
|
792
|
+
cookie: []
|
|
793
|
+
};
|
|
794
|
+
for (const parameter of node.parameters) {
|
|
795
|
+
const literal = serializeParameter(parameter);
|
|
796
|
+
if (!literal) continue;
|
|
797
|
+
groups[parameter.in].push(`${toKey(parameter.name)}: ${literal}`);
|
|
798
|
+
}
|
|
799
|
+
const locations = Object.keys(groups).filter((location) => groups[location].length > 0);
|
|
800
|
+
if (locations.length === 0) return null;
|
|
801
|
+
return `{ ${locations.map((location) => `${location}: { ${groups[location].join(", ")} }`).join(", ")} }`;
|
|
802
|
+
}
|
|
803
|
+
//#endregion
|
|
645
804
|
//#region ../../internals/client/src/builders/validator.ts
|
|
646
805
|
/**
|
|
647
|
-
* Builds the
|
|
648
|
-
* response
|
|
649
|
-
*
|
|
806
|
+
* Builds the validator-hook references for one operation. Request validation runs before the send;
|
|
807
|
+
* response validation runs on the success body only. Returns `null` references when the matching
|
|
808
|
+
* direction is disabled or the schema is absent.
|
|
650
809
|
*/
|
|
651
|
-
function
|
|
810
|
+
function buildValidatorHooks({ node, validator, zodResolver }) {
|
|
652
811
|
const importedZodNames = [];
|
|
653
812
|
const hasRequestBody = Boolean(node.requestBody?.content?.[0]?.schema);
|
|
654
|
-
const zodRequestName = zodResolver &&
|
|
655
|
-
const request = zodRequestName
|
|
813
|
+
const zodRequestName = zodResolver && resolveRequestValidator(validator) === "zod" && hasRequestBody ? zodResolver.resolveDataName?.(node) : null;
|
|
814
|
+
const request = zodRequestName ?? null;
|
|
656
815
|
if (zodRequestName) importedZodNames.push(zodRequestName);
|
|
657
|
-
const responseParse = zodResolver &&
|
|
658
|
-
const response = responseParse ?
|
|
816
|
+
const responseParse = zodResolver && resolveResponseValidator(validator) === "zod" ? buildZodResponseParse(node, zodResolver) : null;
|
|
817
|
+
const response = responseParse ? responseParse.expression : null;
|
|
659
818
|
if (responseParse) importedZodNames.push(...responseParse.importNames);
|
|
660
|
-
const errorParse = zodResolver &&
|
|
661
|
-
const error = errorParse ?
|
|
819
|
+
const errorParse = zodResolver && resolveResponseValidator(validator) === "zod" ? buildZodErrorParse(node, zodResolver) : null;
|
|
820
|
+
const error = errorParse ? errorParse.expression : null;
|
|
662
821
|
if (errorParse) importedZodNames.push(...errorParse.importNames);
|
|
663
822
|
return {
|
|
664
823
|
request,
|
|
@@ -674,34 +833,49 @@ function buildParserHooks({ node, parser, zodResolver }) {
|
|
|
674
833
|
* single `options` object to the resolved client and returns the `RequestResult`. The type, signature,
|
|
675
834
|
* and call config are built with the AST factory, and only the jsx-renderer emits the source.
|
|
676
835
|
*/
|
|
677
|
-
function Operation({ name, node, tsResolver, zodResolver,
|
|
836
|
+
function Operation({ name, node, tsResolver, zodResolver, validator, security, isExportable = true, isIndexable = true }) {
|
|
678
837
|
if (!_kubb_core.ast.isHttpOperationNode(node)) return null;
|
|
679
838
|
const signature = buildGroupedOptionsSignature({
|
|
680
839
|
node,
|
|
681
840
|
tsResolver
|
|
682
841
|
});
|
|
683
|
-
const
|
|
842
|
+
const validators = buildValidatorHooks({
|
|
684
843
|
node,
|
|
685
|
-
|
|
844
|
+
validator,
|
|
686
845
|
zodResolver
|
|
687
846
|
});
|
|
688
847
|
const securityLiteral = buildSecurityMetadata({ security });
|
|
848
|
+
const stylesLiteral = buildStyles({ node });
|
|
689
849
|
const { defaultContentType } = getContentTypeInfo(node);
|
|
690
|
-
const
|
|
691
|
-
const
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
850
|
+
const bakedRequestContentType = Boolean(node.requestBody?.content?.[0]?.schema) && defaultContentType !== "application/json" ? defaultContentType : null;
|
|
851
|
+
const mergeContentType = Boolean(bakedRequestContentType) && getResponseContentTypeInfo(node).isMultipleContentTypes;
|
|
852
|
+
const contentTypeLiteral = !bakedRequestContentType ? null : mergeContentType ? `contentType: { request: '${bakedRequestContentType}', ...(typeof contentType === 'string' ? { request: contentType } : contentType) }` : `contentType: { request: '${bakedRequestContentType}' }`;
|
|
853
|
+
const eventStream = isEventStream(node);
|
|
854
|
+
const responseType = getResponseType(node);
|
|
855
|
+
const responseTypeLiteral = responseType ? `responseType: '${responseType}'` : null;
|
|
856
|
+
const validatorEntries = [
|
|
857
|
+
validators.request ? `request: ${validators.request}` : null,
|
|
858
|
+
validators.response ? `response: ${validators.response}` : null,
|
|
859
|
+
validators.error ? `error: ${validators.error}` : null
|
|
695
860
|
].filter(Boolean);
|
|
696
|
-
const
|
|
861
|
+
const validatorLiteral = validatorEntries.length ? `validator: { ${validatorEntries.join(", ")} }` : null;
|
|
697
862
|
const callConfig = `{ ${[
|
|
698
863
|
`method: '${node.method.toUpperCase()}'`,
|
|
699
864
|
`url: '${Url.toCasedTemplate(node.path, { casing: "camelcase" })}'`,
|
|
700
865
|
securityLiteral ? `security: ${securityLiteral}` : null,
|
|
701
|
-
|
|
866
|
+
stylesLiteral ? `styles: ${stylesLiteral}` : null,
|
|
867
|
+
validatorLiteral,
|
|
702
868
|
contentTypeLiteral,
|
|
869
|
+
responseTypeLiteral,
|
|
703
870
|
"...config"
|
|
704
871
|
].filter(Boolean).join(", ")} }`;
|
|
872
|
+
const eventType = `SuccessOf<${tsResolver.resolveResponsesName(node)}>`;
|
|
873
|
+
const returnType = eventStream ? `Promise<EventStreamResult<${eventType}>>` : signature.returnType;
|
|
874
|
+
const returnStatement = eventStream ? `return toEventStream<${eventType}>(request(${callConfig}))` : buildReturnStatement({
|
|
875
|
+
node,
|
|
876
|
+
tsResolver,
|
|
877
|
+
callConfig
|
|
878
|
+
});
|
|
705
879
|
return /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(_kubb_renderer_jsx.File.Source, {
|
|
706
880
|
name,
|
|
707
881
|
isExportable,
|
|
@@ -711,20 +885,16 @@ function Operation({ name, node, tsResolver, zodResolver, parser, security, isEx
|
|
|
711
885
|
export: isExportable,
|
|
712
886
|
generics: signature.generics,
|
|
713
887
|
params: signature.paramsSignature,
|
|
714
|
-
returnType
|
|
888
|
+
returnType,
|
|
715
889
|
JSDoc: { comments: buildOperationComments(node, {
|
|
716
890
|
link: "urlPath",
|
|
717
891
|
linkPosition: "beforeDeprecated",
|
|
718
892
|
splitLines: true
|
|
719
893
|
}) },
|
|
720
894
|
children: [
|
|
721
|
-
"const { client: request = client, ...config } = options",
|
|
895
|
+
mergeContentType ? "const { client: request = client, contentType, ...config } = options" : "const { client: request = client, ...config } = options",
|
|
722
896
|
/* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)("br", {}),
|
|
723
|
-
|
|
724
|
-
node,
|
|
725
|
-
tsResolver,
|
|
726
|
-
callConfig
|
|
727
|
-
})
|
|
897
|
+
returnStatement
|
|
728
898
|
]
|
|
729
899
|
})
|
|
730
900
|
});
|
|
@@ -733,23 +903,23 @@ function Operation({ name, node, tsResolver, zodResolver, parser, security, isEx
|
|
|
733
903
|
//#region ../../internals/client/src/builders/sdkMethod.ts
|
|
734
904
|
/**
|
|
735
905
|
* Builds the call config literal forwarded to the contract client, mirroring the shared `Operation`
|
|
736
|
-
* component: `{ method, url, security?,
|
|
906
|
+
* component: `{ method, url, security?, validator?, ...config }`. The `...config` spread carries every
|
|
737
907
|
* per-call field (including `throwOnError`), so the method stays a thin wrapper over the contract.
|
|
738
908
|
*/
|
|
739
|
-
function buildCallConfig({ node,
|
|
740
|
-
const
|
|
909
|
+
function buildCallConfig({ node, validator, zodResolver, security }) {
|
|
910
|
+
const validators = buildValidatorHooks({
|
|
741
911
|
node,
|
|
742
|
-
|
|
912
|
+
validator,
|
|
743
913
|
zodResolver
|
|
744
914
|
});
|
|
745
|
-
const
|
|
746
|
-
const
|
|
915
|
+
const validatorEntries = [validators.request ? `request: ${validators.request}` : null, validators.response ? `response: ${validators.response}` : null].filter(Boolean);
|
|
916
|
+
const validatorLiteral = validatorEntries.length ? `validator: { ${validatorEntries.join(", ")} }` : null;
|
|
747
917
|
const securityLiteral = buildSecurityMetadata({ security });
|
|
748
918
|
return `{ ${[
|
|
749
919
|
`method: '${node.method.toUpperCase()}'`,
|
|
750
920
|
`url: '${Url.toCasedTemplate(node.path, { casing: "camelcase" })}'`,
|
|
751
921
|
securityLiteral ? `security: ${securityLiteral}` : null,
|
|
752
|
-
|
|
922
|
+
validatorLiteral,
|
|
753
923
|
"...config"
|
|
754
924
|
].filter(Boolean).join(", ")} }`;
|
|
755
925
|
}
|
|
@@ -759,7 +929,7 @@ function buildCallConfig({ node, parser, zodResolver, security }) {
|
|
|
759
929
|
* returns the `RequestResult`. A per-call `options.client` still overrides the instance client, so
|
|
760
930
|
* one operation can be routed to a different environment without a new instance.
|
|
761
931
|
*/
|
|
762
|
-
function buildSdkMethod({ node, name, tsResolver, zodResolver,
|
|
932
|
+
function buildSdkMethod({ node, name, tsResolver, zodResolver, validator, security }) {
|
|
763
933
|
if (!_kubb_core.ast.isHttpOperationNode(node)) return "";
|
|
764
934
|
const signature = buildGroupedOptionsSignature({
|
|
765
935
|
node,
|
|
@@ -770,7 +940,7 @@ function buildSdkMethod({ node, name, tsResolver, zodResolver, parser, security
|
|
|
770
940
|
tsResolver,
|
|
771
941
|
callConfig: buildCallConfig({
|
|
772
942
|
node,
|
|
773
|
-
|
|
943
|
+
validator,
|
|
774
944
|
zodResolver,
|
|
775
945
|
security
|
|
776
946
|
})
|
|
@@ -796,13 +966,13 @@ function buildSdkMethod({ node, name, tsResolver, zodResolver, parser, security
|
|
|
796
966
|
* instance: `const api = new PetClient({ baseURL }); api.getPetById(...)`. A per-call `client` option
|
|
797
967
|
* still overrides the instance client for a one-off call.
|
|
798
968
|
*/
|
|
799
|
-
function SdkClient({ name, isExportable = true, isIndexable = true, operations,
|
|
969
|
+
function SdkClient({ name, isExportable = true, isIndexable = true, operations, validator, children }) {
|
|
800
970
|
const methods = operations.map(({ node, name: methodName, tsResolver, zodResolver, security }) => buildSdkMethod({
|
|
801
971
|
node,
|
|
802
972
|
name: methodName,
|
|
803
973
|
tsResolver,
|
|
804
974
|
zodResolver,
|
|
805
|
-
|
|
975
|
+
validator,
|
|
806
976
|
security
|
|
807
977
|
}));
|
|
808
978
|
const classCode = `export class ${name} {\n${[
|
|
@@ -848,13 +1018,13 @@ function SdkFacade({ name, isExportable = true, isIndexable = true, members, chi
|
|
|
848
1018
|
function resolveTypeImportNames(node, tsResolver) {
|
|
849
1019
|
return [tsResolver.resolveRequestConfigName(node), tsResolver.resolveResponsesName(node)];
|
|
850
1020
|
}
|
|
851
|
-
function resolveZodImportNames(node, zodResolver,
|
|
1021
|
+
function resolveZodImportNames(node, zodResolver, validator) {
|
|
852
1022
|
const { query: queryParams } = getOperationParameters(node, { paramsCasing: "original" });
|
|
853
1023
|
return [
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
1024
|
+
resolveResponseValidator(validator) === "zod" ? zodResolver.resolveResponseName?.(node) : null,
|
|
1025
|
+
resolveResponseValidator(validator) === "zod" ? buildZodErrorParse(node, zodResolver)?.expression ?? null : null,
|
|
1026
|
+
resolveRequestValidator(validator) === "zod" && node.requestBody?.content?.[0]?.schema ? zodResolver.resolveDataName?.(node) : null,
|
|
1027
|
+
resolveQueryParamsValidator(validator) === "zod" && queryParams.length > 0 ? zodResolver.resolveQueryParamsName?.(node, queryParams[0]) : null
|
|
858
1028
|
].filter((n) => Boolean(n));
|
|
859
1029
|
}
|
|
860
1030
|
/**
|
|
@@ -863,11 +1033,11 @@ function resolveZodImportNames(node, zodResolver, parser) {
|
|
|
863
1033
|
*/
|
|
864
1034
|
function buildControllers(nodes, ctx) {
|
|
865
1035
|
const { driver, resolver, root } = ctx;
|
|
866
|
-
const { output, group,
|
|
1036
|
+
const { output, group, validator } = ctx.options;
|
|
867
1037
|
const pluginTs = driver.getPlugin(_kubb_plugin_ts.pluginTsName);
|
|
868
1038
|
const tsResolver = driver.getResolver(_kubb_plugin_ts.pluginTsName);
|
|
869
1039
|
const tsPluginOptions = pluginTs.options;
|
|
870
|
-
const pluginZod =
|
|
1040
|
+
const pluginZod = isValidatorEnabled(validator) ? driver.getPlugin(_kubb_plugin_zod.pluginZodName) : null;
|
|
871
1041
|
const zodResolver = pluginZod ? driver.getResolver(_kubb_plugin_zod.pluginZodName) : null;
|
|
872
1042
|
const document = ctx.adapter.document;
|
|
873
1043
|
function buildOperationData(node) {
|
|
@@ -954,7 +1124,7 @@ function createSdkGenerator() {
|
|
|
954
1124
|
renderer: _kubb_renderer_jsx.jsxRenderer,
|
|
955
1125
|
operations(nodes, ctx) {
|
|
956
1126
|
const { config, resolver, root } = ctx;
|
|
957
|
-
const { output, group,
|
|
1127
|
+
const { output, group, validator, sdk } = ctx.options;
|
|
958
1128
|
if (!ctx.driver.getPlugin(_kubb_plugin_ts.pluginTsName) || !sdk) return null;
|
|
959
1129
|
const controllers = buildControllers(nodes, ctx);
|
|
960
1130
|
const clientPath = node_path.default.resolve(root, ".kubb/client.ts");
|
|
@@ -979,9 +1149,9 @@ function createSdkGenerator() {
|
|
|
979
1149
|
file: op.typeFile,
|
|
980
1150
|
names: resolveTypeImportNames(op.node, op.tsResolver)
|
|
981
1151
|
}));
|
|
982
|
-
const { namesByPath: zodNamesByPath, filesByPath: zodFilesByPath } =
|
|
1152
|
+
const { namesByPath: zodNamesByPath, filesByPath: zodFilesByPath } = isValidatorEnabled(validator) ? collectImportsByFile(ops, (op) => ({
|
|
983
1153
|
file: op.zodFile,
|
|
984
|
-
names: op.zodResolver ? resolveZodImportNames(op.node, op.zodResolver,
|
|
1154
|
+
names: op.zodResolver ? resolveZodImportNames(op.node, op.zodResolver, validator) : []
|
|
985
1155
|
})) : {
|
|
986
1156
|
namesByPath: /* @__PURE__ */ new Map(),
|
|
987
1157
|
filesByPath: /* @__PURE__ */ new Map()
|
|
@@ -1009,7 +1179,7 @@ function createSdkGenerator() {
|
|
|
1009
1179
|
path: clientPath,
|
|
1010
1180
|
isTypeOnly: true
|
|
1011
1181
|
}),
|
|
1012
|
-
|
|
1182
|
+
validator === "zod" && ops.some((op) => op.node.requestBody?.content?.[0]?.schema != null) && /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(_kubb_renderer_jsx.File.Import, {
|
|
1013
1183
|
name: ["z"],
|
|
1014
1184
|
path: "zod",
|
|
1015
1185
|
isTypeOnly: true
|
|
@@ -1020,7 +1190,7 @@ function createSdkGenerator() {
|
|
|
1020
1190
|
path: typeFilesByPath.get(filePath).path,
|
|
1021
1191
|
isTypeOnly: true
|
|
1022
1192
|
}, filePath)),
|
|
1023
|
-
|
|
1193
|
+
isValidatorEnabled(validator) && Array.from(zodNamesByPath.entries()).map(([filePath, set]) => /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(_kubb_renderer_jsx.File.Import, {
|
|
1024
1194
|
name: Array.from(set),
|
|
1025
1195
|
root: file.path,
|
|
1026
1196
|
path: zodFilesByPath.get(filePath).path
|
|
@@ -1028,7 +1198,7 @@ function createSdkGenerator() {
|
|
|
1028
1198
|
/* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(SdkClient, {
|
|
1029
1199
|
name: className,
|
|
1030
1200
|
operations: ops,
|
|
1031
|
-
|
|
1201
|
+
validator
|
|
1032
1202
|
})
|
|
1033
1203
|
]
|
|
1034
1204
|
}, file.path);
|
|
@@ -1139,18 +1309,18 @@ const clientGenerator = (0, _kubb_core.defineGenerator)({
|
|
|
1139
1309
|
operation(node, ctx) {
|
|
1140
1310
|
if (!_kubb_core.ast.isHttpOperationNode(node)) return null;
|
|
1141
1311
|
const { config, driver, resolver, root } = ctx;
|
|
1142
|
-
const { output,
|
|
1312
|
+
const { output, validator, group } = ctx.options;
|
|
1143
1313
|
const pluginTs = driver.getPlugin(_kubb_plugin_ts.pluginTsName);
|
|
1144
1314
|
if (!pluginTs) return null;
|
|
1145
1315
|
const tsResolver = driver.getResolver(_kubb_plugin_ts.pluginTsName);
|
|
1146
|
-
const pluginZod =
|
|
1316
|
+
const pluginZod = resolveResponseValidator(validator) === "zod" || resolveRequestValidator(validator) === "zod" ? driver.getPlugin(_kubb_plugin_zod.pluginZodName) : null;
|
|
1147
1317
|
const zodResolver = pluginZod ? driver.getResolver(_kubb_plugin_zod.pluginZodName) : null;
|
|
1148
1318
|
const hasRequestBody = Boolean(node.requestBody?.content?.[0]?.schema);
|
|
1149
1319
|
const importedTypeNames = [tsResolver.resolveRequestConfigName(node), tsResolver.resolveResponsesName(node)];
|
|
1150
1320
|
const importedZodNames = zodResolver ? [
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1321
|
+
resolveResponseValidator(validator) === "zod" ? zodResolver.resolveResponseName?.(node) : null,
|
|
1322
|
+
resolveResponseValidator(validator) === "zod" ? buildZodErrorParse(node, zodResolver)?.expression ?? null : null,
|
|
1323
|
+
resolveRequestValidator(validator) === "zod" && hasRequestBody ? zodResolver.resolveDataName?.(node) : null
|
|
1154
1324
|
].filter((name) => Boolean(name)) : [];
|
|
1155
1325
|
const meta = {
|
|
1156
1326
|
name: resolver.resolveName(node.operationId),
|
|
@@ -1176,6 +1346,7 @@ const clientGenerator = (0, _kubb_core.defineGenerator)({
|
|
|
1176
1346
|
path: node.path
|
|
1177
1347
|
});
|
|
1178
1348
|
const clientPath = node_path.default.resolve(root, ".kubb/client.ts");
|
|
1349
|
+
const eventStream = isEventStream(node);
|
|
1179
1350
|
return /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsxs)(_kubb_renderer_jsx.File, {
|
|
1180
1351
|
baseName: meta.file.baseName,
|
|
1181
1352
|
path: meta.file.path,
|
|
@@ -1198,12 +1369,16 @@ const clientGenerator = (0, _kubb_core.defineGenerator)({
|
|
|
1198
1369
|
}),
|
|
1199
1370
|
children: [
|
|
1200
1371
|
/* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(_kubb_renderer_jsx.File.Import, {
|
|
1201
|
-
name: ["client"],
|
|
1372
|
+
name: eventStream ? ["client", "toEventStream"] : ["client"],
|
|
1202
1373
|
root: meta.file.path,
|
|
1203
1374
|
path: clientPath
|
|
1204
1375
|
}),
|
|
1205
1376
|
/* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(_kubb_renderer_jsx.File.Import, {
|
|
1206
|
-
name: [
|
|
1377
|
+
name: eventStream ? [
|
|
1378
|
+
"Options",
|
|
1379
|
+
"EventStreamResult",
|
|
1380
|
+
"SuccessOf"
|
|
1381
|
+
] : ["Options", "RequestResult"],
|
|
1207
1382
|
root: meta.file.path,
|
|
1208
1383
|
path: clientPath,
|
|
1209
1384
|
isTypeOnly: true
|
|
@@ -1224,7 +1399,7 @@ const clientGenerator = (0, _kubb_core.defineGenerator)({
|
|
|
1224
1399
|
node,
|
|
1225
1400
|
tsResolver,
|
|
1226
1401
|
zodResolver,
|
|
1227
|
-
|
|
1402
|
+
validator,
|
|
1228
1403
|
security
|
|
1229
1404
|
})
|
|
1230
1405
|
]
|
|
@@ -1233,12 +1408,15 @@ const clientGenerator = (0, _kubb_core.defineGenerator)({
|
|
|
1233
1408
|
});
|
|
1234
1409
|
//#endregion
|
|
1235
1410
|
//#region src/templates.ts
|
|
1411
|
+
/** Absolute path to the fetch client template, copied into `.kubb/client.ts`. */
|
|
1412
|
+
const fetchClientTemplatePath = (0, node_url.fileURLToPath)(new URL("../templates/fetch.ts", require("url").pathToFileURL(__filename).href));
|
|
1413
|
+
/** Absolute path to the fetch serializers template, copied into `.kubb/serializers.ts`. */
|
|
1414
|
+
const fetchSerializersTemplatePath = (0, node_url.fileURLToPath)(new URL("../templates/serializers.ts", require("url").pathToFileURL(__filename).href));
|
|
1236
1415
|
/**
|
|
1237
|
-
* Absolute path to the
|
|
1238
|
-
*
|
|
1239
|
-
* field to emit the runtime into the generated `.kubb/client.ts` verbatim.
|
|
1416
|
+
* Absolute path to the Standard Schema runtime template. Pass it to a file node's `copy` field to
|
|
1417
|
+
* emit the helper into the generated `.kubb/standardSchema.ts` verbatim.
|
|
1240
1418
|
*/
|
|
1241
|
-
const
|
|
1419
|
+
const standardSchemaTemplatePath = (0, node_url.fileURLToPath)(new URL("../templates/standardSchema.ts", require("url").pathToFileURL(__filename).href));
|
|
1242
1420
|
//#endregion
|
|
1243
1421
|
//#region src/plugin.ts
|
|
1244
1422
|
/**
|
|
@@ -1272,7 +1450,7 @@ const pluginFetch = (0, _kubb_core.definePlugin)((options) => {
|
|
|
1272
1450
|
const { output = {
|
|
1273
1451
|
path: "clients",
|
|
1274
1452
|
barrel: { type: "named" }
|
|
1275
|
-
}, exclude = [], include, override = [], baseURL,
|
|
1453
|
+
}, exclude = [], include, override = [], baseURL, validator = false, group, sdk, resolver: userResolver } = options;
|
|
1276
1454
|
const resolved = {
|
|
1277
1455
|
output,
|
|
1278
1456
|
exclude,
|
|
@@ -1280,7 +1458,7 @@ const pluginFetch = (0, _kubb_core.definePlugin)((options) => {
|
|
|
1280
1458
|
override,
|
|
1281
1459
|
group: createGroupConfig(group),
|
|
1282
1460
|
baseURL,
|
|
1283
|
-
|
|
1461
|
+
validator,
|
|
1284
1462
|
sdk: sdk ? {
|
|
1285
1463
|
mode: sdk.mode ?? "tag",
|
|
1286
1464
|
name: sdk.name
|
|
@@ -1294,19 +1472,29 @@ const pluginFetch = (0, _kubb_core.definePlugin)((options) => {
|
|
|
1294
1472
|
return {
|
|
1295
1473
|
name: pluginFetchName,
|
|
1296
1474
|
options,
|
|
1297
|
-
dependencies: [_kubb_plugin_ts.pluginTsName,
|
|
1475
|
+
dependencies: [_kubb_plugin_ts.pluginTsName, isValidatorEnabled(resolved.validator) ? _kubb_plugin_zod.pluginZodName : null].filter((dependency) => Boolean(dependency)),
|
|
1298
1476
|
hooks: { "kubb:plugin:setup"(ctx) {
|
|
1299
1477
|
ctx.setOptions(resolved);
|
|
1300
1478
|
ctx.setResolver(resolved.resolver);
|
|
1301
1479
|
ctx.setMacros([...defaultMacros, ...options.macros ?? []]);
|
|
1302
1480
|
ctx.addGenerator(...selectedGenerators);
|
|
1303
1481
|
const root = node_path.default.resolve(ctx.config.root, ctx.config.output.path);
|
|
1482
|
+
ctx.injectFile({
|
|
1483
|
+
baseName: "serializers.ts",
|
|
1484
|
+
path: node_path.default.resolve(root, ".kubb/serializers.ts"),
|
|
1485
|
+
copy: fetchSerializersTemplatePath
|
|
1486
|
+
});
|
|
1304
1487
|
ctx.injectFile({
|
|
1305
1488
|
baseName: "client.ts",
|
|
1306
1489
|
path: node_path.default.resolve(root, ".kubb/client.ts"),
|
|
1307
1490
|
copy: fetchClientTemplatePath,
|
|
1308
1491
|
footer: baseURL ? `client.setConfig({ baseURL: ${JSON.stringify(baseURL)} })` : void 0
|
|
1309
1492
|
});
|
|
1493
|
+
ctx.injectFile({
|
|
1494
|
+
baseName: "standardSchema.ts",
|
|
1495
|
+
path: node_path.default.resolve(root, ".kubb/standardSchema.ts"),
|
|
1496
|
+
copy: standardSchemaTemplatePath
|
|
1497
|
+
});
|
|
1310
1498
|
} }
|
|
1311
1499
|
};
|
|
1312
1500
|
});
|