@dudousxd/nestjs-codegen 0.12.0 → 0.13.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.
@@ -1,2 +1,2 @@
1
- export { m as ApiClientLayer, n as ApiHeaderContribution, o as ApiModuleDeps, C as CodegenExtension, p as EmittedFile, E as ExtensionContext, L as LeafModel, q as RequestModel, s as RequestShape, t as defineExtension, u as requestShape } from '../index-CxkGbILp.cjs';
1
+ export { m as ApiClientLayer, n as ApiHeaderContribution, o as ApiModuleDeps, C as CodegenExtension, p as EmittedFile, E as ExtensionContext, L as LeafModel, q as RequestModel, s as RequestShape, t as defineExtension, u as requestShape } from '../index-D8RIMVpU.cjs';
2
2
  import 'ts-morph';
@@ -1,2 +1,2 @@
1
- export { m as ApiClientLayer, n as ApiHeaderContribution, o as ApiModuleDeps, C as CodegenExtension, p as EmittedFile, E as ExtensionContext, L as LeafModel, q as RequestModel, s as RequestShape, t as defineExtension, u as requestShape } from '../index-CxkGbILp.js';
1
+ export { m as ApiClientLayer, n as ApiHeaderContribution, o as ApiModuleDeps, C as CodegenExtension, p as EmittedFile, E as ExtensionContext, L as LeafModel, q as RequestModel, s as RequestShape, t as defineExtension, u as requestShape } from '../index-D8RIMVpU.js';
2
2
  import 'ts-morph';
@@ -489,6 +489,21 @@ interface ContractSource {
489
489
  * `AsyncIterable<T>` stream rather than a single awaited value.
490
490
  */
491
491
  stream?: boolean;
492
+ /**
493
+ * True when the route consumes `multipart/form-data` — its handler takes an
494
+ * `@UploadedFile()` / `@UploadedFiles()` (via a Multer interceptor). The
495
+ * uploaded-file field(s) are carried in `multipartBody` (typed `File | Blob`)
496
+ * and intersected onto `body` at emit time, and the generated client
497
+ * serializes the body to a `FormData` instead of JSON.
498
+ */
499
+ multipart?: boolean;
500
+ /**
501
+ * The uploaded-file field(s) for a multipart route, as an object-type string
502
+ * (e.g. `{ file: File | Blob }`). Kept separate from `body` so the emitter can
503
+ * intersect it onto whichever body representation it picks — a named `bodyRef`
504
+ * or the inline `body` text — preserving the import when there is one.
505
+ */
506
+ multipartBody?: string | null;
492
507
  }
