diff --git a/package.json b/package.json index 88e5a2310..6995bdc67 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "private": true, "name": "@nestia/station", - "version": "3.13.0", + "version": "3.14.0-dev.20240918", "description": "Nestia station", "scripts": { "build": "node build/index.js", diff --git a/packages/core/package.json b/packages/core/package.json index 46eeb3a22..1f102dd37 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@nestia/core", - "version": "3.13.0", + "version": "3.14.0-dev.20240918", "description": "Super-fast validation decorators of NestJS", "main": "lib/index.js", "typings": "lib/index.d.ts", @@ -36,10 +36,10 @@ }, "homepage": "https://nestia.io", "dependencies": { - "@nestia/fetcher": "^3.13.0", + "@nestia/fetcher": "^3.14.0-dev.20240918", "@nestjs/common": ">=7.0.1", "@nestjs/core": ">=7.0.1", - "@samchon/openapi": "^0.4.6", + "@samchon/openapi": "^1.0.2", "detect-ts-node": "^1.0.5", "get-function-location": "^2.0.0", "glob": "^7.2.0", @@ -53,7 +53,7 @@ "ws": "^7.5.3" }, "peerDependencies": { - "@nestia/fetcher": ">=3.13.0", + "@nestia/fetcher": ">=3.14.0-dev.20240918", "@nestjs/common": ">=7.0.1", "@nestjs/core": ">=7.0.1", "reflect-metadata": ">=0.1.12", diff --git a/packages/fetcher/package.json b/packages/fetcher/package.json index 28cd35d98..154231ef4 100644 --- a/packages/fetcher/package.json +++ b/packages/fetcher/package.json @@ -1,6 +1,6 @@ { "name": "@nestia/fetcher", - "version": "3.13.0", + "version": "3.14.0-dev.20240918", "description": "Fetcher library of Nestia SDK", "main": "lib/index.js", "typings": "lib/index.d.ts", @@ -26,7 +26,7 @@ }, "homepage": "https://nestia.io", "dependencies": { - "@samchon/openapi": "^1.0.0" + "@samchon/openapi": "^1.0.2" }, "peerDependencies": { "typescript": ">= 4.8.0" diff --git a/packages/fetcher/src/FormDataInput.ts b/packages/fetcher/src/FormDataInput.ts new file mode 100644 index 000000000..266a05939 --- /dev/null +++ b/packages/fetcher/src/FormDataInput.ts @@ -0,0 +1,88 @@ +/** + * FormData input type. + * + * `FormDataInput` is a type for the input of the `FormData` request, casting + * `File` property value type as an union of `File` and {@link FormDataInput.IFileProps}, + * especially for the React Native environment. + * + * You know what? In the React Native environment, `File` class is not supported. + * Therefore, when composing a `FormData` request, you have to put the URI address + * of the local filesystem with file name and contet type that is represented by the + * {@link FormDataInput.IFileProps} type. + * + * This `FormDataInput` type is designed for that purpose. If the property value + * type is a `File` class, it converts it to an union type of `File` and + * {@link FormDataInput.IFileProps} type. Also, if the property value type is an array + * of `File` class, it converts it to an array of union type of `File` and + * {@link FormDataInput.IFileProps} type too. + * + * Before | After + * ----------|------------------------ + * `boolean` | `boolean` + * `bigint` | `bigint` + * `number` | `number` + * `string` | `string` + * `File` | `File \| IFileProps` + * `Object` | `never` + * + * @template T Target object type. + * @author Jeongho Nam - https://github.com/samchon + */ +export type FormDataInput = + T extends Array + ? never + : T extends Function + ? never + : { + [P in keyof T]: T[P] extends Array + ? FormDataInput.Value[] + : FormDataInput.Value; + }; +export namespace FormDataInput { + /** + * Value type of the `FormDataInput`. + * + * `Value` is a type for the property value defined in the `FormDataInput`. + * + * If the original value type is a `File` class, `Value` converts it to an union + * type of `File` and {@link IFileProps} type which is a structured data for the + * URI file location in the React Native environment. + */ + export type Value = T extends File ? T | IFileProps : T; + + /** + * Properties of a file. + * + * In the React Native, this `IFileProps` structured data can replace the `File` + * class instance in the `FormData` request. + * + * Just put the {@link uri URI address} of the local file system with the file's + * {@link name} and {@link type}. It would be casted to the `File` class instance + * automatically in the `FormData` request. + * + * Note that, this `IFileProps` type works only in the React Native environment. + * If you are developing a Web or NodeJS application, you have to utilize the + * `File` class instance directly. + */ + export interface IFileProps { + /** + * URI address of the file. + * + * In the React Native, the URI address in the local file system can replace + * the `File` class instance. If + * + * @format uri + */ + uri: string; + + /** + * Name of the file. + */ + name: string; + + /** + * Content type of the file. + */ + type: string; + } +} diff --git a/packages/fetcher/src/index.ts b/packages/fetcher/src/index.ts index a6abd9401..96c85b964 100644 --- a/packages/fetcher/src/index.ts +++ b/packages/fetcher/src/index.ts @@ -1,3 +1,4 @@ +export * from "./FormDataInput"; export * from "./HttpError"; export * from "./IConnection"; export * from "./IEncryptionPassword"; diff --git a/packages/fetcher/src/internal/FetcherBase.ts b/packages/fetcher/src/internal/FetcherBase.ts index 848955430..b4118f2a4 100644 --- a/packages/fetcher/src/internal/FetcherBase.ts +++ b/packages/fetcher/src/internal/FetcherBase.ts @@ -214,10 +214,9 @@ const request_form_data_body = (input: Record): FormData => { const encoded: FormData = new FormData(); const append = (key: string) => (value: any) => { if (value === undefined) return; - else if (value instanceof Blob) - if (value instanceof File) encoded.append(key, value, value.name); - else encoded.append(key, value); - else encoded.append(key, String(value)); + else if (typeof File === "function" && value instanceof File) + encoded.append(key, value, value.name); + else encoded.append(key, value); }; for (const [key, value] of Object.entries(input)) if (Array.isArray(value)) value.map(append(key)); diff --git a/packages/migrate/package.json b/packages/migrate/package.json index b99305b27..c4a6372fb 100644 --- a/packages/migrate/package.json +++ b/packages/migrate/package.json @@ -65,7 +65,7 @@ "typescript-transform-paths": "^3.4.6" }, "dependencies": { - "@samchon/openapi": "^1.0.0", + "@samchon/openapi": "^1.0.2", "commander": "10.0.0", "inquirer": "8.2.5", "prettier": "^3.2.5", diff --git a/packages/sdk/package.json b/packages/sdk/package.json index f59e4629b..e0eb90815 100644 --- a/packages/sdk/package.json +++ b/packages/sdk/package.json @@ -1,6 +1,6 @@ { "name": "@nestia/sdk", - "version": "3.13.0", + "version": "3.14.0-dev.20240918", "description": "Nestia SDK and Swagger generator", "main": "lib/index.js", "typings": "lib/index.d.ts", @@ -32,9 +32,9 @@ }, "homepage": "https://nestia.io", "dependencies": { - "@nestia/core": "^3.13.0", - "@nestia/fetcher": "^3.13.0", - "@samchon/openapi": "^1.0.0", + "@nestia/core": "^3.14.0-dev.20240918", + "@nestia/fetcher": "^3.14.0-dev.20240918", + "@samchon/openapi": "^1.0.2", "cli": "^1.0.1", "get-function-location": "^2.0.0", "glob": "^7.2.0", @@ -47,8 +47,8 @@ "typia": "^6.10.0" }, "peerDependencies": { - "@nestia/core": ">=3.13.0", - "@nestia/fetcher": ">=3.13.0", + "@nestia/core": ">=3.14.0-dev.20240918", + "@nestia/fetcher": ">=3.14.0-dev.20240918", "@nestjs/common": ">=7.0.1", "@nestjs/core": ">=7.0.1", "reflect-metadata": ">=0.1.12", diff --git a/packages/sdk/src/generates/internal/SdkAliasCollection.ts b/packages/sdk/src/generates/internal/SdkAliasCollection.ts index 64f93e55e..a713341eb 100644 --- a/packages/sdk/src/generates/internal/SdkAliasCollection.ts +++ b/packages/sdk/src/generates/internal/SdkAliasCollection.ts @@ -65,11 +65,17 @@ export namespace SdkAliasCollection { export const input = (project: INestiaProject) => (importer: ImportDictionary) => - (param: ITypedHttpRouteParameter): ts.TypeNode => { - if (project.config.clone === true) - return from(project)(importer)(param.metadata); + (param: ITypedHttpRouteParameter.IBody): ts.TypeNode => { + if (project.config.clone === true) { + const type: ts.TypeNode = from(project)(importer)(param.metadata); + return param.contentType === "multipart/form-data" + ? formDataInput(importer)(type) + : type; + } const type: ts.TypeNode = name(param); - if (project.config.primitive === false) return type; + if (param.contentType === "multipart/form-data") + return formDataInput(importer)(type); + else if (project.config.primitive === false) return type; return ts.factory.createTypeReferenceNode( importer.external({ type: true, @@ -161,6 +167,16 @@ export namespace SdkAliasCollection { propagate: false, }, })(importer)(route); + + const formDataInput = (importer: ImportDictionary) => (type: ts.TypeNode) => + ts.factory.createTypeReferenceNode( + importer.external({ + type: true, + library: "@nestia/fetcher", + instance: "FormDataInput", + }), + [type], + ); } interface IBranch { diff --git a/test/features/multipart-form-data-fastify/src/api/functional/multipart/index.ts b/test/features/multipart-form-data-fastify/src/api/functional/multipart/index.ts index 31c240f26..8620af8ab 100644 --- a/test/features/multipart-form-data-fastify/src/api/functional/multipart/index.ts +++ b/test/features/multipart-form-data-fastify/src/api/functional/multipart/index.ts @@ -4,7 +4,7 @@ * @nestia Generated by Nestia - https://github.com/samchon/nestia */ //================================================================ -import type { IConnection, Resolved, Primitive } from "@nestia/fetcher"; +import type { IConnection, FormDataInput, Primitive } from "@nestia/fetcher"; import { PlainFetcher } from "@nestia/fetcher/lib/PlainFetcher"; import type { IMultipart } from "../../structures/IMultipart"; @@ -29,7 +29,7 @@ export async function post( ); } export namespace post { - export type Input = Resolved; + export type Input = FormDataInput; export type Output = Primitive; export const METADATA = { diff --git a/test/features/multipart-form-data-fastify/swagger.json b/test/features/multipart-form-data-fastify/swagger.json index f613de413..a110a8377 100644 --- a/test/features/multipart-form-data-fastify/swagger.json +++ b/test/features/multipart-form-data-fastify/swagger.json @@ -7,7 +7,7 @@ } ], "info": { - "version": "3.13.0-dev.2024910", + "version": "3.14.0-dev.20240917", "title": "@samchon/nestia-test", "description": "Test program of Nestia", "license": { diff --git a/test/features/multipart-form-data/src/api/functional/multipart/index.ts b/test/features/multipart-form-data/src/api/functional/multipart/index.ts index 31c240f26..8620af8ab 100644 --- a/test/features/multipart-form-data/src/api/functional/multipart/index.ts +++ b/test/features/multipart-form-data/src/api/functional/multipart/index.ts @@ -4,7 +4,7 @@ * @nestia Generated by Nestia - https://github.com/samchon/nestia */ //================================================================ -import type { IConnection, Resolved, Primitive } from "@nestia/fetcher"; +import type { IConnection, FormDataInput, Primitive } from "@nestia/fetcher"; import { PlainFetcher } from "@nestia/fetcher/lib/PlainFetcher"; import type { IMultipart } from "../../structures/IMultipart"; @@ -29,7 +29,7 @@ export async function post( ); } export namespace post { - export type Input = Resolved; + export type Input = FormDataInput; export type Output = Primitive; export const METADATA = { diff --git a/test/features/multipart-form-data/swagger.json b/test/features/multipart-form-data/swagger.json index f613de413..a110a8377 100644 --- a/test/features/multipart-form-data/swagger.json +++ b/test/features/multipart-form-data/swagger.json @@ -7,7 +7,7 @@ } ], "info": { - "version": "3.13.0-dev.2024910", + "version": "3.14.0-dev.20240917", "title": "@samchon/nestia-test", "description": "Test program of Nestia", "license": { diff --git a/test/package.json b/test/package.json index 64d52ac4f..51843e483 100644 --- a/test/package.json +++ b/test/package.json @@ -1,7 +1,7 @@ { "private": true, "name": "@samchon/nestia-test", - "version": "3.13.0", + "version": "3.14.0-dev.20240918", "description": "Test program of Nestia", "main": "index.js", "scripts": { @@ -26,9 +26,9 @@ }, "homepage": "https://nestia.io", "devDependencies": { - "@nestia/sdk": "^3.13.0", + "@nestia/sdk": "^3.14.0-dev.20240918", "@nestjs/swagger": "^7.4.0", - "@samchon/openapi": "^1.0.1", + "@samchon/openapi": "^1.0.2", "@types/express": "^4.17.17", "@types/node": "20.11.16", "@types/uuid": "^9.0.8", @@ -40,9 +40,9 @@ }, "dependencies": { "@fastify/multipart": "^8.1.0", - "@nestia/core": "^3.13.0", + "@nestia/core": "^3.14.0-dev.20240918", "@nestia/e2e": "^0.7.0", - "@nestia/fetcher": "^3.13.0", + "@nestia/fetcher": "^3.14.0-dev.20240918", "@nestjs/common": "^10.4.1", "@nestjs/core": "^10.4.1", "@nestjs/platform-express": "^10.4.1", diff --git a/website/package.json b/website/package.json index a5a0899d1..1b9caedd5 100644 --- a/website/package.json +++ b/website/package.json @@ -24,7 +24,7 @@ "@mui/material": "5.15.6", "@mui/system": "5.15.6", "@nestia/migrate": "^0.18.0", - "@samchon/openapi": "^1.0.1", + "@samchon/openapi": "^1.0.2", "@stackblitz/sdk": "^1.9.0", "js-yaml": "^4.1.0", "next": "14.2.5",