diff --git a/benchmark/package.json b/benchmark/package.json index 3d5705fea..bd20e264b 100644 --- a/benchmark/package.json +++ b/benchmark/package.json @@ -42,7 +42,7 @@ "reflect-metadata": "^0.2.2", "tgrid": "^1.1.0", "tstl": "^3.0.0", - "typia": "^7.2.0" + "typia": "^7.3.0" }, "devDependencies": { "@types/autocannon": "^7.9.0", diff --git a/package.json b/package.json index e878cd347..013edd715 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "private": true, "name": "@nestia/station", - "version": "4.2.0", + "version": "4.3.0", "description": "Nestia station", "scripts": { "build": "node deploy build", diff --git a/packages/benchmark/package.json b/packages/benchmark/package.json index 22d39fc36..18312d25d 100644 --- a/packages/benchmark/package.json +++ b/packages/benchmark/package.json @@ -34,7 +34,7 @@ "ts-patch": "^3.3.0", "typescript": "~5.7.2", "typescript-transform-paths": "^3.4.7", - "typia": "^7.2.0", + "typia": "^7.3.0", "uuid": "^10.0.0" }, "dependencies": { diff --git a/packages/core/package.json b/packages/core/package.json index f9d08e1fc..40bd04b12 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@nestia/core", - "version": "4.2.0-dev.20241211-2", + "version": "4.3.0-dev.20241215-2", "description": "Super-fast validation decorators of NestJS", "main": "lib/index.js", "typings": "lib/index.d.ts", @@ -48,11 +48,11 @@ "reflect-metadata": ">=0.1.12", "rxjs": ">=6.0.3", "tgrid": "^1.1.0", - "typia": "^7.2.0", + "typia": "^7.3.0", "ws": "^7.5.3" }, "peerDependencies": { - "@nestia/fetcher": ">=4.2.0-dev.20241211-2", + "@nestia/fetcher": ">=4.3.0-dev.20241215-2", "@nestjs/common": ">=7.0.1", "@nestjs/core": ">=7.0.1", "reflect-metadata": ">=0.1.12", diff --git a/packages/e2e/package.json b/packages/e2e/package.json index 0e19613df..a3ede6dee 100644 --- a/packages/e2e/package.json +++ b/packages/e2e/package.json @@ -41,7 +41,7 @@ "ts-patch": "^3.3.0", "typescript": "~5.7.2", "typescript-transform-paths": "^3.4.7", - "typia": "^7.2.0" + "typia": "^7.3.0" }, "files": [ "lib", diff --git a/packages/editor/package.json b/packages/editor/package.json index 81906db3c..94275c309 100644 --- a/packages/editor/package.json +++ b/packages/editor/package.json @@ -1,6 +1,6 @@ { "name": "@nestia/editor", - "version": "4.2.0-dev.20241211-2", + "version": "4.3.0-dev.20241215-2", "typings": "lib/index.d.ts", "main": "lib/index.js", "module": "lib/index.mjs", @@ -40,7 +40,7 @@ "js-yaml": "^4.1.0", "prettier": "3.3.3", "react-mui-fileuploader": "^0.5.2", - "typia": "^7.2.0" + "typia": "^7.3.0" }, "devDependencies": { "@eslint/js": "^9.13.0", diff --git a/packages/fetcher/package.json b/packages/fetcher/package.json index deec3c5b2..666abbd4b 100644 --- a/packages/fetcher/package.json +++ b/packages/fetcher/package.json @@ -1,6 +1,6 @@ { "name": "@nestia/fetcher", - "version": "4.2.0-dev.20241211-2", + "version": "4.3.0-dev.20241215-2", "description": "Fetcher library of Nestia SDK", "main": "lib/index.js", "typings": "lib/index.d.ts", @@ -27,7 +27,7 @@ "homepage": "https://nestia.io", "dependencies": { "@samchon/openapi": "^2.0.1", - "typia": "^7.2.0" + "typia": "^7.3.0" }, "peerDependencies": { "typescript": ">= 4.8.0" @@ -38,7 +38,7 @@ "@typescript-eslint/parser": "^5.46.1", "rimraf": "^3.0.2", "typescript": "~5.7.2", - "typia": "^7.2.0" + "typia": "^7.3.0" }, "files": [ "README.md", diff --git a/packages/migrate/package.json b/packages/migrate/package.json index eca3db3b6..2b8056705 100644 --- a/packages/migrate/package.json +++ b/packages/migrate/package.json @@ -1,6 +1,6 @@ { "name": "@nestia/migrate", - "version": "4.2.0-dev.20241211-2", + "version": "4.3.0-dev.20241215-2", "description": "Migration program from swagger to NestJS", "typings": "lib/index.d.ts", "main": "lib/index.js", @@ -78,7 +78,7 @@ "prettier": "^3.2.5", "tstl": "^3.0.0", "typescript": "~5.7.2", - "typia": "^7.2.0" + "typia": "^7.3.0" }, "files": [ "lib", diff --git a/packages/sdk/package.json b/packages/sdk/package.json index 128ef8bb9..f4b91d220 100644 --- a/packages/sdk/package.json +++ b/packages/sdk/package.json @@ -1,6 +1,6 @@ { "name": "@nestia/sdk", - "version": "4.2.0-dev.20241211-2", + "version": "4.3.0-dev.20241215-2", "description": "Nestia SDK and Swagger generator", "main": "lib/index.js", "typings": "lib/index.d.ts", @@ -44,11 +44,11 @@ "tsconfck": "^2.1.2", "tsconfig-paths": "^4.1.1", "tstl": "^3.0.0", - "typia": "^7.2.0" + "typia": "^7.3.0" }, "peerDependencies": { - "@nestia/core": ">=4.2.0-dev.20241211-2", - "@nestia/fetcher": ">=4.2.0-dev.20241211-2", + "@nestia/core": ">=4.3.0-dev.20241215-2", + "@nestia/fetcher": ">=4.3.0-dev.20241215-2", "@nestjs/common": ">=7.0.1", "@nestjs/core": ">=7.0.1", "reflect-metadata": ">=0.1.12", diff --git a/packages/sdk/src/generates/internal/SdkTypeProgrammer.ts b/packages/sdk/src/generates/internal/SdkTypeProgrammer.ts index 405bfbca2..16402d3bf 100644 --- a/packages/sdk/src/generates/internal/SdkTypeProgrammer.ts +++ b/packages/sdk/src/generates/internal/SdkTypeProgrammer.ts @@ -17,6 +17,7 @@ import { Escaper } from "typia/lib/utils/Escaper"; import { INestiaProject } from "../../structures/INestiaProject"; import { FilePrinter } from "./FilePrinter"; import { ImportDictionary } from "./ImportDictionary"; +import { SdkTypeTagProgrammer } from "./SdkTypeTagProgrammer"; export namespace SdkTypeProgrammer { /* ----------------------------------------------------------- @@ -100,7 +101,7 @@ export namespace SdkTypeProgrammer { ) return ts.factory.createIntersectionTypeNode([ TypeFactory.keyword("string"), - writeTag(importer)({ + SdkTypeTagProgrammer.write(importer, "string", { name: "Format", value: "date-time", } as IMetadataTypeTag), @@ -184,6 +185,7 @@ export namespace SdkTypeProgrammer { (importer: ImportDictionary) => (meta: MetadataAtomic): ts.TypeNode => write_type_tag_matrix(importer)( + meta.type as "boolean" | "bigint" | "number" | "string", ts.factory.createKeywordTypeNode( meta.type === "boolean" ? ts.SyntaxKind.BooleanKeyword @@ -204,6 +206,7 @@ export namespace SdkTypeProgrammer { (importer: ImportDictionary) => (meta: MetadataArray): ts.TypeNode => write_type_tag_matrix(importer)( + "array", ts.factory.createArrayTypeNode( write(project)(importer)(meta.type.value), ), @@ -298,22 +301,30 @@ export namespace SdkTypeProgrammer { ----------------------------------------------------------- */ const write_type_tag_matrix = (importer: ImportDictionary) => - (base: ts.TypeNode, matrix: IMetadataTypeTag[][]): ts.TypeNode => { + ( + from: "array" | "boolean" | "number" | "bigint" | "string" | "object", + base: ts.TypeNode, + matrix: IMetadataTypeTag[][], + ): ts.TypeNode => { matrix = matrix.filter((row) => row.length !== 0); if (matrix.length === 0) return base; else if (matrix.length === 1) return ts.factory.createIntersectionTypeNode([ base, - ...matrix[0].map((tag) => writeTag(importer)(tag)), + ...matrix[0].map((tag) => + SdkTypeTagProgrammer.write(importer, from, tag), + ), ]); return ts.factory.createIntersectionTypeNode([ base, ts.factory.createUnionTypeNode( matrix.map((row) => row.length === 1 - ? writeTag(importer)(row[0]) + ? SdkTypeTagProgrammer.write(importer, from, row[0]) : ts.factory.createIntersectionTypeNode( - row.map((tag) => writeTag(importer)(tag)), + row.map((tag) => + SdkTypeTagProgrammer.write(importer, from, tag), + ), ), ), ), @@ -322,34 +333,6 @@ export namespace SdkTypeProgrammer { } const writeNode = (text: string) => ts.factory.createTypeReferenceNode(text); -const writeTag = (importer: ImportDictionary) => (tag: IMetadataTypeTag) => { - const instance: string = tag.name.split("<")[0]; - return ts.factory.createTypeReferenceNode( - importer.external({ - type: true, - library: `typia/lib/tags/${instance}`, - instance, - }), - [ - ts.factory.createLiteralTypeNode( - typeof tag.value === "boolean" - ? tag.value - ? ts.factory.createTrue() - : ts.factory.createFalse() - : typeof tag.value === "bigint" - ? tag.value < BigInt(0) - ? ts.factory.createPrefixUnaryExpression( - ts.SyntaxKind.MinusToken, - ts.factory.createBigIntLiteral((-tag.value).toString()), - ) - : ts.factory.createBigIntLiteral(tag.value.toString()) - : typeof tag.value === "number" - ? ExpressionFactory.number(tag.value) - : ts.factory.createStringLiteral(tag.value), - ), - ], - ); -}; const writeComment = (atomics: MetadataAtomic[]) => (description: string | null, jsDocTags: IJsDocTagInfo[]): string => { diff --git a/packages/sdk/src/generates/internal/SdkTypeTagProgrammer.ts b/packages/sdk/src/generates/internal/SdkTypeTagProgrammer.ts new file mode 100644 index 000000000..6c9c277f7 --- /dev/null +++ b/packages/sdk/src/generates/internal/SdkTypeTagProgrammer.ts @@ -0,0 +1,120 @@ +import ts from "typescript"; +import { ExpressionFactory } from "typia/lib/factories/ExpressionFactory"; +import { LiteralFactory } from "typia/lib/factories/LiteralFactory"; +import { IMetadataTypeTag } from "typia/lib/schemas/metadata/IMetadataTypeTag"; + +import { ImportDictionary } from "./ImportDictionary"; + +export namespace SdkTypeTagProgrammer { + export const write = ( + importer: ImportDictionary, + from: "object" | "array" | "boolean" | "number" | "bigint" | "string", + tag: IMetadataTypeTag, + ) => { + const instance: string = tag.name.split("<")[0]; + if (PREDEFINED[from]?.has(instance) === true) + return ts.factory.createTypeReferenceNode( + importer.external({ + type: true, + library: `typia/lib/tags/${instance}`, + instance, + }), + instance === "Example" + ? [] + : [ + ts.factory.createLiteralTypeNode( + typeof tag.value === "boolean" + ? tag.value + ? ts.factory.createTrue() + : ts.factory.createFalse() + : typeof tag.value === "bigint" + ? tag.value < BigInt(0) + ? ts.factory.createPrefixUnaryExpression( + ts.SyntaxKind.MinusToken, + ts.factory.createBigIntLiteral( + (-tag.value).toString(), + ), + ) + : ts.factory.createBigIntLiteral(tag.value.toString()) + : typeof tag.value === "number" + ? ExpressionFactory.number(tag.value) + : ts.factory.createStringLiteral(tag.value), + ), + ], + ); + return ts.factory.createTypeReferenceNode( + importer.external({ + type: true, + library: `typia/lib/tags/TagBase`, + instance: "TagBase", + }), + [ + ts.factory.createLiteralTypeNode( + LiteralFactory.write({ + target: from, + kind: tag.kind, + value: tag.value, + validate: tag.validate, + exclusive: tag.exclusive, + schema: tag.schema, + }) as any, + ), + ], + ); + }; +} + +const COMMON_KINDS = ["Default", "Example", "Examples", "Sequence"]; +const PREDEFINED = { + object: new Set([...COMMON_KINDS]), + array: new Set([...COMMON_KINDS, "MinItems", "MaxItems", "UniqueItems"]), + boolean: new Set([...COMMON_KINDS]), + number: new Set([ + ...COMMON_KINDS, + "Minimum", + "Maximum", + "ExclusiveMinimum", + "ExclusiveMaximum", + "MultipleOf", + "Type", + ]), + bigint: new Set([ + ...COMMON_KINDS, + "Minimum", + "Maximum", + "ExclusiveMinimum", + "ExclusiveMaximum", + "MultipleOf", + "Type", + ]), + string: new Set([ + ...COMMON_KINDS, + "ContentMediaType", + "Format", + "MaxLength", + "MinLength", + "Pattern", + ]), +}; + +// export * from "./Constant"; +// export * from "./ContentMediaType"; +// export * from "./Default"; +// export * from "./Example"; +// export * from "./Examples"; +// export * from "./ExclusiveMaximum"; +// export * from "./ExclusiveMinimum"; +// export * from "./Format"; +// export * from "./JsonSchemaPlugin"; +// export * from "./Maximum"; +// export * from "./MaxItems"; +// export * from "./MaxLength"; +// export * from "./Minimum"; +// export * from "./MinItems"; +// export * from "./MinLength"; +// export * from "./MultipleOf"; +// export * from "./Pattern"; +// export * from "./Sequence"; +// export * from "./TagBase"; +// export * from "./Type"; +// export * from "./UniqueItems"; diff --git a/packages/sdk/src/utils/TypeLiteralExpression.ts b/packages/sdk/src/utils/TypeLiteralExpression.ts new file mode 100644 index 000000000..e69de29bb diff --git a/test/features/clone-tags/nestia.config.ts b/test/features/clone-tags/nestia.config.ts new file mode 100644 index 000000000..af9063eac --- /dev/null +++ b/test/features/clone-tags/nestia.config.ts @@ -0,0 +1,54 @@ +import { INestiaConfig } from "@nestia/sdk"; + +export const NESTIA_CONFIG: INestiaConfig = { + input: ["src/controllers"], + output: "src/api", + clone: true, + swagger: { + output: "swagger.json", + beautify: true, + operationId: (props) => `${props.class}.${props.function}`, + security: { + //---- + // YOU CAN CHOOSE ANY SECURITY SCHEMES LIKE + //---- + // @security basic + // @security bearer + // @security oauth2 read write + // @security custom + basic: { + type: "http", + scheme: "basic", + }, + bearer: { + type: "http", + scheme: "bearer", + }, + oauth2: { + type: "oauth2", + flows: { + implicit: { + authorizationUrl: "https://example.com/api/oauth/dialog", + refreshUrl: "https://example.com/api/oauth/refresh", + scopes: { + //---- + // YOU CAN CHOOSE ANY SCOPES + //---- + // (@security oauth2 read write) -> BOTH OF THEM + // (@security oauth2 read) -> ONE OF THEM + // (@security oauth) -> NOTHING + read: "read authority", + write: "write authority", + }, + }, + }, + }, + custom: { + type: "apiKey", + in: "header", + name: "Authorization", + }, + }, + }, +}; +export default NESTIA_CONFIG; diff --git a/test/features/clone-tags/src/Backend.ts b/test/features/clone-tags/src/Backend.ts new file mode 100644 index 000000000..be2c2d840 --- /dev/null +++ b/test/features/clone-tags/src/Backend.ts @@ -0,0 +1,27 @@ +import core from "@nestia/core"; +import { INestApplication } from "@nestjs/common"; +import { NestFactory } from "@nestjs/core"; + +export class Backend { + private application_?: INestApplication; + + public async open(): Promise { + this.application_ = await NestFactory.create( + await core.EncryptedModule.dynamic(__dirname + "/controllers", { + key: "A".repeat(32), + iv: "B".repeat(16), + }), + { logger: false }, + ); + await this.application_.listen(37_000); + } + + public async close(): Promise { + if (this.application_ === undefined) return; + + const app = this.application_; + await app.close(); + + delete this.application_; + } +} diff --git a/test/features/clone-tags/src/api/HttpError.ts b/test/features/clone-tags/src/api/HttpError.ts new file mode 100644 index 000000000..5df328ae4 --- /dev/null +++ b/test/features/clone-tags/src/api/HttpError.ts @@ -0,0 +1 @@ +export { HttpError } from "@nestia/fetcher"; diff --git a/test/features/clone-tags/src/api/IConnection.ts b/test/features/clone-tags/src/api/IConnection.ts new file mode 100644 index 000000000..107bdb8f8 --- /dev/null +++ b/test/features/clone-tags/src/api/IConnection.ts @@ -0,0 +1 @@ +export type { IConnection } from "@nestia/fetcher"; diff --git a/test/features/clone-tags/src/api/Primitive.ts b/test/features/clone-tags/src/api/Primitive.ts new file mode 100644 index 000000000..ebdcd7620 --- /dev/null +++ b/test/features/clone-tags/src/api/Primitive.ts @@ -0,0 +1 @@ +export type { Primitive } from "typia"; diff --git a/test/features/clone-tags/src/api/Resolved.ts b/test/features/clone-tags/src/api/Resolved.ts new file mode 100644 index 000000000..7cf4920b0 --- /dev/null +++ b/test/features/clone-tags/src/api/Resolved.ts @@ -0,0 +1 @@ +export type { Resolved } from "typia"; diff --git a/test/features/clone-tags/src/api/functional/bbs/articles/index.ts b/test/features/clone-tags/src/api/functional/bbs/articles/index.ts new file mode 100644 index 000000000..13afa5652 --- /dev/null +++ b/test/features/clone-tags/src/api/functional/bbs/articles/index.ts @@ -0,0 +1,170 @@ +/** + * @packageDocumentation + * @module api.functional.bbs.articles + * @nestia Generated by Nestia - https://github.com/samchon/nestia + */ +//================================================================ +import type { IConnection } from "@nestia/fetcher"; +import { PlainFetcher } from "@nestia/fetcher/lib/PlainFetcher"; +import type { Format } from "typia/lib/tags/Format"; + +import type { IBbsArticle } from "../../../structures/IBbsArticle"; + +/** + * Would be shown without any mark. + * + * @param section Section code + * @param input Content to store + * @returns Newly archived article + * @tag public Some description describing public group... + * @tag write Write accessor + * @summary Public API + * @security bearer + * @security oauth2 read write + * + * @controller BbsArticlesController.store + * @path POST /bbs/articles/:section + * @nestia Generated by Nestia - https://github.com/samchon/nestia + */ +export async function store( + connection: IConnection, + section: string, + input: store.Input, +): Promise { + return PlainFetcher.fetch( + { + ...connection, + headers: { + ...connection.headers, + "Content-Type": "application/json", + }, + }, + { + ...store.METADATA, + template: store.METADATA.path, + path: store.path(section), + }, + input, + ); +} +export namespace store { + export type Input = IBbsArticle.IStore; + export type Output = IBbsArticle; + + export const METADATA = { + method: "POST", + path: "/bbs/articles/:section", + request: { + type: "application/json", + encrypted: false, + }, + response: { + type: "application/json", + encrypted: false, + }, + status: 201, + } as const; + + export const path = (section: string) => + `/bbs/articles/${encodeURIComponent(section?.toString() ?? "null")}`; +} + +/** + * Deprecated API. + * + * Would be marked as "deprecated". + * + * For reference, top sentence "Deprecated API." can replace the `@summary` tag. + * + * @param section Section code + * @param id Target article ID + * @param input Content to update + * @returns Updated content + * @deprecated + * @operationId updateArticle + * @security basic + * @security bearer + * + * @controller BbsArticlesController.update + * @path PUT /bbs/articles/:section/:id + * @nestia Generated by Nestia - https://github.com/samchon/nestia + */ +export async function update( + connection: IConnection, + section: string, + id: string & Format<"uuid">, + input: update.Input, +): Promise { + return PlainFetcher.fetch( + { + ...connection, + headers: { + ...connection.headers, + "Content-Type": "application/json", + }, + }, + { + ...update.METADATA, + template: update.METADATA.path, + path: update.path(section, id), + }, + input, + ); +} +export namespace update { + export type Input = IBbsArticle.IStore; + export type Output = IBbsArticle; + + export const METADATA = { + method: "PUT", + path: "/bbs/articles/:section/:id", + request: { + type: "application/json", + encrypted: false, + }, + response: { + type: "application/json", + encrypted: false, + }, + status: 200, + } as const; + + export const path = (section: string, id: string & Format<"uuid">) => + `/bbs/articles/${encodeURIComponent(section?.toString() ?? "null")}/${encodeURIComponent(id?.toString() ?? "null")}`; +} + +/** + * Would not be shown. + * + * @internal + * + * @controller BbsArticlesController.erase + * @path DELETE /bbs/articles/:section/:id + * @nestia Generated by Nestia - https://github.com/samchon/nestia + */ +export async function erase( + connection: IConnection, + section: string, + id: string & Format<"uuid">, +): Promise { + return PlainFetcher.fetch(connection, { + ...erase.METADATA, + template: erase.METADATA.path, + path: erase.path(section, id), + }); +} +export namespace erase { + export const METADATA = { + method: "DELETE", + path: "/bbs/articles/:section/:id", + request: null, + response: { + type: "application/json", + encrypted: false, + }, + status: 200, + } as const; + + export const path = (section: string, id: string & Format<"uuid">) => + `/bbs/articles/${encodeURIComponent(section?.toString() ?? "null")}/${encodeURIComponent(id?.toString() ?? "null")}`; +} diff --git a/test/features/clone-tags/src/api/functional/bbs/index.ts b/test/features/clone-tags/src/api/functional/bbs/index.ts new file mode 100644 index 000000000..7a891f888 --- /dev/null +++ b/test/features/clone-tags/src/api/functional/bbs/index.ts @@ -0,0 +1,7 @@ +/** + * @packageDocumentation + * @module api.functional.bbs + * @nestia Generated by Nestia - https://github.com/samchon/nestia + */ +//================================================================ +export * as articles from "./articles"; diff --git a/test/features/clone-tags/src/api/functional/index.ts b/test/features/clone-tags/src/api/functional/index.ts new file mode 100644 index 000000000..65d3ff67d --- /dev/null +++ b/test/features/clone-tags/src/api/functional/index.ts @@ -0,0 +1,8 @@ +/** + * @packageDocumentation + * @module api.functional + * @nestia Generated by Nestia - https://github.com/samchon/nestia + */ +//================================================================ +export * as bbs from "./bbs"; +export * as v0 from "./v0"; diff --git a/test/features/clone-tags/src/api/functional/v0/index.ts b/test/features/clone-tags/src/api/functional/v0/index.ts new file mode 100644 index 000000000..733372738 --- /dev/null +++ b/test/features/clone-tags/src/api/functional/v0/index.ts @@ -0,0 +1,7 @@ +/** + * @packageDocumentation + * @module api.functional.v0 + * @nestia Generated by Nestia - https://github.com/samchon/nestia + */ +//================================================================ +export * as transaction from "./transaction"; diff --git a/test/features/clone-tags/src/api/functional/v0/transaction/index.ts b/test/features/clone-tags/src/api/functional/v0/transaction/index.ts new file mode 100644 index 000000000..99302cc7a --- /dev/null +++ b/test/features/clone-tags/src/api/functional/v0/transaction/index.ts @@ -0,0 +1,7 @@ +/** + * @packageDocumentation + * @module api.functional.v0.transaction + * @nestia Generated by Nestia - https://github.com/samchon/nestia + */ +//================================================================ +export * as user from "./user"; diff --git a/test/features/clone-tags/src/api/functional/v0/transaction/user/index.ts b/test/features/clone-tags/src/api/functional/v0/transaction/user/index.ts new file mode 100644 index 000000000..0efde2c6a --- /dev/null +++ b/test/features/clone-tags/src/api/functional/v0/transaction/user/index.ts @@ -0,0 +1,72 @@ +/** + * @packageDocumentation + * @module api.functional.v0.transaction.user + * @nestia Generated by Nestia - https://github.com/samchon/nestia + */ +//================================================================ +import type { IConnection } from "@nestia/fetcher"; +import { PlainFetcher } from "@nestia/fetcher/lib/PlainFetcher"; +import type { TagBase } from "typia/lib/tags/TagBase"; + +/** + * @controller TransactionController.findTransactionsByUser + * @path GET /v0/transaction/user/:pubkey + * @nestia Generated by Nestia - https://github.com/samchon/nestia + */ +export async function findTransactionsByUser( + connection: IConnection, + pubkey: string & + TagBase<{ + target: "string"; + kind: "regexPattern"; + value: "/^[0-9a-fA-F]+$/"; + validate: "RegExp(/^[0-9a-fA-F]+$/).test($input)"; + exclusive: false; + schema: {}; + }> & + TagBase<{ + target: "string"; + kind: "lengthDivisibleBy"; + value: 2; + validate: "$input.length % 2 === 0"; + exclusive: false; + }>, +): Promise { + return PlainFetcher.fetch(connection, { + ...findTransactionsByUser.METADATA, + template: findTransactionsByUser.METADATA.path, + path: findTransactionsByUser.path(pubkey), + }); +} +export namespace findTransactionsByUser { + export const METADATA = { + method: "GET", + path: "/v0/transaction/user/:pubkey", + request: null, + response: { + type: "application/json", + encrypted: false, + }, + status: 200, + } as const; + + export const path = ( + pubkey: string & + TagBase<{ + target: "string"; + kind: "regexPattern"; + value: "/^[0-9a-fA-F]+$/"; + validate: "RegExp(/^[0-9a-fA-F]+$/).test($input)"; + exclusive: false; + schema: {}; + }> & + TagBase<{ + target: "string"; + kind: "lengthDivisibleBy"; + value: 2; + validate: "$input.length % 2 === 0"; + exclusive: false; + }>, + ) => + `/v0/transaction/user/${encodeURIComponent(pubkey?.toString() ?? "null")}`; +} diff --git a/test/features/clone-tags/src/api/index.ts b/test/features/clone-tags/src/api/index.ts new file mode 100644 index 000000000..1705f43c8 --- /dev/null +++ b/test/features/clone-tags/src/api/index.ts @@ -0,0 +1,4 @@ +import * as api from "./module"; + +export * from "./module"; +export default api; diff --git a/test/features/clone-tags/src/api/module.ts b/test/features/clone-tags/src/api/module.ts new file mode 100644 index 000000000..2137b4473 --- /dev/null +++ b/test/features/clone-tags/src/api/module.ts @@ -0,0 +1,6 @@ +export type * from "./IConnection"; +export type * from "./Primitive"; +export type * from "./Resolved"; +export * from "./HttpError"; + +export * as functional from "./functional"; diff --git a/test/features/clone-tags/src/api/structures/IAttachmentFile.ts b/test/features/clone-tags/src/api/structures/IAttachmentFile.ts new file mode 100644 index 000000000..bcf38c37f --- /dev/null +++ b/test/features/clone-tags/src/api/structures/IAttachmentFile.ts @@ -0,0 +1,12 @@ +import type { Format } from "typia/lib/tags/Format"; +import type { MaxLength } from "typia/lib/tags/MaxLength"; +import type { MinLength } from "typia/lib/tags/MinLength"; + +export type IAttachmentFile = { + /** + * @minLengt 1 + */ + name: null | (string & MaxLength<255>); + extension: null | (string & MinLength<1> & MaxLength<8>); + url: string & Format<"uri">; +}; diff --git a/test/features/clone-tags/src/api/structures/IBbsArticle.ts b/test/features/clone-tags/src/api/structures/IBbsArticle.ts new file mode 100644 index 000000000..90e4745ae --- /dev/null +++ b/test/features/clone-tags/src/api/structures/IBbsArticle.ts @@ -0,0 +1,21 @@ +import type { Format } from "typia/lib/tags/Format"; +import type { MaxLength } from "typia/lib/tags/MaxLength"; +import type { MinLength } from "typia/lib/tags/MinLength"; + +import type { IAttachmentFile } from "./IAttachmentFile"; + +export type IBbsArticle = { + id: string & Format<"uuid">; + section: string; + created_at: string & Format<"date-time">; + title: string & MinLength<3> & MaxLength<50>; + body: string; + files: IAttachmentFile[]; +}; +export namespace IBbsArticle { + export type IStore = { + title: string & MinLength<3> & MaxLength<50>; + body: string; + files: IAttachmentFile[]; + }; +} diff --git a/test/features/clone-tags/src/controllers/BbsArticlesController.ts b/test/features/clone-tags/src/controllers/BbsArticlesController.ts new file mode 100644 index 000000000..038ecc884 --- /dev/null +++ b/test/features/clone-tags/src/controllers/BbsArticlesController.ts @@ -0,0 +1,82 @@ +import { TypedBody, TypedParam, TypedRoute } from "@nestia/core"; +import { Controller } from "@nestjs/common"; +import { ApiSecurity, ApiTags } from "@nestjs/swagger"; +import typia, { tags } from "typia"; + +import { IBbsArticle } from "@api/lib/structures/IBbsArticle"; + +@ApiTags("bbs") +@Controller("bbs/articles/:section") +export class BbsArticlesController { + /** + * Would be shown without any mark. + * + * @param section Section code + * @param input Content to store + * @returns Newly archived article + * + * @tag public Some description describing public group... + * @tag write Write accessor + * @summary Public API + * @security bearer + * @security oauth2 read write + */ + @TypedRoute.Post() + public async store( + @TypedParam("section") section: string, + @TypedBody() input: IBbsArticle.IStore, + ): Promise { + return { + ...typia.random(), + ...input, + section, + }; + } + + /** + * Deprecated API. + * + * Would be marked as "deprecated". + * + * For reference, top sentence "Deprecated API." can replace the `@summary` tag. + * + * @param section Section code + * @param id Target article ID + * @param input Content to update + * @returns Updated content + * + * @deprecated + * @operationId updateArticle + * @security basic + * @security bearer + */ + @ApiTags("public", "write") + @TypedRoute.Put(":id") + public async update( + @TypedParam("section") section: string, + @TypedParam("id") id: string & tags.Format<"uuid">, + @TypedBody() input: IBbsArticle.IStore, + ): Promise { + return { + ...typia.random(), + ...input, + id, + section, + }; + } + + /** + * Would not be shown. + * + * @internal + */ + @ApiSecurity("custom") // LEGACY DECORATOR ALSO CAN BE USED + @TypedRoute.Delete(":id") + public erase( + @TypedParam("section") section: string, + @TypedParam("id") id: string & tags.Format<"uuid">, + ): void { + section; + id; + } +} diff --git a/test/features/clone-tags/src/controllers/TransactionController.ts b/test/features/clone-tags/src/controllers/TransactionController.ts new file mode 100644 index 000000000..32fe32638 --- /dev/null +++ b/test/features/clone-tags/src/controllers/TransactionController.ts @@ -0,0 +1,35 @@ +import { TypedParam, TypedQuery, TypedRoute } from "@nestia/core"; +import { Controller } from "@nestjs/common"; +import typia from "typia"; + +export type RegexPattern = typia.tags.TagBase<{ + target: "string"; + kind: "regexPattern"; + value: Value; + validate: `RegExp(${Value}).test($input)`; + schema: {}; +}>; + +export type LengthDivisibleBy = typia.tags.TagBase<{ + target: "string"; + kind: "lengthDivisibleBy"; + value: Value; + validate: `$input.length % ${Value} === 0`; +}>; + +export type PubkeyInput = string & + RegexPattern<"/^[0-9a-fA-F]+$/"> & + LengthDivisibleBy<2>; + +@Controller("v0/transaction") +export class TransactionController { + constructor() {} + + @TypedRoute.Get("user/:pubkey") + async findTransactionsByUser( + @TypedParam("pubkey") + pubkey: PubkeyInput, + ): Promise { + pubkey; + } +} diff --git a/test/features/clone-tags/src/test/features/test_swagger.ts b/test/features/clone-tags/src/test/features/test_swagger.ts new file mode 100644 index 000000000..8e8fa6220 --- /dev/null +++ b/test/features/clone-tags/src/test/features/test_swagger.ts @@ -0,0 +1,11 @@ +import { TestValidator } from "@nestia/e2e"; + +export const test_swagger = async (): Promise => { + const swagger = await import("../../../swagger.json"); + TestValidator.equals("tags of store()")( + swagger.paths["/bbs/articles/{section}"].post.tags, + )(["bbs", "public", "write"]); + TestValidator.equals("tags of update()")( + swagger.paths["/bbs/articles/{section}/{id}"].put.tags, + )(["bbs", "public", "write"]); +}; diff --git a/test/features/clone-tags/src/test/index.ts b/test/features/clone-tags/src/test/index.ts new file mode 100644 index 000000000..7e8f43d54 --- /dev/null +++ b/test/features/clone-tags/src/test/index.ts @@ -0,0 +1,47 @@ +import { DynamicExecutor } from "@nestia/e2e"; + +import { Backend } from "../Backend"; + +async function main(): Promise { + const server: Backend = new Backend(); + await server.open(); + + const report: DynamicExecutor.IReport = await DynamicExecutor.validate({ + extension: __filename.substring(__filename.length - 2), + prefix: "test", + parameters: () => [ + { + host: "http://127.0.0.1:37000", + encryption: { + key: "A".repeat(32), + iv: "B".repeat(16), + }, + }, + ], + location: `${__dirname}/features`, + onComplete: (exec) => { + const elapsed: number = + new Date(exec.completed_at).getTime() - + new Date(exec.started_at).getTime(); + console.log(` - ${exec.name}: ${elapsed.toLocaleString()} ms`); + }, + }); + await server.close(); + + const exceptions: Error[] = report.executions + .filter((exec) => exec.error !== null) + .map((exec) => exec.error!); + if (exceptions.length === 0) { + console.log("Success"); + console.log("Elapsed time", report.time.toLocaleString(), `ms`); + } else { + for (const exp of exceptions) console.log(exp); + console.log("Failed"); + console.log("Elapsed time", report.time.toLocaleString(), `ms`); + process.exit(-1); + } +} +main().catch((exp) => { + console.log(exp); + process.exit(-1); +}); diff --git a/test/features/clone-tags/swagger.json b/test/features/clone-tags/swagger.json new file mode 100644 index 000000000..6131fa395 --- /dev/null +++ b/test/features/clone-tags/swagger.json @@ -0,0 +1,310 @@ +{ + "openapi": "3.1.0", + "servers": [ + { + "url": "https://github.com/samchon/nestia", + "description": "insert your server url" + } + ], + "info": { + "version": "4.0.4", + "title": "@samchon/nestia-test", + "description": "Test program of Nestia", + "license": { + "name": "MIT" + } + }, + "paths": { + "/bbs/articles/{section}": { + "post": { + "summary": "Public API", + "description": "Would be shown without any mark.", + "tags": [ + "bbs", + "public", + "write" + ], + "operationId": "BbsArticlesController.store", + "parameters": [ + { + "name": "section", + "in": "path", + "schema": { + "type": "string" + }, + "required": true, + "description": " Section code" + } + ], + "requestBody": { + "description": "Content to store", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/IBbsArticle.IStore" + } + } + }, + "required": true + }, + "responses": { + "201": { + "description": "Newly archived article", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/IBbsArticle" + } + } + } + } + }, + "security": [ + { + "bearer": [] + }, + { + "oauth2": [ + "read", + "write" + ] + } + ] + } + }, + "/bbs/articles/{section}/{id}": { + "put": { + "summary": "Deprecated API", + "description": "Deprecated API.\n\nWould be marked as \"deprecated\".\n\nFor reference, top sentence \"Deprecated API.\" can replace the `@summary` tag.", + "deprecated": true, + "tags": [ + "bbs", + "public", + "write" + ], + "operationId": "updateArticle", + "parameters": [ + { + "name": "section", + "in": "path", + "schema": { + "type": "string" + }, + "required": true, + "description": " Section code" + }, + { + "name": "id", + "in": "path", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true, + "description": " Target article ID" + } + ], + "requestBody": { + "description": "Content to update", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/IBbsArticle.IStore" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Updated content", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/IBbsArticle" + } + } + } + } + }, + "security": [ + { + "basic": [] + }, + { + "bearer": [] + } + ] + } + }, + "/v0/transaction/user/{pubkey}": { + "get": { + "tags": [], + "operationId": "TransactionController.findTransactionsByUser", + "parameters": [ + { + "name": "pubkey", + "in": "path", + "schema": { + "type": "string" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": {} + } + } + } + } + } + }, + "components": { + "schemas": { + "IBbsArticle": { + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "section": { + "type": "string" + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "title": { + "type": "string", + "minLength": 3, + "maxLength": 50 + }, + "body": { + "type": "string" + }, + "files": { + "type": "array", + "items": { + "$ref": "#/components/schemas/IAttachmentFile" + } + } + }, + "required": [ + "id", + "section", + "created_at", + "title", + "body", + "files" + ] + }, + "IAttachmentFile": { + "type": "object", + "properties": { + "name": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string", + "maxLength": 255 + } + ] + }, + "extension": { + "oneOf": [ + { + "type": "null" + }, + { + "type": "string", + "minLength": 1, + "maxLength": 8 + } + ] + }, + "url": { + "type": "string", + "format": "uri" + } + }, + "required": [ + "name", + "extension", + "url" + ] + }, + "IBbsArticle.IStore": { + "type": "object", + "properties": { + "title": { + "type": "string", + "minLength": 3, + "maxLength": 50 + }, + "body": { + "type": "string" + }, + "files": { + "type": "array", + "items": { + "$ref": "#/components/schemas/IAttachmentFile" + } + } + }, + "required": [ + "title", + "body", + "files" + ] + } + }, + "securitySchemes": { + "basic": { + "type": "http", + "scheme": "basic" + }, + "bearer": { + "type": "http", + "scheme": "bearer" + }, + "oauth2": { + "type": "oauth2", + "flows": { + "implicit": { + "authorizationUrl": "https://example.com/api/oauth/dialog", + "refreshUrl": "https://example.com/api/oauth/refresh", + "scopes": { + "read": "read authority", + "write": "write authority" + } + } + } + }, + "custom": { + "type": "apiKey", + "in": "header", + "name": "Authorization" + } + } + }, + "tags": [ + { + "name": "bbs" + }, + { + "name": "public", + "description": "Some description describing public group..." + }, + { + "name": "write", + "description": "Write accessor" + } + ], + "x-samchon-emended": true +} \ No newline at end of file diff --git a/test/features/clone-tags/tsconfig.json b/test/features/clone-tags/tsconfig.json new file mode 100644 index 000000000..ef75df78e --- /dev/null +++ b/test/features/clone-tags/tsconfig.json @@ -0,0 +1,98 @@ +{ + "compilerOptions": { + /* Visit https://aka.ms/tsconfig to read more about this file */ + /* Projects */ + // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ + // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ + // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ + // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ + // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ + // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ + /* Language and Environment */ + "target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ + // "jsx": "preserve", /* Specify what JSX code is generated. */ + "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */ + "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ + // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ + // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ + // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ + // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ + // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ + // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ + // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ + /* Modules */ + "module": "commonjs", /* Specify what module code is generated. */// "rootDir": "./", /* Specify the root folder within your source files. */ + // "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */ + // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ + "paths": { + "@api": ["./src/api"], + "@api/lib/*": ["./src/api/*"], + }, /* Specify a set of entries that re-map imports to additional lookup locations. */ + // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ + // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ + // "types": [], /* Specify type package names to be included without being referenced in a source file. */ + // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ + "resolveJsonModule": true, /* Enable importing .json files. */ + // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ + /* JavaScript Support */ + // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ + // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ + // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ + /* Emit */ + // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ + // "declarationMap": true, /* Create sourcemaps for d.ts files. */ + // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ + // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ + // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ + // "outDir": "./", /* Specify an output folder for all emitted files. */ + // "removeComments": true, /* Disable emitting comments. */ + "noEmit": true, /* Disable emitting files from a compilation. */ + // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ + // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ + // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ + // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ + // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ + // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ + // "newLine": "crlf", /* Set the newline character for emitting files. */ + // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ + // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ + // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ + // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ + // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ + // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ + /* Interop Constraints */ + // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ + // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ + "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ + "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. *//* Type Checking */ + "strict": true, /* Enable all strict type-checking options. */// "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ + // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ + // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ + // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ + // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ + // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ + // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ + // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ + // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ + // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ + // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ + // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ + // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ + // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ + // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ + // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ + // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ + // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ + /* Completeness */ + // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ + "skipLibCheck": true, /* Skip type checking all .d.ts files. */ + "plugins": [ + { "transform": "typescript-transform-paths" }, + { "transform": "typia/lib/transform" }, + { "transform": "@nestia/core/lib/transform" }, + ], + } + } \ No newline at end of file diff --git a/test/features/tags/src/api/functional/index.ts b/test/features/tags/src/api/functional/index.ts index 26113199a..9a297de8a 100644 --- a/test/features/tags/src/api/functional/index.ts +++ b/test/features/tags/src/api/functional/index.ts @@ -5,3 +5,4 @@ */ //================================================================ export * as bbs from "./bbs"; +export * as transaction from "./transaction"; diff --git a/test/features/tags/src/api/functional/transaction/index.ts b/test/features/tags/src/api/functional/transaction/index.ts new file mode 100644 index 000000000..b3dbd5d26 --- /dev/null +++ b/test/features/tags/src/api/functional/transaction/index.ts @@ -0,0 +1,7 @@ +/** + * @packageDocumentation + * @module api.functional.transaction + * @nestia Generated by Nestia - https://github.com/samchon/nestia + */ +//================================================================ +export * as user from "./user"; diff --git a/test/features/tags/src/api/functional/transaction/user/index.ts b/test/features/tags/src/api/functional/transaction/user/index.ts new file mode 100644 index 000000000..fc774f503 --- /dev/null +++ b/test/features/tags/src/api/functional/transaction/user/index.ts @@ -0,0 +1,41 @@ +/** + * @packageDocumentation + * @module api.functional.transaction.user + * @nestia Generated by Nestia - https://github.com/samchon/nestia + */ +//================================================================ +import type { IConnection } from "@nestia/fetcher"; +import { PlainFetcher } from "@nestia/fetcher/lib/PlainFetcher"; + +import type { PubkeyInput } from "../../../../controllers/TransactionController"; + +/** + * @controller TransactionController.findTransactionsByUser + * @path GET /transaction/user/:pubkey + * @nestia Generated by Nestia - https://github.com/samchon/nestia + */ +export async function findTransactionsByUser( + connection: IConnection, + pubkey: PubkeyInput, +): Promise { + return PlainFetcher.fetch(connection, { + ...findTransactionsByUser.METADATA, + template: findTransactionsByUser.METADATA.path, + path: findTransactionsByUser.path(pubkey), + }); +} +export namespace findTransactionsByUser { + export const METADATA = { + method: "GET", + path: "/transaction/user/:pubkey", + request: null, + response: { + type: "application/json", + encrypted: false, + }, + status: 200, + } as const; + + export const path = (pubkey: PubkeyInput) => + `/transaction/user/${encodeURIComponent(pubkey?.toString() ?? "null")}`; +} diff --git a/test/features/tags/src/controllers/TransactionController.ts b/test/features/tags/src/controllers/TransactionController.ts new file mode 100644 index 000000000..04e0c927a --- /dev/null +++ b/test/features/tags/src/controllers/TransactionController.ts @@ -0,0 +1,35 @@ +import { TypedParam, TypedQuery, TypedRoute } from "@nestia/core"; +import { Controller } from "@nestjs/common"; +import typia from "typia"; + +export type RegexPattern = typia.tags.TagBase<{ + target: "string"; + kind: "regexPattern"; + value: Value; + validate: `RegExp(${Value}).test($input)`; + schema: {}; +}>; + +export type LengthDivisibleBy = typia.tags.TagBase<{ + target: "string"; + kind: "lengthDivisibleBy"; + value: Value; + validate: `$input.length % ${Value} === 0`; +}>; + +export type PubkeyInput = string & + RegexPattern<"/^[0-9a-fA-F]+$/"> & + LengthDivisibleBy<2>; + +@Controller("transaction") +export class TransactionController { + constructor() {} + + @TypedRoute.Get("user/:pubkey") + async findTransactionsByUser( + @TypedParam("pubkey") + pubkey: PubkeyInput, + ): Promise { + pubkey; + } +} diff --git a/test/features/tags/swagger.json b/test/features/tags/swagger.json index afb094a53..5c68638be 100644 --- a/test/features/tags/swagger.json +++ b/test/features/tags/swagger.json @@ -136,6 +136,30 @@ } ] } + }, + "/transaction/user/{pubkey}": { + "get": { + "tags": [], + "operationId": "TransactionController.findTransactionsByUser", + "parameters": [ + { + "name": "pubkey", + "in": "path", + "schema": { + "type": "string" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": {} + } + } + } + } } }, "components": { diff --git a/website/pages/docs/sdk/sdk.mdx b/website/pages/docs/sdk/sdk.mdx index b9f38451c..ccf34de30 100644 --- a/website/pages/docs/sdk/sdk.mdx +++ b/website/pages/docs/sdk/sdk.mdx @@ -1187,8 +1187,8 @@ Also, if your SDK library utilize special alias `paths`, you also need to custom "ts-patch": "^3.1.0" }, "dependencies": { - "@nestia/fetcher": "^4.0.0", - "typia": "^7.1.0" + "@nestia/fetcher": "^4.2.0", + "typia": "^7.3.0" }, "files": [ "lib",