@dudousxd/nestjs-codegen 0.13.0 → 0.13.2

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,31 @@
1
1
  # @dudousxd/nestjs-codegen
2
2
 
3
+ ## 0.13.2
4
+
5
+ ### Patch Changes
6
+
7
+ - 889af1f: Resolve the members of an inline object-literal type (`{ a: Foo; b: Bar }`) in response/stream types instead of emitting the node's raw text. A named type nested in an object literal — most commonly an SSE payload's `Observable<{ data: SomeType }>`, where `SomeType` is imported from another package — was previously copied verbatim, leaving a bare, unimported identifier that is undefined in the generated file. Each member's type is now resolved (expanded inline, or reduced to `unknown` when unresolvable) like any other named reference.
8
+
9
+ ## 0.13.1
10
+
11
+ ### Patch Changes
12
+
13
+ - 6b51c7b: fix(multipart): intersect the uploaded-file field at emit time so it survives a named `bodyRef`, and leave deliberately-loose bodies untouched.
14
+
15
+ Two fixes to the multipart upload routes shipped in 0.13.0:
16
+
17
+ - **Named body refs now include the file field.** Discovery carries the uploaded-file
18
+ field(s) in a new `multipartBody` (kept off `body`), and the emitter intersects it onto
19
+ whichever body expression it picks — a named `bodyRef` (`BaseFileUploadDto`) or the inline
20
+ text. Previously the merge lived on the inline `body` string, so a route whose `@Body`
21
+ resolved to an imported DTO emitted the plain `BaseFileUploadDto` and dropped the file
22
+ field (`api.X({ body: { ...fields, file } })` failed to type-check).
23
+
24
+ - **Deliberately-loose bodies are left alone.** A `@Body() x: SomeDto | any` handler resolves
25
+ to a top-level `unknown`/`any` union arm; intersecting `(Dto | unknown) & { file }` collapses
26
+ it and wrongly tightens the type. The emitter now detects a permissive body and skips the
27
+ intersection, keeping the author's loose `@Body()` (the route is still flagged `multipart`).
28
+
3
29
  ## 0.13.0
4
30
 
5
31
  ### 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);
@@ -3579,6 +3606,19 @@ function resolveTypeNodeToString(typeNode, sourceFile, project, depth, subst = /
3579
3606
  dbg("unresolvable type:", name, "in", sourceFile.getFilePath());
3580
3607
  return "unknown";
3581
3608
  }
3609
+ if (import_ts_morph7.Node.isTypeLiteral(typeNode)) {
3610
+ const members = [];
3611
+ for (const member of typeNode.getMembers()) {
3612
+ if (import_ts_morph7.Node.isPropertySignature(member)) {
3613
+ const memberTypeNode = member.getTypeNode();
3614
+ const memberType = memberTypeNode ? resolveTypeNodeToString(memberTypeNode, sourceFile, project, depth, subst) : "unknown";
3615
+ members.push(`${member.getName()}${member.hasQuestionToken() ? "?" : ""}: ${memberType}`);
3616
+ } else {
3617
+ members.push(member.getText());
3618
+ }
3619
+ }
3620
+ return members.length > 0 ? `{ ${members.join("; ")} }` : "{}";
3621
+ }
3582
3622
  const kind = typeNode.getKind();
3583
3623
  if (kind === import_ts_morph7.SyntaxKind.StringKeyword) return "string";
3584
3624
  if (kind === import_ts_morph7.SyntaxKind.NumberKeyword) return "number";
@@ -3885,10 +3925,7 @@ function extractDtoContract(method, sourceFile, project) {
3885
3925
  const filterInfo = extractApplyFilterInfo(method, sourceFile, project);
3886
3926
  const query = extractQueryType(method, sourceFile, project);
3887
3927
  const uploads = extractUploadedFiles(method);
3888
- if (uploads.fields) {
3889
- const fileObject = `{ ${uploads.fields} }`;
3890
- body = body ? `(${body}) & ${fileObject}` : fileObject;
3891
- }
3928
+ const multipartBody = uploads.fields ? `{ ${uploads.fields} }` : null;
3892
3929
  const streamElement = detectStreamElement(method);
3893
3930
  const isStream = streamElement !== null;
3894
3931
  if (filterInfo && filterInfo.source === "body") {
@@ -3972,7 +4009,8 @@ function extractDtoContract(method, sourceFile, project) {
3972
4009
  bodySchema,
3973
4010
  querySchema,
3974
4011
  stream: isStream,
3975
- multipart: uploads.multipart
4012
+ multipart: uploads.multipart,
4013
+ multipartBody
3976
4014
  };
3977
4015
  }
3978
4016
  function resolveParamClass(method, decoratorName, sourceFile, project) {
@@ -4458,7 +4496,8 @@ function extractDtoRoute(args) {
4458
4496
  bodySchema: dtoContract?.bodySchema ?? null,
4459
4497
  querySchema: dtoContract?.querySchema ?? null,
4460
4498
  stream: dtoContract?.stream ?? false,
4461
- multipart: dtoContract?.multipart ?? false
4499
+ multipart: dtoContract?.multipart ?? false,
4500
+ multipartBody: dtoContract?.multipartBody ?? null
4462
4501
  }
4463
4502
  });
4464
4503
  }
@@ -4688,7 +4727,7 @@ async function watch(config, onChange, options = {}) {
4688
4727
  }
4689
4728
 
4690
4729
  // src/index.ts
4691
- var VERSION = "0.13.0";
4730
+ var VERSION = "0.13.2";
4692
4731
 
4693
4732
  // src/cli/codegen.ts
4694
4733
  async function runCodegen(opts = {}) {