@kubb/plugin-fetch 5.0.0-beta.76 → 5.0.0-beta.79
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 +242 -87
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +242 -87
- 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 +311 -170
- 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,55 @@ 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
|
+
}
|
|
393
459
|
function buildOperationComments(node, options = {}) {
|
|
394
460
|
const { link = "pathTemplate", linkPosition = "afterDeprecated", splitLines = false } = options;
|
|
395
461
|
const linkComment = getOperationLink(node, link);
|
|
@@ -410,10 +476,10 @@ function buildOperationComments(node, options = {}) {
|
|
|
410
476
|
function getOperationParameters(node, options = {}) {
|
|
411
477
|
const params = caseParams(node.parameters, options.paramsCasing === "original" ? void 0 : "camelcase");
|
|
412
478
|
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")
|
|
479
|
+
path: dedupeByCasedName(params.filter((param) => param.in === "path")),
|
|
480
|
+
query: dedupeByCasedName(params.filter((param) => param.in === "query")),
|
|
481
|
+
header: dedupeByCasedName(params.filter((param) => param.in === "header")),
|
|
482
|
+
cookie: dedupeByCasedName(params.filter((param) => param.in === "cookie"))
|
|
417
483
|
};
|
|
418
484
|
}
|
|
419
485
|
function getStatusCodeNumber(statusCode) {
|
|
@@ -424,6 +490,15 @@ function isSuccessStatusCode(statusCode) {
|
|
|
424
490
|
const code = getStatusCodeNumber(statusCode);
|
|
425
491
|
return code !== null && code >= 200 && code < 300;
|
|
426
492
|
}
|
|
493
|
+
function getSuccessResponses(responses) {
|
|
494
|
+
return responses.filter((response) => isSuccessStatusCode(response.statusCode));
|
|
495
|
+
}
|
|
496
|
+
function getOperationSuccessResponses(node) {
|
|
497
|
+
return getSuccessResponses(node.responses);
|
|
498
|
+
}
|
|
499
|
+
function getPrimarySuccessResponse(node) {
|
|
500
|
+
return getOperationSuccessResponses(node)[0] ?? null;
|
|
501
|
+
}
|
|
427
502
|
//#endregion
|
|
428
503
|
//#region ../../internals/shared/src/group.ts
|
|
429
504
|
/**
|
|
@@ -456,39 +531,39 @@ function createGroupConfig(group) {
|
|
|
456
531
|
};
|
|
457
532
|
}
|
|
458
533
|
//#endregion
|
|
459
|
-
//#region ../../internals/client/src/builders/
|
|
534
|
+
//#region ../../internals/client/src/builders/validatorOptions.ts
|
|
460
535
|
/**
|
|
461
|
-
* Returns `true` when any direction of the
|
|
536
|
+
* Returns `true` when any direction of the validator uses zod (used for dependency checks).
|
|
462
537
|
*/
|
|
463
|
-
function
|
|
464
|
-
if (!
|
|
465
|
-
if (
|
|
466
|
-
return Boolean(
|
|
538
|
+
function isValidatorEnabled(validator) {
|
|
539
|
+
if (!validator) return false;
|
|
540
|
+
if (validator === "zod") return true;
|
|
541
|
+
return Boolean(validator.request || validator.response);
|
|
467
542
|
}
|
|
468
543
|
/**
|
|
469
544
|
* Returns `'zod'` when request body parsing is enabled, `null` otherwise. The string shorthand
|
|
470
545
|
* `'zod'` validates the response only, so it does not enable request parsing.
|
|
471
546
|
*/
|
|
472
|
-
function
|
|
473
|
-
if (!
|
|
474
|
-
return
|
|
547
|
+
function resolveRequestValidator(validator) {
|
|
548
|
+
if (!validator || validator === "zod") return null;
|
|
549
|
+
return validator.request ?? null;
|
|
475
550
|
}
|
|
476
551
|
/**
|
|
477
552
|
* Returns `'zod'` when query-parameters parsing is enabled, `null` otherwise. Only the object form
|
|
478
553
|
* `{ request: 'zod' }` enables it.
|
|
479
554
|
*/
|
|
480
|
-
function
|
|
481
|
-
if (!
|
|
482
|
-
return
|
|
555
|
+
function resolveQueryParamsValidator(validator) {
|
|
556
|
+
if (!validator || validator === "zod") return null;
|
|
557
|
+
return validator.request ?? null;
|
|
483
558
|
}
|
|
484
559
|
/**
|
|
485
560
|
* Returns `'zod'` when response parsing is enabled, `null` otherwise. The string shorthand `'zod'`
|
|
486
561
|
* maps to response parsing.
|
|
487
562
|
*/
|
|
488
|
-
function
|
|
489
|
-
if (!
|
|
490
|
-
if (
|
|
491
|
-
return
|
|
563
|
+
function resolveResponseValidator(validator) {
|
|
564
|
+
if (!validator) return null;
|
|
565
|
+
if (validator === "zod") return "zod";
|
|
566
|
+
return validator.response ?? null;
|
|
492
567
|
}
|
|
493
568
|
/**
|
|
494
569
|
* Resolves the zod expression a generated client validates a success response with. Only success
|
|
@@ -642,23 +717,74 @@ function buildGroupedOptionsSignature({ node, tsResolver }) {
|
|
|
642
717
|
};
|
|
643
718
|
}
|
|
644
719
|
//#endregion
|
|
720
|
+
//#region ../../internals/client/src/builders/styles.ts
|
|
721
|
+
/**
|
|
722
|
+
* Renders a parameter name as an object-literal key, quoting it when the camelCased name is not a
|
|
723
|
+
* bare identifier (for example a name that starts with a digit) so the emitted literal stays valid.
|
|
724
|
+
*/
|
|
725
|
+
function toKey(name) {
|
|
726
|
+
const cased = camelCase(name);
|
|
727
|
+
return isValidVarName(cased) ? cased : JSON.stringify(cased);
|
|
728
|
+
}
|
|
729
|
+
/**
|
|
730
|
+
* Serializes one parameter's metadata into a `{ style, explode }` literal, or `null` when the
|
|
731
|
+
* parameter carries neither. Path and query carry the serialization `style`; header and cookie use a
|
|
732
|
+
* fixed style (`simple` and `form`), so only `explode` is emitted for them.
|
|
733
|
+
*/
|
|
734
|
+
function serializeParameter(parameter) {
|
|
735
|
+
const parts = [];
|
|
736
|
+
if ((parameter.in === "path" || parameter.in === "query") && parameter.style) parts.push(`style: '${parameter.style}'`);
|
|
737
|
+
if (parameter.explode !== void 0) parts.push(`explode: ${parameter.explode}`);
|
|
738
|
+
return parts.length > 0 ? `{ ${parts.join(", ")} }` : null;
|
|
739
|
+
}
|
|
740
|
+
/**
|
|
741
|
+
* Builds the per-operation `styles` literal from the operation's parameters, grouped by location and
|
|
742
|
+
* keyed by the camelCased parameter name to match the generated `path` / `query` / `headers` keys.
|
|
743
|
+
* Only parameters whose source defines `style` or `explode` are emitted, so calls without
|
|
744
|
+
* serialization metadata keep the runtime defaults and existing output is unchanged. Returns `null`
|
|
745
|
+
* when no parameter carries metadata.
|
|
746
|
+
*
|
|
747
|
+
* @example
|
|
748
|
+
* ```ts
|
|
749
|
+
* // a path param with { style: 'matrix', explode: true } and a query param with { explode: false }
|
|
750
|
+
* buildStyles({ node }) // "{ path: { id: { style: 'matrix', explode: true } }, query: { tags: { explode: false } } }"
|
|
751
|
+
* ```
|
|
752
|
+
*/
|
|
753
|
+
function buildStyles({ node }) {
|
|
754
|
+
if (!_kubb_core.ast.isHttpOperationNode(node)) return null;
|
|
755
|
+
const groups = {
|
|
756
|
+
path: [],
|
|
757
|
+
query: [],
|
|
758
|
+
header: [],
|
|
759
|
+
cookie: []
|
|
760
|
+
};
|
|
761
|
+
for (const parameter of node.parameters) {
|
|
762
|
+
const literal = serializeParameter(parameter);
|
|
763
|
+
if (!literal) continue;
|
|
764
|
+
groups[parameter.in].push(`${toKey(parameter.name)}: ${literal}`);
|
|
765
|
+
}
|
|
766
|
+
const locations = Object.keys(groups).filter((location) => groups[location].length > 0);
|
|
767
|
+
if (locations.length === 0) return null;
|
|
768
|
+
return `{ ${locations.map((location) => `${location}: { ${groups[location].join(", ")} }`).join(", ")} }`;
|
|
769
|
+
}
|
|
770
|
+
//#endregion
|
|
645
771
|
//#region ../../internals/client/src/builders/validator.ts
|
|
646
772
|
/**
|
|
647
|
-
* Builds the
|
|
648
|
-
* response
|
|
649
|
-
*
|
|
773
|
+
* Builds the validator-hook references for one operation. Request validation runs before the send;
|
|
774
|
+
* response validation runs on the success body only. Returns `null` references when the matching
|
|
775
|
+
* direction is disabled or the schema is absent.
|
|
650
776
|
*/
|
|
651
|
-
function
|
|
777
|
+
function buildValidatorHooks({ node, validator, zodResolver }) {
|
|
652
778
|
const importedZodNames = [];
|
|
653
779
|
const hasRequestBody = Boolean(node.requestBody?.content?.[0]?.schema);
|
|
654
|
-
const zodRequestName = zodResolver &&
|
|
655
|
-
const request = zodRequestName
|
|
780
|
+
const zodRequestName = zodResolver && resolveRequestValidator(validator) === "zod" && hasRequestBody ? zodResolver.resolveDataName?.(node) : null;
|
|
781
|
+
const request = zodRequestName ?? null;
|
|
656
782
|
if (zodRequestName) importedZodNames.push(zodRequestName);
|
|
657
|
-
const responseParse = zodResolver &&
|
|
658
|
-
const response = responseParse ?
|
|
783
|
+
const responseParse = zodResolver && resolveResponseValidator(validator) === "zod" ? buildZodResponseParse(node, zodResolver) : null;
|
|
784
|
+
const response = responseParse ? responseParse.expression : null;
|
|
659
785
|
if (responseParse) importedZodNames.push(...responseParse.importNames);
|
|
660
|
-
const errorParse = zodResolver &&
|
|
661
|
-
const error = errorParse ?
|
|
786
|
+
const errorParse = zodResolver && resolveResponseValidator(validator) === "zod" ? buildZodErrorParse(node, zodResolver) : null;
|
|
787
|
+
const error = errorParse ? errorParse.expression : null;
|
|
662
788
|
if (errorParse) importedZodNames.push(...errorParse.importNames);
|
|
663
789
|
return {
|
|
664
790
|
request,
|
|
@@ -674,34 +800,49 @@ function buildParserHooks({ node, parser, zodResolver }) {
|
|
|
674
800
|
* single `options` object to the resolved client and returns the `RequestResult`. The type, signature,
|
|
675
801
|
* and call config are built with the AST factory, and only the jsx-renderer emits the source.
|
|
676
802
|
*/
|
|
677
|
-
function Operation({ name, node, tsResolver, zodResolver,
|
|
803
|
+
function Operation({ name, node, tsResolver, zodResolver, validator, security, isExportable = true, isIndexable = true }) {
|
|
678
804
|
if (!_kubb_core.ast.isHttpOperationNode(node)) return null;
|
|
679
805
|
const signature = buildGroupedOptionsSignature({
|
|
680
806
|
node,
|
|
681
807
|
tsResolver
|
|
682
808
|
});
|
|
683
|
-
const
|
|
809
|
+
const validators = buildValidatorHooks({
|
|
684
810
|
node,
|
|
685
|
-
|
|
811
|
+
validator,
|
|
686
812
|
zodResolver
|
|
687
813
|
});
|
|
688
814
|
const securityLiteral = buildSecurityMetadata({ security });
|
|
815
|
+
const stylesLiteral = buildStyles({ node });
|
|
689
816
|
const { defaultContentType } = getContentTypeInfo(node);
|
|
690
|
-
const
|
|
691
|
-
const
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
817
|
+
const bakedRequestContentType = Boolean(node.requestBody?.content?.[0]?.schema) && defaultContentType !== "application/json" ? defaultContentType : null;
|
|
818
|
+
const mergeContentType = Boolean(bakedRequestContentType) && getResponseContentTypeInfo(node).isMultipleContentTypes;
|
|
819
|
+
const contentTypeLiteral = !bakedRequestContentType ? null : mergeContentType ? `contentType: { request: '${bakedRequestContentType}', ...(typeof contentType === 'string' ? { request: contentType } : contentType) }` : `contentType: { request: '${bakedRequestContentType}' }`;
|
|
820
|
+
const eventStream = isEventStream(node);
|
|
821
|
+
const responseType = getResponseType(node);
|
|
822
|
+
const responseTypeLiteral = responseType ? `responseType: '${responseType}'` : null;
|
|
823
|
+
const validatorEntries = [
|
|
824
|
+
validators.request ? `request: ${validators.request}` : null,
|
|
825
|
+
validators.response ? `response: ${validators.response}` : null,
|
|
826
|
+
validators.error ? `error: ${validators.error}` : null
|
|
695
827
|
].filter(Boolean);
|
|
696
|
-
const
|
|
828
|
+
const validatorLiteral = validatorEntries.length ? `validator: { ${validatorEntries.join(", ")} }` : null;
|
|
697
829
|
const callConfig = `{ ${[
|
|
698
830
|
`method: '${node.method.toUpperCase()}'`,
|
|
699
831
|
`url: '${Url.toCasedTemplate(node.path, { casing: "camelcase" })}'`,
|
|
700
832
|
securityLiteral ? `security: ${securityLiteral}` : null,
|
|
701
|
-
|
|
833
|
+
stylesLiteral ? `styles: ${stylesLiteral}` : null,
|
|
834
|
+
validatorLiteral,
|
|
702
835
|
contentTypeLiteral,
|
|
836
|
+
responseTypeLiteral,
|
|
703
837
|
"...config"
|
|
704
838
|
].filter(Boolean).join(", ")} }`;
|
|
839
|
+
const eventType = `SuccessOf<${tsResolver.resolveResponsesName(node)}>`;
|
|
840
|
+
const returnType = eventStream ? `Promise<EventStreamResult<${eventType}>>` : signature.returnType;
|
|
841
|
+
const returnStatement = eventStream ? `return toEventStream<${eventType}>(request(${callConfig}))` : buildReturnStatement({
|
|
842
|
+
node,
|
|
843
|
+
tsResolver,
|
|
844
|
+
callConfig
|
|
845
|
+
});
|
|
705
846
|
return /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(_kubb_renderer_jsx.File.Source, {
|
|
706
847
|
name,
|
|
707
848
|
isExportable,
|
|
@@ -711,20 +852,16 @@ function Operation({ name, node, tsResolver, zodResolver, parser, security, isEx
|
|
|
711
852
|
export: isExportable,
|
|
712
853
|
generics: signature.generics,
|
|
713
854
|
params: signature.paramsSignature,
|
|
714
|
-
returnType
|
|
855
|
+
returnType,
|
|
715
856
|
JSDoc: { comments: buildOperationComments(node, {
|
|
716
857
|
link: "urlPath",
|
|
717
858
|
linkPosition: "beforeDeprecated",
|
|
718
859
|
splitLines: true
|
|
719
860
|
}) },
|
|
720
861
|
children: [
|
|
721
|
-
"const { client: request = client, ...config } = options",
|
|
862
|
+
mergeContentType ? "const { client: request = client, contentType, ...config } = options" : "const { client: request = client, ...config } = options",
|
|
722
863
|
/* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)("br", {}),
|
|
723
|
-
|
|
724
|
-
node,
|
|
725
|
-
tsResolver,
|
|
726
|
-
callConfig
|
|
727
|
-
})
|
|
864
|
+
returnStatement
|
|
728
865
|
]
|
|
729
866
|
})
|
|
730
867
|
});
|
|
@@ -733,23 +870,23 @@ function Operation({ name, node, tsResolver, zodResolver, parser, security, isEx
|
|
|
733
870
|
//#region ../../internals/client/src/builders/sdkMethod.ts
|
|
734
871
|
/**
|
|
735
872
|
* Builds the call config literal forwarded to the contract client, mirroring the shared `Operation`
|
|
736
|
-
* component: `{ method, url, security?,
|
|
873
|
+
* component: `{ method, url, security?, validator?, ...config }`. The `...config` spread carries every
|
|
737
874
|
* per-call field (including `throwOnError`), so the method stays a thin wrapper over the contract.
|
|
738
875
|
*/
|
|
739
|
-
function buildCallConfig({ node,
|
|
740
|
-
const
|
|
876
|
+
function buildCallConfig({ node, validator, zodResolver, security }) {
|
|
877
|
+
const validators = buildValidatorHooks({
|
|
741
878
|
node,
|
|
742
|
-
|
|
879
|
+
validator,
|
|
743
880
|
zodResolver
|
|
744
881
|
});
|
|
745
|
-
const
|
|
746
|
-
const
|
|
882
|
+
const validatorEntries = [validators.request ? `request: ${validators.request}` : null, validators.response ? `response: ${validators.response}` : null].filter(Boolean);
|
|
883
|
+
const validatorLiteral = validatorEntries.length ? `validator: { ${validatorEntries.join(", ")} }` : null;
|
|
747
884
|
const securityLiteral = buildSecurityMetadata({ security });
|
|
748
885
|
return `{ ${[
|
|
749
886
|
`method: '${node.method.toUpperCase()}'`,
|
|
750
887
|
`url: '${Url.toCasedTemplate(node.path, { casing: "camelcase" })}'`,
|
|
751
888
|
securityLiteral ? `security: ${securityLiteral}` : null,
|
|
752
|
-
|
|
889
|
+
validatorLiteral,
|
|
753
890
|
"...config"
|
|
754
891
|
].filter(Boolean).join(", ")} }`;
|
|
755
892
|
}
|
|
@@ -759,7 +896,7 @@ function buildCallConfig({ node, parser, zodResolver, security }) {
|
|
|
759
896
|
* returns the `RequestResult`. A per-call `options.client` still overrides the instance client, so
|
|
760
897
|
* one operation can be routed to a different environment without a new instance.
|
|
761
898
|
*/
|
|
762
|
-
function buildSdkMethod({ node, name, tsResolver, zodResolver,
|
|
899
|
+
function buildSdkMethod({ node, name, tsResolver, zodResolver, validator, security }) {
|
|
763
900
|
if (!_kubb_core.ast.isHttpOperationNode(node)) return "";
|
|
764
901
|
const signature = buildGroupedOptionsSignature({
|
|
765
902
|
node,
|
|
@@ -770,7 +907,7 @@ function buildSdkMethod({ node, name, tsResolver, zodResolver, parser, security
|
|
|
770
907
|
tsResolver,
|
|
771
908
|
callConfig: buildCallConfig({
|
|
772
909
|
node,
|
|
773
|
-
|
|
910
|
+
validator,
|
|
774
911
|
zodResolver,
|
|
775
912
|
security
|
|
776
913
|
})
|
|
@@ -796,13 +933,13 @@ function buildSdkMethod({ node, name, tsResolver, zodResolver, parser, security
|
|
|
796
933
|
* instance: `const api = new PetClient({ baseURL }); api.getPetById(...)`. A per-call `client` option
|
|
797
934
|
* still overrides the instance client for a one-off call.
|
|
798
935
|
*/
|
|
799
|
-
function SdkClient({ name, isExportable = true, isIndexable = true, operations,
|
|
936
|
+
function SdkClient({ name, isExportable = true, isIndexable = true, operations, validator, children }) {
|
|
800
937
|
const methods = operations.map(({ node, name: methodName, tsResolver, zodResolver, security }) => buildSdkMethod({
|
|
801
938
|
node,
|
|
802
939
|
name: methodName,
|
|
803
940
|
tsResolver,
|
|
804
941
|
zodResolver,
|
|
805
|
-
|
|
942
|
+
validator,
|
|
806
943
|
security
|
|
807
944
|
}));
|
|
808
945
|
const classCode = `export class ${name} {\n${[
|
|
@@ -848,13 +985,13 @@ function SdkFacade({ name, isExportable = true, isIndexable = true, members, chi
|
|
|
848
985
|
function resolveTypeImportNames(node, tsResolver) {
|
|
849
986
|
return [tsResolver.resolveRequestConfigName(node), tsResolver.resolveResponsesName(node)];
|
|
850
987
|
}
|
|
851
|
-
function resolveZodImportNames(node, zodResolver,
|
|
988
|
+
function resolveZodImportNames(node, zodResolver, validator) {
|
|
852
989
|
const { query: queryParams } = getOperationParameters(node, { paramsCasing: "original" });
|
|
853
990
|
return [
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
991
|
+
resolveResponseValidator(validator) === "zod" ? zodResolver.resolveResponseName?.(node) : null,
|
|
992
|
+
resolveResponseValidator(validator) === "zod" ? buildZodErrorParse(node, zodResolver)?.expression ?? null : null,
|
|
993
|
+
resolveRequestValidator(validator) === "zod" && node.requestBody?.content?.[0]?.schema ? zodResolver.resolveDataName?.(node) : null,
|
|
994
|
+
resolveQueryParamsValidator(validator) === "zod" && queryParams.length > 0 ? zodResolver.resolveQueryParamsName?.(node, queryParams[0]) : null
|
|
858
995
|
].filter((n) => Boolean(n));
|
|
859
996
|
}
|
|
860
997
|
/**
|
|
@@ -863,11 +1000,11 @@ function resolveZodImportNames(node, zodResolver, parser) {
|
|
|
863
1000
|
*/
|
|
864
1001
|
function buildControllers(nodes, ctx) {
|
|
865
1002
|
const { driver, resolver, root } = ctx;
|
|
866
|
-
const { output, group,
|
|
1003
|
+
const { output, group, validator } = ctx.options;
|
|
867
1004
|
const pluginTs = driver.getPlugin(_kubb_plugin_ts.pluginTsName);
|
|
868
1005
|
const tsResolver = driver.getResolver(_kubb_plugin_ts.pluginTsName);
|
|
869
1006
|
const tsPluginOptions = pluginTs.options;
|
|
870
|
-
const pluginZod =
|
|
1007
|
+
const pluginZod = isValidatorEnabled(validator) ? driver.getPlugin(_kubb_plugin_zod.pluginZodName) : null;
|
|
871
1008
|
const zodResolver = pluginZod ? driver.getResolver(_kubb_plugin_zod.pluginZodName) : null;
|
|
872
1009
|
const document = ctx.adapter.document;
|
|
873
1010
|
function buildOperationData(node) {
|
|
@@ -954,7 +1091,7 @@ function createSdkGenerator() {
|
|
|
954
1091
|
renderer: _kubb_renderer_jsx.jsxRenderer,
|
|
955
1092
|
operations(nodes, ctx) {
|
|
956
1093
|
const { config, resolver, root } = ctx;
|
|
957
|
-
const { output, group,
|
|
1094
|
+
const { output, group, validator, sdk } = ctx.options;
|
|
958
1095
|
if (!ctx.driver.getPlugin(_kubb_plugin_ts.pluginTsName) || !sdk) return null;
|
|
959
1096
|
const controllers = buildControllers(nodes, ctx);
|
|
960
1097
|
const clientPath = node_path.default.resolve(root, ".kubb/client.ts");
|
|
@@ -979,9 +1116,9 @@ function createSdkGenerator() {
|
|
|
979
1116
|
file: op.typeFile,
|
|
980
1117
|
names: resolveTypeImportNames(op.node, op.tsResolver)
|
|
981
1118
|
}));
|
|
982
|
-
const { namesByPath: zodNamesByPath, filesByPath: zodFilesByPath } =
|
|
1119
|
+
const { namesByPath: zodNamesByPath, filesByPath: zodFilesByPath } = isValidatorEnabled(validator) ? collectImportsByFile(ops, (op) => ({
|
|
983
1120
|
file: op.zodFile,
|
|
984
|
-
names: op.zodResolver ? resolveZodImportNames(op.node, op.zodResolver,
|
|
1121
|
+
names: op.zodResolver ? resolveZodImportNames(op.node, op.zodResolver, validator) : []
|
|
985
1122
|
})) : {
|
|
986
1123
|
namesByPath: /* @__PURE__ */ new Map(),
|
|
987
1124
|
filesByPath: /* @__PURE__ */ new Map()
|
|
@@ -1009,7 +1146,7 @@ function createSdkGenerator() {
|
|
|
1009
1146
|
path: clientPath,
|
|
1010
1147
|
isTypeOnly: true
|
|
1011
1148
|
}),
|
|
1012
|
-
|
|
1149
|
+
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
1150
|
name: ["z"],
|
|
1014
1151
|
path: "zod",
|
|
1015
1152
|
isTypeOnly: true
|
|
@@ -1020,7 +1157,7 @@ function createSdkGenerator() {
|
|
|
1020
1157
|
path: typeFilesByPath.get(filePath).path,
|
|
1021
1158
|
isTypeOnly: true
|
|
1022
1159
|
}, filePath)),
|
|
1023
|
-
|
|
1160
|
+
isValidatorEnabled(validator) && Array.from(zodNamesByPath.entries()).map(([filePath, set]) => /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(_kubb_renderer_jsx.File.Import, {
|
|
1024
1161
|
name: Array.from(set),
|
|
1025
1162
|
root: file.path,
|
|
1026
1163
|
path: zodFilesByPath.get(filePath).path
|
|
@@ -1028,7 +1165,7 @@ function createSdkGenerator() {
|
|
|
1028
1165
|
/* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(SdkClient, {
|
|
1029
1166
|
name: className,
|
|
1030
1167
|
operations: ops,
|
|
1031
|
-
|
|
1168
|
+
validator
|
|
1032
1169
|
})
|
|
1033
1170
|
]
|
|
1034
1171
|
}, file.path);
|
|
@@ -1139,18 +1276,18 @@ const clientGenerator = (0, _kubb_core.defineGenerator)({
|
|
|
1139
1276
|
operation(node, ctx) {
|
|
1140
1277
|
if (!_kubb_core.ast.isHttpOperationNode(node)) return null;
|
|
1141
1278
|
const { config, driver, resolver, root } = ctx;
|
|
1142
|
-
const { output,
|
|
1279
|
+
const { output, validator, group } = ctx.options;
|
|
1143
1280
|
const pluginTs = driver.getPlugin(_kubb_plugin_ts.pluginTsName);
|
|
1144
1281
|
if (!pluginTs) return null;
|
|
1145
1282
|
const tsResolver = driver.getResolver(_kubb_plugin_ts.pluginTsName);
|
|
1146
|
-
const pluginZod =
|
|
1283
|
+
const pluginZod = resolveResponseValidator(validator) === "zod" || resolveRequestValidator(validator) === "zod" ? driver.getPlugin(_kubb_plugin_zod.pluginZodName) : null;
|
|
1147
1284
|
const zodResolver = pluginZod ? driver.getResolver(_kubb_plugin_zod.pluginZodName) : null;
|
|
1148
1285
|
const hasRequestBody = Boolean(node.requestBody?.content?.[0]?.schema);
|
|
1149
1286
|
const importedTypeNames = [tsResolver.resolveRequestConfigName(node), tsResolver.resolveResponsesName(node)];
|
|
1150
1287
|
const importedZodNames = zodResolver ? [
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1288
|
+
resolveResponseValidator(validator) === "zod" ? zodResolver.resolveResponseName?.(node) : null,
|
|
1289
|
+
resolveResponseValidator(validator) === "zod" ? buildZodErrorParse(node, zodResolver)?.expression ?? null : null,
|
|
1290
|
+
resolveRequestValidator(validator) === "zod" && hasRequestBody ? zodResolver.resolveDataName?.(node) : null
|
|
1154
1291
|
].filter((name) => Boolean(name)) : [];
|
|
1155
1292
|
const meta = {
|
|
1156
1293
|
name: resolver.resolveName(node.operationId),
|
|
@@ -1176,6 +1313,7 @@ const clientGenerator = (0, _kubb_core.defineGenerator)({
|
|
|
1176
1313
|
path: node.path
|
|
1177
1314
|
});
|
|
1178
1315
|
const clientPath = node_path.default.resolve(root, ".kubb/client.ts");
|
|
1316
|
+
const eventStream = isEventStream(node);
|
|
1179
1317
|
return /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsxs)(_kubb_renderer_jsx.File, {
|
|
1180
1318
|
baseName: meta.file.baseName,
|
|
1181
1319
|
path: meta.file.path,
|
|
@@ -1198,12 +1336,16 @@ const clientGenerator = (0, _kubb_core.defineGenerator)({
|
|
|
1198
1336
|
}),
|
|
1199
1337
|
children: [
|
|
1200
1338
|
/* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(_kubb_renderer_jsx.File.Import, {
|
|
1201
|
-
name: ["client"],
|
|
1339
|
+
name: eventStream ? ["client", "toEventStream"] : ["client"],
|
|
1202
1340
|
root: meta.file.path,
|
|
1203
1341
|
path: clientPath
|
|
1204
1342
|
}),
|
|
1205
1343
|
/* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(_kubb_renderer_jsx.File.Import, {
|
|
1206
|
-
name: [
|
|
1344
|
+
name: eventStream ? [
|
|
1345
|
+
"Options",
|
|
1346
|
+
"EventStreamResult",
|
|
1347
|
+
"SuccessOf"
|
|
1348
|
+
] : ["Options", "RequestResult"],
|
|
1207
1349
|
root: meta.file.path,
|
|
1208
1350
|
path: clientPath,
|
|
1209
1351
|
isTypeOnly: true
|
|
@@ -1224,7 +1366,7 @@ const clientGenerator = (0, _kubb_core.defineGenerator)({
|
|
|
1224
1366
|
node,
|
|
1225
1367
|
tsResolver,
|
|
1226
1368
|
zodResolver,
|
|
1227
|
-
|
|
1369
|
+
validator,
|
|
1228
1370
|
security
|
|
1229
1371
|
})
|
|
1230
1372
|
]
|
|
@@ -1233,12 +1375,15 @@ const clientGenerator = (0, _kubb_core.defineGenerator)({
|
|
|
1233
1375
|
});
|
|
1234
1376
|
//#endregion
|
|
1235
1377
|
//#region src/templates.ts
|
|
1378
|
+
/** Absolute path to the fetch client template, copied into `.kubb/client.ts`. */
|
|
1379
|
+
const fetchClientTemplatePath = (0, node_url.fileURLToPath)(new URL("../templates/fetch.ts", require("url").pathToFileURL(__filename).href));
|
|
1380
|
+
/** Absolute path to the fetch serializers template, copied into `.kubb/serializers.ts`. */
|
|
1381
|
+
const fetchSerializersTemplatePath = (0, node_url.fileURLToPath)(new URL("../templates/serializers.ts", require("url").pathToFileURL(__filename).href));
|
|
1236
1382
|
/**
|
|
1237
|
-
* Absolute path to the
|
|
1238
|
-
*
|
|
1239
|
-
* field to emit the runtime into the generated `.kubb/client.ts` verbatim.
|
|
1383
|
+
* Absolute path to the Standard Schema runtime template. Pass it to a file node's `copy` field to
|
|
1384
|
+
* emit the helper into the generated `.kubb/standardSchema.ts` verbatim.
|
|
1240
1385
|
*/
|
|
1241
|
-
const
|
|
1386
|
+
const standardSchemaTemplatePath = (0, node_url.fileURLToPath)(new URL("../templates/standardSchema.ts", require("url").pathToFileURL(__filename).href));
|
|
1242
1387
|
//#endregion
|
|
1243
1388
|
//#region src/plugin.ts
|
|
1244
1389
|
/**
|
|
@@ -1272,7 +1417,7 @@ const pluginFetch = (0, _kubb_core.definePlugin)((options) => {
|
|
|
1272
1417
|
const { output = {
|
|
1273
1418
|
path: "clients",
|
|
1274
1419
|
barrel: { type: "named" }
|
|
1275
|
-
}, exclude = [], include, override = [], baseURL,
|
|
1420
|
+
}, exclude = [], include, override = [], baseURL, validator = false, group, sdk, resolver: userResolver } = options;
|
|
1276
1421
|
const resolved = {
|
|
1277
1422
|
output,
|
|
1278
1423
|
exclude,
|
|
@@ -1280,7 +1425,7 @@ const pluginFetch = (0, _kubb_core.definePlugin)((options) => {
|
|
|
1280
1425
|
override,
|
|
1281
1426
|
group: createGroupConfig(group),
|
|
1282
1427
|
baseURL,
|
|
1283
|
-
|
|
1428
|
+
validator,
|
|
1284
1429
|
sdk: sdk ? {
|
|
1285
1430
|
mode: sdk.mode ?? "tag",
|
|
1286
1431
|
name: sdk.name
|
|
@@ -1294,19 +1439,29 @@ const pluginFetch = (0, _kubb_core.definePlugin)((options) => {
|
|
|
1294
1439
|
return {
|
|
1295
1440
|
name: pluginFetchName,
|
|
1296
1441
|
options,
|
|
1297
|
-
dependencies: [_kubb_plugin_ts.pluginTsName,
|
|
1442
|
+
dependencies: [_kubb_plugin_ts.pluginTsName, isValidatorEnabled(resolved.validator) ? _kubb_plugin_zod.pluginZodName : null].filter((dependency) => Boolean(dependency)),
|
|
1298
1443
|
hooks: { "kubb:plugin:setup"(ctx) {
|
|
1299
1444
|
ctx.setOptions(resolved);
|
|
1300
1445
|
ctx.setResolver(resolved.resolver);
|
|
1301
1446
|
ctx.setMacros([...defaultMacros, ...options.macros ?? []]);
|
|
1302
1447
|
ctx.addGenerator(...selectedGenerators);
|
|
1303
1448
|
const root = node_path.default.resolve(ctx.config.root, ctx.config.output.path);
|
|
1449
|
+
ctx.injectFile({
|
|
1450
|
+
baseName: "serializers.ts",
|
|
1451
|
+
path: node_path.default.resolve(root, ".kubb/serializers.ts"),
|
|
1452
|
+
copy: fetchSerializersTemplatePath
|
|
1453
|
+
});
|
|
1304
1454
|
ctx.injectFile({
|
|
1305
1455
|
baseName: "client.ts",
|
|
1306
1456
|
path: node_path.default.resolve(root, ".kubb/client.ts"),
|
|
1307
1457
|
copy: fetchClientTemplatePath,
|
|
1308
1458
|
footer: baseURL ? `client.setConfig({ baseURL: ${JSON.stringify(baseURL)} })` : void 0
|
|
1309
1459
|
});
|
|
1460
|
+
ctx.injectFile({
|
|
1461
|
+
baseName: "standardSchema.ts",
|
|
1462
|
+
path: node_path.default.resolve(root, ".kubb/standardSchema.ts"),
|
|
1463
|
+
copy: standardSchemaTemplatePath
|
|
1464
|
+
});
|
|
1310
1465
|
} }
|
|
1311
1466
|
};
|
|
1312
1467
|
});
|