Skip to content

Commit

Permalink
Merge pull request #888 from samchon/features/ws
Browse files Browse the repository at this point in the history
Close #887: SDK generator for `@WebSocketRoute()`
  • Loading branch information
samchon authored Apr 29, 2024
2 parents 5e57cbf + 333902f commit 5de4481
Show file tree
Hide file tree
Showing 152 changed files with 2,883 additions and 2,196 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"private": true,
"name": "@nestia/station",
"version": "3.0.5",
"version": "3.1.0-dev.20240426",
"description": "Nestia station",
"scripts": {
"build": "node build/index.js",
Expand Down
8 changes: 4 additions & 4 deletions packages/core/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@nestia/core",
"version": "3.0.5",
"version": "3.1.0-dev.20240426",
"description": "Super-fast validation decorators of NestJS",
"main": "lib/index.js",
"typings": "lib/index.d.ts",
Expand Down Expand Up @@ -36,7 +36,7 @@
},
"homepage": "https://nestia.io",
"dependencies": {
"@nestia/fetcher": "^3.0.5",
"@nestia/fetcher": "D:\\github\\samchon\\nestia\\packages\\fetcher\\nestia-fetcher-3.1.0-dev.20240426.tgz",
"@nestjs/common": ">=7.0.1",
"@nestjs/core": ">=7.0.1",
"@samchon/openapi": "^0.1.18",
Expand All @@ -47,12 +47,12 @@
"raw-body": "^2.0.0",
"reflect-metadata": ">=0.1.12",
"rxjs": ">=6.0.3",
"tgrid": "^0.10.3",
"tgrid": "^0.11.0",
"typia": "^6.0.3",
"ws": "^7.5.3"
},
"peerDependencies": {
"@nestia/fetcher": ">=3.0.5",
"@nestia/fetcher": ">=3.1.0-dev.20240426",
"@nestjs/common": ">=7.0.1",
"@nestjs/core": ">=7.0.1",
"reflect-metadata": ">=0.1.12",
Expand Down
12 changes: 7 additions & 5 deletions packages/core/src/adaptors/WebSocketAdaptor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -281,9 +281,11 @@ const visitMethod = (props: {
set.size === 0
? getVersions(props.config.versioning.defaultVersion)
: Array.from(set);
return array.map((v) =>
typeof v === "symbol" ? "" : `${props.config.versioning!.prefix}${v}`,
);
return array.length === 0
? [""]
: array.map((v) =>
typeof v === "symbol" ? "" : `${props.config.versioning!.prefix}${v}`,
);
})();
for (const v of versions)
for (const cp of props.controller.prefixes)
Expand Down Expand Up @@ -332,7 +334,7 @@ const visitMethod = (props: {
parser,
handler: async (input: {
params: Record<string, string>;
acceptor: WebAcceptor<any, any>;
acceptor: WebAcceptor<any, any, any>;
}): Promise<void> => {
const args: any[] = [];
try {
Expand Down Expand Up @@ -395,7 +397,7 @@ interface IOperator {
parser: Path;
handler: (props: {
params: Record<string, string>;
acceptor: WebAcceptor<any, any>;
acceptor: WebAcceptor<any, any, any>;
}) => Promise<any>;
}
interface IConfig {
Expand Down
2 changes: 1 addition & 1 deletion packages/fetcher/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@nestia/fetcher",
"version": "3.0.5",
"version": "3.1.0-dev.20240426",
"description": "Fetcher library of Nestia SDK",
"main": "lib/index.js",
"typings": "lib/index.d.ts",
Expand Down
13 changes: 7 additions & 6 deletions packages/sdk/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@nestia/sdk",
"version": "3.0.5",
"version": "3.1.0-dev.20240426",
"description": "Nestia SDK and Swagger generator",
"main": "lib/index.js",
"typings": "lib/index.d.ts",
Expand Down Expand Up @@ -32,21 +32,21 @@
},
"homepage": "https://nestia.io",
"dependencies": {
"@nestia/fetcher": "^3.0.5",
"@nestia/fetcher": "D:\\github\\samchon\\nestia\\packages\\fetcher\\nestia-fetcher-3.1.0-dev.20240426.tgz",
"@samchon/openapi": "^0.1.18",
"cli": "^1.0.1",
"get-function-location": "^2.0.0",
"glob": "^7.2.0",
"path-to-regexp": "^6.2.1",
"prettier": "^3.2.5",
"ts-node": ">=10.6.0",
"tsconfck": "^2.0.1",
"tsconfig-paths": "^4.1.1",
"ts-node": ">=10.6.0",
"tstl": "^3.0.0",
"typia": "^6.0.3"
},
"peerDependencies": {
"@nestia/fetcher": ">=3.0.5",
"@nestia/fetcher": ">=3.1.0-dev.20240426",
"@nestjs/common": ">=7.0.1",
"@nestjs/core": ">=7.0.1",
"reflect-metadata": ">=0.1.12",
Expand All @@ -66,8 +66,9 @@
"eslint": "^8.29.0",
"eslint-plugin-deprecation": "^1.4.1",
"rimraf": "^3.0.2",
"ts-patch": "v3.0.2",
"typescript": "^5.4.2",
"tgrid": "^0.11.0",
"ts-patch": "^3.1.2",
"typescript": "^5.4.5",
"typescript-transform-paths": "^3.4.4",
"uuid": "^9.0.0"
},
Expand Down
70 changes: 36 additions & 34 deletions packages/sdk/src/NestiaSdkApplication.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,16 @@ import ts from "typescript";
import { INestiaConfig } from "./INestiaConfig";
import { AccessorAnalyzer } from "./analyses/AccessorAnalyzer";
import { ConfigAnalyzer } from "./analyses/ConfigAnalyzer";
import { ControllerAnalyzer } from "./analyses/ControllerAnalyzer";
import { ReflectAnalyzer } from "./analyses/ReflectAnalyzer";
import { ReflectControllerAnalyzer } from "./analyses/ReflectControllerAnalyzer";
import { TypedControllerAnalyzer } from "./analyses/TypedControllerAnalyzer";
import { E2eGenerator } from "./generates/E2eGenerator";
import { SdkGenerator } from "./generates/SdkGenerator";
import { SwaggerGenerator } from "./generates/SwaggerGenerator";
import { IController } from "./structures/IController";
import { IErrorReport } from "./structures/IErrorReport";
import { INestiaProject } from "./structures/INestiaProject";
import { IRoute } from "./structures/IRoute";
import { IReflectController } from "./structures/IReflectController";
import { ITypedHttpRoute } from "./structures/ITypedHttpRoute";
import { ITypedWebSocketRoute } from "./structures/ITypedWebSocketRoute";
import { MapUtil } from "./utils/MapUtil";

export class NestiaSdkApplication {
Expand Down Expand Up @@ -46,14 +47,12 @@ export class NestiaSdkApplication {
await validate("e2e")(this.config.e2e);

print_title("Nestia E2E Generator");
await this.generate(
"e2e",
(config) => config,
(checker) => (config) => async (routes) => {
await SdkGenerator.generate(checker)(config)(routes);
await E2eGenerator.generate(checker)(config)(routes);
},
);
await this.generate("e2e", (project) => async (routes) => {
await SdkGenerator.generate(project)(routes);
await E2eGenerator.generate(project)(
routes.filter((r) => r.protocol === "http") as ITypedHttpRoute[],
);
});
}

public async sdk(): Promise<void> {
Expand All @@ -70,7 +69,7 @@ export class NestiaSdkApplication {
);

print_title("Nestia SDK Generator");
await this.generate("sdk", (config) => config, SdkGenerator.generate);
await this.generate("sdk", SdkGenerator.generate);
}

public async swagger(): Promise<void> {
Expand All @@ -90,25 +89,22 @@ export class NestiaSdkApplication {
);

print_title("Nestia Swagger Generator");
await this.generate(
"swagger",
(config) => config.swagger!,
SwaggerGenerator.generate,
);
await this.generate("swagger", SwaggerGenerator.generate);
}

private async generate<Config>(
private async generate(
method: string,
config: (entire: INestiaConfig) => Config,
archiver: (
checker: ts.TypeChecker,
) => (config: Config) => (routes: IRoute[]) => Promise<void>,
project: INestiaProject,
) => (
routes: Array<ITypedHttpRoute | ITypedWebSocketRoute>,
) => Promise<void>,
): Promise<void> {
//----
// ANALYZE REFLECTS
//----
const unique: WeakSet<any> = new WeakSet();
const controllers: IController[] = [];
const controllers: IReflectController[] = [];
const project: INestiaProject = {
config: this.config,
input: await ConfigAnalyzer.input(this.config),
Expand All @@ -120,7 +116,7 @@ export class NestiaSdkApplication {
console.log("Analyzing reflections");
for (const include of (await ConfigAnalyzer.input(this.config)).include)
controllers.push(
...(await ReflectAnalyzer.analyze(project)(
...(await ReflectControllerAnalyzer.analyze(project)(
unique,
include.file,
include.paths,
Expand All @@ -132,9 +128,11 @@ export class NestiaSdkApplication {
const set: Set<string> = new Set();
for (const c of controllers)
for (const cPath of c.paths)
for (const f of c.functions)
for (const fPath of f.paths)
set.add(`${f.method}::${cPath}/${fPath}`);
for (const op of c.operations)
for (const fPath of op.paths)
set.add(
`${op.protocol === "http" ? `${op.method}::` : ""}${cPath}/${fPath}`,
);
return set.size;
})();

Expand All @@ -145,7 +143,7 @@ export class NestiaSdkApplication {
.map(
(c) =>
c.paths.length *
c.functions.map((f) => f.paths.length).reduce((a, b) => a + b, 0),
c.operations.map((f) => f.paths.length).reduce((a, b) => a + b, 0),
)
.reduce((a, b) => a + b, 0)}`,
);
Expand All @@ -161,11 +159,13 @@ export class NestiaSdkApplication {
);
project.checker = program.getTypeChecker();

const routeList: IRoute[] = [];
const routeList: Array<ITypedHttpRoute | ITypedWebSocketRoute> = [];
for (const c of controllers) {
const file: ts.SourceFile | undefined = program.getSourceFile(c.file);
if (file === undefined) continue;
routeList.push(...(await ControllerAnalyzer.analyze(project)(file, c)));
routeList.push(
...(await TypedControllerAnalyzer.analyze(project)(file, c)),
);
}

// REPORT ERRORS
Expand All @@ -177,7 +177,9 @@ export class NestiaSdkApplication {

// FIND IMPLICIT TYPES
if (this.config.clone !== true) {
const implicit: IRoute[] = routeList.filter(is_implicit_return_typed);
const implicit: ITypedHttpRoute[] = routeList.filter(
(r) => r.protocol === "http" && is_implicit_return_typed(r),
) as ITypedHttpRoute[];
if (implicit.length > 0)
throw new Error(
`NestiaApplication.${method}(): implicit return type is not allowed.\n` +
Expand All @@ -186,15 +188,15 @@ export class NestiaSdkApplication {
implicit
.map(
(it) =>
` - ${it.target.class.name}.${it.target.function.name} at "${it.location}"`,
` - ${it.controller.name}.${it.name} at "${it.location}"`,
)
.join("\n"),
);
}

// DO GENERATE
AccessorAnalyzer.analyze(routeList);
await archiver(project.checker)(config(this.config))(routeList);
await archiver(project)(routeList);
}
}

Expand All @@ -204,7 +206,7 @@ const print_title = (str: string): void => {
console.log("-----------------------------------------------------------");
};

const is_implicit_return_typed = (route: IRoute): boolean => {
const is_implicit_return_typed = (route: ITypedHttpRoute): boolean => {
const name: string = route.output.typeName;
if (name === "void") return false;
else if (name.indexOf("readonly [") !== -1) return true;
Expand Down
17 changes: 12 additions & 5 deletions packages/sdk/src/analyses/AccessorAnalyzer.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
import { Escaper } from "typia/lib/utils/Escaper";

import { IRoute } from "../structures/IRoute";
import { ITypedHttpRoute } from "../structures/ITypedHttpRoute";
import { ITypedWebSocketRoute } from "../structures/ITypedWebSocketRoute";

export namespace AccessorAnalyzer {
export const analyze = (routes: IRoute[]) => {
export const analyze = (
routes: Array<ITypedHttpRoute | ITypedWebSocketRoute>,
) => {
shrink(routes);
variable(routes);
shrink(routes);
for (const r of routes) r.name = r.accessors.at(-1) ?? r.name;
};

const prepare = (routeList: IRoute[]): Map<string, number> => {
const prepare = (
routeList: Array<ITypedHttpRoute | ITypedWebSocketRoute>,
): Map<string, number> => {
const dict: Map<string, number> = new Map();
for (const route of routeList)
route.accessors.forEach((_a, i) => {
Expand All @@ -20,7 +25,9 @@ export namespace AccessorAnalyzer {
return dict;
};

const variable = (routeList: IRoute[]) => {
const variable = (
routeList: Array<ITypedHttpRoute | ITypedWebSocketRoute>,
) => {
const dict: Map<string, number> = prepare(routeList);
for (const route of routeList) {
const emended: string[] = route.accessors.slice();
Expand All @@ -42,7 +49,7 @@ export namespace AccessorAnalyzer {
}
};

const shrink = (routeList: IRoute[]) => {
const shrink = (routeList: Array<ITypedHttpRoute | ITypedWebSocketRoute>) => {
const dict: Map<string, number> = prepare(routeList);
for (const route of routeList) {
if (
Expand Down
Loading

0 comments on commit 5de4481

Please sign in to comment.