@dudousxd/nestjs-codegen 0.12.0 → 0.13.0
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 +31 -0
- package/dist/cli/main.cjs +61 -4
- package/dist/cli/main.cjs.map +1 -1
- package/dist/cli/main.js +61 -4
- package/dist/cli/main.js.map +1 -1
- package/dist/extension/index.d.cts +1 -1
- package/dist/extension/index.d.ts +1 -1
- package/dist/{index-CxkGbILp.d.cts → index-DvUzPXdh.d.cts} +7 -0
- package/dist/{index-CxkGbILp.d.ts → index-DvUzPXdh.d.ts} +7 -0
- package/dist/index.cjs +61 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +61 -4
- package/dist/index.js.map +1 -1
- package/dist/nest/index.cjs +61 -4
- package/dist/nest/index.cjs.map +1 -1
- package/dist/nest/index.d.cts +1 -1
- package/dist/nest/index.d.ts +1 -1
- package/dist/nest/index.js +61 -4
- package/dist/nest/index.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,36 @@
|
|
|
1
1
|
# @dudousxd/nestjs-codegen
|
|
2
2
|
|
|
3
|
+
## 0.13.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- a044e73: feat: typed `multipart/form-data` upload routes (`@UploadedFile()` / Multer interceptors).
|
|
8
|
+
|
|
9
|
+
The codegen now understands handlers that accept uploaded files, so multipart uploads
|
|
10
|
+
become first-class typed routes (`api.X({ body: { ...fields, file } })`) instead of
|
|
11
|
+
needing the `fetchRaw` escape hatch.
|
|
12
|
+
|
|
13
|
+
**core (`@dudousxd/nestjs-codegen`):**
|
|
14
|
+
|
|
15
|
+
- Discovery detects `@UploadedFile()` / `@UploadedFiles()` handlers and reads the HTTP
|
|
16
|
+
field name(s) + arity from the Multer interceptor in `@UseInterceptors(...)`:
|
|
17
|
+
- `FileInterceptor('file')` → `file: File | Blob`
|
|
18
|
+
- `FilesInterceptor('files')` → `files: Array<File | Blob>`
|
|
19
|
+
- `FileFieldsInterceptor([{ name: 'a' }, { name: 'b' }])` → `a: Array<File | Blob>; b: Array<File | Blob>`
|
|
20
|
+
- `AnyFilesInterceptor()` → flagged multipart (no statically known field names)
|
|
21
|
+
- The uploaded-file field(s) are merged into the route `body` as an intersection with the
|
|
22
|
+
`@Body` DTO (`SomeDto & { file: File | Blob }`), typed for the browser as `File | Blob`
|
|
23
|
+
(never the server-side `Express.Multer.File`).
|
|
24
|
+
- The route carries a new `multipart` flag, emitted into the generated client so the call
|
|
25
|
+
passes `multipart: true` to the fetcher.
|
|
26
|
+
|
|
27
|
+
**client (`@dudousxd/nestjs-client`):**
|
|
28
|
+
|
|
29
|
+
- `RequestOpts` gains `multipart?: boolean`. When set, the fetcher serializes the body
|
|
30
|
+
object to a `FormData` (scalars as strings, `Date` as ISO, `File`/`Blob` as file parts,
|
|
31
|
+
arrays as repeated parts) instead of JSON, letting the runtime set the multipart
|
|
32
|
+
boundary. `onUploadProgress` already rides the same path.
|
|
33
|
+
|
|
3
34
|
## 0.12.0
|
|
4
35
|
|
|
5
36
|
### Minor Changes
|
package/dist/cli/main.cjs
CHANGED
|
@@ -820,6 +820,7 @@ function buildRequestModel(c) {
|
|
|
820
820
|
const optsParts = [];
|
|
821
821
|
if (hasQuery) optsParts.push("query: input?.query as Record<string, unknown> | undefined");
|
|
822
822
|
if (hasBody) optsParts.push("body: input?.body");
|
|
823
|
+
if (hasBody && c.contractSource.multipart) optsParts.push("multipart: true");
|
|
823
824
|
const optsExpr = optsParts.length ? `{ ${optsParts.join(", ")} }` : "{}";
|
|
824
825
|
return {
|
|
825
826
|
routeName: c.name,
|
|
@@ -3700,6 +3701,55 @@ function extractParamsType(method, sourceFile, project) {
|
|
|
3700
3701
|
}
|
|
3701
3702
|
return entries.length > 0 ? `{ ${entries.join("; ")} }` : null;
|
|
3702
3703
|
}
|
|
3704
|
+
function extractUploadedFiles(method) {
|
|
3705
|
+
const FILE = "File | Blob";
|
|
3706
|
+
const entries = [];
|
|
3707
|
+
let multipart = false;
|
|
3708
|
+
const hasUploadedFileParam = method.getParameters().some(
|
|
3709
|
+
(p) => p.getDecorators().some((d) => {
|
|
3710
|
+
const name = d.getName();
|
|
3711
|
+
return name === "UploadedFile" || name === "UploadedFiles";
|
|
3712
|
+
})
|
|
3713
|
+
);
|
|
3714
|
+
for (const decorator of method.getDecorators()) {
|
|
3715
|
+
if (decorator.getName() !== "UseInterceptors") continue;
|
|
3716
|
+
for (const arg of decorator.getArguments()) {
|
|
3717
|
+
if (!import_ts_morph7.Node.isCallExpression(arg)) continue;
|
|
3718
|
+
const interceptor = arg.getExpression().getText();
|
|
3719
|
+
const callArgs = arg.getArguments();
|
|
3720
|
+
const firstArg2 = callArgs[0];
|
|
3721
|
+
if (interceptor === "FileInterceptor") {
|
|
3722
|
+
if (firstArg2 && import_ts_morph7.Node.isStringLiteral(firstArg2)) {
|
|
3723
|
+
entries.push(`${firstArg2.getLiteralValue()}: ${FILE}`);
|
|
3724
|
+
multipart = true;
|
|
3725
|
+
}
|
|
3726
|
+
} else if (interceptor === "FilesInterceptor") {
|
|
3727
|
+
if (firstArg2 && import_ts_morph7.Node.isStringLiteral(firstArg2)) {
|
|
3728
|
+
entries.push(`${firstArg2.getLiteralValue()}: Array<${FILE}>`);
|
|
3729
|
+
multipart = true;
|
|
3730
|
+
}
|
|
3731
|
+
} else if (interceptor === "FileFieldsInterceptor") {
|
|
3732
|
+
if (firstArg2 && import_ts_morph7.Node.isArrayLiteralExpression(firstArg2)) {
|
|
3733
|
+
for (const el of firstArg2.getElements()) {
|
|
3734
|
+
if (!import_ts_morph7.Node.isObjectLiteralExpression(el)) continue;
|
|
3735
|
+
const nameProp = el.getProperty("name");
|
|
3736
|
+
if (nameProp && import_ts_morph7.Node.isPropertyAssignment(nameProp)) {
|
|
3737
|
+
const init = nameProp.getInitializer();
|
|
3738
|
+
if (init && import_ts_morph7.Node.isStringLiteral(init)) {
|
|
3739
|
+
entries.push(`${init.getLiteralValue()}: Array<${FILE}>`);
|
|
3740
|
+
}
|
|
3741
|
+
}
|
|
3742
|
+
}
|
|
3743
|
+
multipart = true;
|
|
3744
|
+
}
|
|
3745
|
+
} else if (interceptor === "AnyFilesInterceptor") {
|
|
3746
|
+
multipart = true;
|
|
3747
|
+
}
|
|
3748
|
+
}
|
|
3749
|
+
}
|
|
3750
|
+
if (hasUploadedFileParam) multipart = true;
|
|
3751
|
+
return { fields: entries.length > 0 ? entries.join("; ") : null, multipart };
|
|
3752
|
+
}
|
|
3703
3753
|
function extractResponseType(method, sourceFile, project) {
|
|
3704
3754
|
const apiResponseDecorator = method.getDecorators().find((d) => d.getName() === "ApiResponse" && (apiResponseStatus(d) ?? 0) < 400);
|
|
3705
3755
|
if (apiResponseDecorator) {
|
|
@@ -3834,6 +3884,11 @@ function extractDtoContract(method, sourceFile, project) {
|
|
|
3834
3884
|
let body = extractBodyType(method, sourceFile, project);
|
|
3835
3885
|
const filterInfo = extractApplyFilterInfo(method, sourceFile, project);
|
|
3836
3886
|
const query = extractQueryType(method, sourceFile, project);
|
|
3887
|
+
const uploads = extractUploadedFiles(method);
|
|
3888
|
+
if (uploads.fields) {
|
|
3889
|
+
const fileObject = `{ ${uploads.fields} }`;
|
|
3890
|
+
body = body ? `(${body}) & ${fileObject}` : fileObject;
|
|
3891
|
+
}
|
|
3837
3892
|
const streamElement = detectStreamElement(method);
|
|
3838
3893
|
const isStream = streamElement !== null;
|
|
3839
3894
|
if (filterInfo && filterInfo.source === "body") {
|
|
@@ -3843,7 +3898,7 @@ function extractDtoContract(method, sourceFile, project) {
|
|
|
3843
3898
|
const paramsType = extractParamsType(method, sourceFile, project);
|
|
3844
3899
|
const response = isStream ? resolveTypeNodeToString(streamElement, sourceFile, project, 3) : extractResponseType(method, sourceFile, project);
|
|
3845
3900
|
const errorInfo = extractErrorType(method, sourceFile, project);
|
|
3846
|
-
if (body === null && query === null && paramsType === null && response === "unknown" && errorInfo === null && filterInfo === null && !isStream) {
|
|
3901
|
+
if (body === null && query === null && paramsType === null && response === "unknown" && errorInfo === null && filterInfo === null && !isStream && !uploads.multipart) {
|
|
3847
3902
|
return null;
|
|
3848
3903
|
}
|
|
3849
3904
|
let bodyRef = null;
|
|
@@ -3916,7 +3971,8 @@ function extractDtoContract(method, sourceFile, project) {
|
|
|
3916
3971
|
formWarnings,
|
|
3917
3972
|
bodySchema,
|
|
3918
3973
|
querySchema,
|
|
3919
|
-
stream: isStream
|
|
3974
|
+
stream: isStream,
|
|
3975
|
+
multipart: uploads.multipart
|
|
3920
3976
|
};
|
|
3921
3977
|
}
|
|
3922
3978
|
function resolveParamClass(method, decoratorName, sourceFile, project) {
|
|
@@ -4401,7 +4457,8 @@ function extractDtoRoute(args) {
|
|
|
4401
4457
|
formWarnings: dtoContract?.formWarnings ?? [],
|
|
4402
4458
|
bodySchema: dtoContract?.bodySchema ?? null,
|
|
4403
4459
|
querySchema: dtoContract?.querySchema ?? null,
|
|
4404
|
-
stream: dtoContract?.stream ?? false
|
|
4460
|
+
stream: dtoContract?.stream ?? false,
|
|
4461
|
+
multipart: dtoContract?.multipart ?? false
|
|
4405
4462
|
}
|
|
4406
4463
|
});
|
|
4407
4464
|
}
|
|
@@ -4631,7 +4688,7 @@ async function watch(config, onChange, options = {}) {
|
|
|
4631
4688
|
}
|
|
4632
4689
|
|
|
4633
4690
|
// src/index.ts
|
|
4634
|
-
var VERSION = "0.
|
|
4691
|
+
var VERSION = "0.13.0";
|
|
4635
4692
|
|
|
4636
4693
|
// src/cli/codegen.ts
|
|
4637
4694
|
async function runCodegen(opts = {}) {
|