@dudousxd/nestjs-codegen 0.13.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,25 @@
1
1
  # @dudousxd/nestjs-codegen
2
2
 
3
+ ## 0.13.1
4
+
5
+ ### Patch Changes
6
+
7
+ - 6b51c7b: fix(multipart): intersect the uploaded-file field at emit time so it survives a named `bodyRef`, and leave deliberately-loose bodies untouched.
8
+
9
+ Two fixes to the multipart upload routes shipped in 0.13.0:
10
+
11
+ - **Named body refs now include the file field.** Discovery carries the uploaded-file
12
+ field(s) in a new `multipartBody` (kept off `body`), and the emitter intersects it onto
13
+ whichever body expression it picks — a named `bodyRef` (`BaseFileUploadDto`) or the inline
14
+ text. Previously the merge lived on the inline `body` string, so a route whose `@Body`
15
+ resolved to an imported DTO emitted the plain `BaseFileUploadDto` and dropped the file
16
+ field (`api.X({ body: { ...fields, file } })` failed to type-check).
17
+
18
+ - **Deliberately-loose bodies are left alone.** A `@Body() x: SomeDto | any` handler resolves
19
+ to a top-level `unknown`/`any` union arm; intersecting `(Dto | unknown) & { file }` collapses
20
+ it and wrongly tightens the type. The emitter now detects a permissive body and skips the
21
+ intersection, keeping the author's loose `@Body()` (the route is still flagged `multipart`).
22
+
3
23
  ## 0.13.0
4
24
 
5
25
  ### Minor Changes
package/dist/cli/main.cjs CHANGED
@@ -785,7 +785,15 @@ function emitRouterTypeBlock(tree, indent, outDir, serialization) {
785
785
  const isFilterQuery = c.contractSource.filterSource === "query" && !!c.contractSource.filterFields?.length;
786
786
  const query = queryRef ? queryRef.isArray ? `Array<${queryRef.name}>` : queryRef.name : isFilterQuery ? emitFilterQueryType(c) : c.contractSource.query ?? "never";
787
787
  const bodyRef = c.contractSource.bodyRef;
788
- const body = method === "GET" ? "never" : bodyRef ? bodyRef.isArray ? `Array<${bodyRef.name}>` : bodyRef.name : c.contractSource.body ?? "never";
788
+ let body = method === "GET" ? "never" : bodyRef ? bodyRef.isArray ? `Array<${bodyRef.name}>` : bodyRef.name : c.contractSource.body ?? "never";
789
+ const multipartBody = c.contractSource.multipartBody;
790
+ if (c.contractSource.multipart && multipartBody) {
791
+ if (body === "never") {
792
+ body = multipartBody;
793
+ } else if (!bodyAcceptsAnything(body)) {
794
+ body = `(${body}) & ${multipartBody}`;
795
+ }
796
+ }
789
797
  const response = buildResponseType(c, outDir, serialization);
790
798
  const error = buildErrorType(c);
791
799
  const params = buildParamsType(c.params);
@@ -804,6 +812,25 @@ function emitRouterTypeBlock(tree, indent, outDir, serialization) {
804
812
  }
805
813
  return lines;
806
814
  }
815
+ function topLevelUnionArms(type) {
816
+ const arms = [];
817
+ let depth = 0;
818
+ let start = 0;
819
+ for (let i = 0; i < type.length; i++) {
820
+ const ch = type[i];
821
+ if (ch === "{" || ch === "[" || ch === "<" || ch === "(") depth++;
822
+ else if (ch === "}" || ch === "]" || ch === ">" || ch === ")") depth--;
823
+ else if (ch === "|" && depth === 0) {
824
+ arms.push(type.slice(start, i).trim());
825
+ start = i + 1;
826
+ }
827
+ }
828
+ arms.push(type.slice(start).trim());
829
+ return arms;
830
+ }
831
+ function bodyAcceptsAnything(body) {
832
+ return topLevelUnionArms(body).some((arm) => arm === "unknown" || arm === "any");
833
+ }
807
834
  function buildRequestModel(c) {
808
835
  const m = c.method.toLowerCase();
809
836
  const flat = JSON.stringify(c.name);
@@ -3885,10 +3912,7 @@ function extractDtoContract(method, sourceFile, project) {
3885
3912
  const filterInfo = extractApplyFilterInfo(method, sourceFile, project);
3886
3913
  const query = extractQueryType(method, sourceFile, project);
3887
3914
  const uploads = extractUploadedFiles(method);
3888
- if (uploads.fields) {
3889
- const fileObject = `{ ${uploads.fields} }`;
3890
- body = body ? `(${body}) & ${fileObject}` : fileObject;
3891
- }
3915
+ const multipartBody = uploads.fields ? `{ ${uploads.fields} }` : null;
3892
3916
  const streamElement = detectStreamElement(method);
3893
3917
  const isStream = streamElement !== null;
3894
3918
  if (filterInfo && filterInfo.source === "body") {
@@ -3972,7 +3996,8 @@ function extractDtoContract(method, sourceFile, project) {
3972
3996
  bodySchema,
3973
3997
  querySchema,
3974
3998
  stream: isStream,
3975
- multipart: uploads.multipart
3999
+ multipart: uploads.multipart,
4000
+ multipartBody
3976
4001
  };
3977
4002
  }
3978
4003
  function resolveParamClass(method, decoratorName, sourceFile, project) {
@@ -4458,7 +4483,8 @@ function extractDtoRoute(args) {
4458
4483
  bodySchema: dtoContract?.bodySchema ?? null,
4459
4484
  querySchema: dtoContract?.querySchema ?? null,
4460
4485
  stream: dtoContract?.stream ?? false,
4461
- multipart: dtoContract?.multipart ?? false
4486
+ multipart: dtoContract?.multipart ?? false,
4487
+ multipartBody: dtoContract?.multipartBody ?? null
4462
4488
  }
4463
4489
  });
4464
4490
  }
@@ -4688,7 +4714,7 @@ async function watch(config, onChange, options = {}) {
4688
4714
  }
4689
4715
 
4690
4716
  // src/index.ts
4691
- var VERSION = "0.13.0";
4717
+ var VERSION = "0.13.1";
4692
4718
 
4693
4719
  // src/cli/codegen.ts
4694
4720
  async function runCodegen(opts = {}) {