493
508
  interface ContractDescriptor {
494
509
  contractSource: ContractSource;
@@ -489,6 +489,21 @@ interface ContractSource {
489
489
  * `AsyncIterable<T>` stream rather than a single awaited value.
490
490
  */
491
491
  stream?: boolean;
492
+ /**
493
+ * True when the route consumes `multipart/form-data` — its handler takes an
494
+ * `@UploadedFile()` / `@UploadedFiles()` (via a Multer interceptor). The
495
+ * uploaded-file field(s) are carried in `multipartBody` (typed `File | Blob`)
496
+ * and intersected onto `body` at emit time, and the generated client
497
+ * serializes the body to a `FormData` instead of JSON.
498
+ */
499
+ multipart?: boolean;
500
+ /**
501
+ * The uploaded-file field(s) for a multipart route, as an object-type string
502
+ * (e.g. `{ file: File | Blob }`). Kept separate from `body` so the emitter can
503
+ * intersect it onto whichever body representation it picks — a named `bodyRef`
504
+ * or the inline `body` text — preserving the import when there is one.
505
+ */
506
+ multipartBody?: string | null;
492
507
  }
493
508
  interface ContractDescriptor {
494
509
  contractSource: ContractSource;
package/dist/index.cjs CHANGED
@@ -816,7 +816,15 @@ function emitRouterTypeBlock(tree, indent, outDir, serialization) {
816
816
  const isFilterQuery = c.contractSource.filterSource === "query" && !!c.contractSource.filterFields?.length;
817
817
  const query = queryRef ? queryRef.isArray ? `Array<${queryRef.name}>` : queryRef.name : isFilterQuery ? emitFilterQueryType(c) : c.contractSource.query ?? "never";
818
818
  const bodyRef = c.contractSource.bodyRef;
819
- const body = method === "GET" ? "never" : bodyRef ? bodyRef.isArray ? `Array<${bodyRef.name}>` : bodyRef.name : c.contractSource.body ?? "never";
819
+ let body = method === "GET" ? "never" : bodyRef ? bodyRef.isArray ? `Array<${bodyRef.name}>` : bodyRef.name : c.contractSource.body ?? "never";
820
+ const multipartBody = c.contractSource.multipartBody;
821
+ if (c.contractSource.multipart && multipartBody) {
822
+ if (body === "never") {
823
+ body = multipartBody;
824
+ } else if (!bodyAcceptsAnything(body)) {
825
+ body = `(${body}) & ${multipartBody}`;
826
+ }
827
+ }
820
828
  const response = buildResponseType(c, outDir, serialization);
821
829
  const error = buildErrorType(c);
822
830
  const params = buildParamsType(c.params);
@@ -835,6 +843,25 @@ function emitRouterTypeBlock(tree, indent, outDir, serialization) {
835
843
  }
836
844
  return lines;
837
845
  }
846
+ function topLevelUnionArms(type) {
847
+ const arms = [];
848
+ let depth = 0;
849
+ let start = 0;
850
+ for (let i = 0; i < type.length; i++) {
851
+ const ch = type[i];
852
+ if (ch === "{" || ch === "[" || ch === "<" || ch === "(") depth++;
853
+ else if (ch === "}" || ch === "]" || ch === ">" || ch === ")") depth--;
854
+ else if (ch === "|" && depth === 0) {
855
+ arms.push(type.slice(start, i).trim());
856
+ start = i + 1;
857
+ }
858
+ }
859
+ arms.push(type.slice(start).trim());
860
+ return arms;
861
+ }
862
+ function bodyAcceptsAnything(body) {
863
+ return topLevelUnionArms(body).some((arm) => arm === "unknown" || arm === "any");
864
+ }
838
865
  function buildRequestModel(c) {
839
866
  const m = c.method.toLowerCase();
840
867
  const flat = JSON.stringify(c.name);
@@ -851,6 +878,7 @@ function buildRequestModel(c) {
851
878
  const optsParts = [];
852
879
  if (hasQuery) optsParts.push("query: input?.query as Record<string, unknown> | undefined");
853
880
  if (hasBody) optsParts.push("body: input?.body");
881
+ if (hasBody && c.contractSource.multipart) optsParts.push("multipart: true");
854
882
  const optsExpr = optsParts.length ? `{ ${optsParts.join(", ")} }` : "{}";
855
883
  return {
856
884
  routeName: c.name,
@@ -3734,6 +3762,55 @@ function extractParamsType(method, sourceFile, project) {
3734
3762
  }
3735
3763
  return entries.length > 0 ? `{ ${entries.join("; ")} }` : null;
3736
3764
  }
3765
+ function extractUploadedFiles(method) {
3766
+ const FILE = "File | Blob";
3767
+ const entries = [];
3768
+ let multipart = false;
3769
+ const hasUploadedFileParam = method.getParameters().some(
3770
+ (p) => p.getDecorators().some((d) => {
3771
+ const name = d.getName();
3772
+ return name === "UploadedFile" || name === "UploadedFiles";
3773
+ })
3774
+ );
3775
+ for (const decorator of method.getDecorators()) {
3776
+ if (decorator.getName() !== "UseInterceptors") continue;
3777
+ for (const arg of decorator.getArguments()) {
3778
+ if (!import_ts_morph7.Node.isCallExpression(arg)) continue;
3779
+ const interceptor = arg.getExpression().getText();
3780
+ const callArgs = arg.getArguments();
3781
+ const firstArg2 = callArgs[0];
3782
+ if (interceptor === "FileInterceptor") {
3783
+ if (firstArg2 && import_ts_morph7.Node.isStringLiteral(firstArg2)) {
3784
+ entries.push(`${firstArg2.getLiteralValue()}: ${FILE}`);
3785
+ multipart = true;
3786
+ }
3787
+ } else if (interceptor === "FilesInterceptor") {
3788
+ if (firstArg2 && import_ts_morph7.Node.isStringLiteral(firstArg2)) {
3789
+ entries.push(`${firstArg2.getLiteralValue()}: Array<${FILE}>`);
3790
+ multipart = true;
3791
+ }
3792
+ } else if (interceptor === "FileFieldsInterceptor") {
3793
+ if (firstArg2 && import_ts_morph7.Node.isArrayLiteralExpression(firstArg2)) {
3794
+ for (const el of firstArg2.getElements()) {
3795
+ if (!import_ts_morph7.Node.isObjectLiteralExpression(el)) continue;
3796
+ const nameProp = el.getProperty("name");
3797
+ if (nameProp && import_ts_morph7.Node.isPropertyAssignment(nameProp)) {
3798
+ const init = nameProp.getInitializer();
3799
+ if (init && import_ts_morph7.Node.isStringLiteral(init)) {
3800
+ entries.push(`${init.getLiteralValue()}: Array<${FILE}>`);
3801
+ }
3802
+ }
3803
+ }
3804
+ multipart = true;
3805
+ }
3806
+ } else if (interceptor === "AnyFilesInterceptor") {
3807
+ multipart = true;
3808
+ }
3809
+ }
3810
+ }
3811
+ if (hasUploadedFileParam) multipart = true;
3812
+ return { fields: entries.length > 0 ? entries.join("; ") : null, multipart };
3813
+ }
3737
3814
  function extractResponseType(method, sourceFile, project) {
3738
3815
  const apiResponseDecorator = method.getDecorators().find((d) => d.getName() === "ApiResponse" && (apiResponseStatus(d) ?? 0) < 400);
3739
3816
  if (apiResponseDecorator) {
@@ -3868,6 +3945,8 @@ function extractDtoContract(method, sourceFile, project) {
3868
3945
  let body = extractBodyType(method, sourceFile, project);
3869
3946
  const filterInfo = extractApplyFilterInfo(method, sourceFile, project);
3870
3947
  const query = extractQueryType(method, sourceFile, project);
3948
+ const uploads = extractUploadedFiles(method);
3949
+ const multipartBody = uploads.fields ? `{ ${uploads.fields} }` : null;
3871
3950
  const streamElement = detectStreamElement(method);
3872
3951
  const isStream = streamElement !== null;
3873
3952
  if (filterInfo && filterInfo.source === "body") {
@@ -3877,7 +3956,7 @@ function extractDtoContract(method, sourceFile, project) {
3877
3956
  const paramsType = extractParamsType(method, sourceFile, project);
3878
3957
  const response = isStream ? resolveTypeNodeToString(streamElement, sourceFile, project, 3) : extractResponseType(method, sourceFile, project);
3879
3958
  const errorInfo = extractErrorType(method, sourceFile, project);
3880
- if (body === null && query === null && paramsType === null && response === "unknown" && errorInfo === null && filterInfo === null && !isStream) {
3959
+ if (body === null && query === null && paramsType === null && response === "unknown" && errorInfo === null && filterInfo === null && !isStream && !uploads.multipart) {
3881
3960
  return null;
3882
3961
  }
3883
3962
  let bodyRef = null;
@@ -3950,7 +4029,9 @@ function extractDtoContract(method, sourceFile, project) {
3950
4029
  formWarnings,
3951
4030
  bodySchema,
3952
4031
  querySchema,
3953
- stream: isStream
4032
+ stream: isStream,
4033
+ multipart: uploads.multipart,
4034
+ multipartBody
3954
4035
  };
3955
4036
  }
3956
4037
  function resolveParamClass(method, decoratorName, sourceFile, project) {
@@ -4435,7 +4516,9 @@ function extractDtoRoute(args) {
4435
4516
  formWarnings: dtoContract?.formWarnings ?? [],
4436
4517
  bodySchema: dtoContract?.bodySchema ?? null,
4437
4518
  querySchema: dtoContract?.querySchema ?? null,
4438
- stream: dtoContract?.stream ?? false
4519
+ stream: dtoContract?.stream ?? false,
4520
+ multipart: dtoContract?.multipart ?? false,
4521
+ multipartBody: dtoContract?.multipartBody ?? null
4439
4522
  }
4440
4523
  });
4441
4524
  }
@@ -4748,7 +4831,7 @@ function createChainModuleRenderer(opts) {
4748
4831
  }
4749
4832
 
4750
4833
  // src/index.ts
4751
- var VERSION = "0.12.0";
4834
+ var VERSION = "0.13.1";
4752
4835
  // Annotate the CommonJS export names for ESM import in node:
4753
4836
  0 && (module.exports = {
4754
4837
  CodegenError,