@nestia/sdk
Version:
Nestia SDK and Swagger generator
160 lines (146 loc) • 5.24 kB
text/typescript
import fs from "fs";
import NodePath from "path";
import { IPointer } from "tstl";
import { INestiaConfig } from "../INestiaConfig";
import { IReflectOperationError } from "../structures/IReflectOperationError";
import { IReflectType } from "../structures/IReflectType";
import { ITypedApplication } from "../structures/ITypedApplication";
import { ITypedHttpRoute } from "../structures/ITypedHttpRoute";
import { CloneGenerator } from "./CloneGenerator";
import { SdkDistributionComposer } from "./internal/SdkDistributionComposer";
import { SdkFileProgrammer } from "./internal/SdkFileProgrammer";
export namespace SdkGenerator {
export const generate = async (app: ITypedApplication): Promise<void> => {
if (app.project.config.output === undefined)
throw new Error("Output directory is not defined.");
// PREPARE NEW DIRECTORIES
console.log("Generating SDK Library");
try {
await fs.promises.mkdir(app.project.config.output);
} catch {}
// BUNDLING
const bundle: string[] = await fs.promises.readdir(BUNDLE_PATH);
for (const file of bundle) {
const current: string = `${BUNDLE_PATH}/${file}`;
const target: string = `${app.project.config.output}/${file}`;
const stats: fs.Stats = await fs.promises.stat(current);
if (stats.isFile() === true) {
const content: string = await fs.promises.readFile(current, "utf8");
if (fs.existsSync(target) === false)
await fs.promises.writeFile(target, content, "utf8");
else if (BUNDLE_CHANGES[file] !== undefined) {
const r: IPointer<string> = {
value: await fs.promises.readFile(target, "utf8"),
};
for (const [before, after] of BUNDLE_CHANGES[file])
r.value = r.value.replace(before, after);
await fs.promises.writeFile(target, r.value, "utf8");
}
}
}
// STRUCTURES
if (app.project.config.clone === true) await CloneGenerator.write(app);
// FUNCTIONAL
await SdkFileProgrammer.generate(app);
// DISTRIBUTION
if (app.project.config.distribute !== undefined)
await SdkDistributionComposer.compose({
config: app.project.config,
websocket: app.routes.some((r) => r.protocol === "websocket"),
});
};
export const validate = (
app: ITypedApplication,
): IReflectOperationError[] => {
const errors: IReflectOperationError[] = [];
if (app.project.config.clone === true) return errors;
for (const route of app.routes)
if (route.protocol === "http")
validateImplicit({
config: app.project.config,
errors,
route,
});
return errors;
};
const validateImplicit = (props: {
config: INestiaConfig;
errors: IReflectOperationError[];
route: ITypedHttpRoute;
}): void => {
for (const p of props.route.parameters) {
if (isImplicitType(p.type))
props.errors.push({
file: props.route.controller.file,
class: props.route.controller.class.name,
function: props.route.name,
from: `parameter ${JSON.stringify(p.name)}`,
contents: [`implicit (unnamed) parameter type.`],
});
}
if (props.config.propagate === true)
for (const [key, value] of Object.entries(props.route.exceptions))
if (isImplicitType(value.type))
props.errors.push({
file: props.route.controller.file,
class: props.route.controller.class.name,
function: props.route.name,
from: `exception ${JSON.stringify(key)}`,
contents: [`implicit (unnamed) exception type.`],
});
if (isImplicitType(props.route.success.type))
props.errors.push({
file: props.route.controller.file,
class: props.route.controller.class.name,
function: props.route.name,
from: "success",
contents: [`implicit (unnamed) return type.`],
});
};
const isImplicitType = (type: IReflectType): boolean =>
type.name === "__type" ||
type.name === "__object" ||
type.name.startsWith("__type.") ||
type.name.startsWith("__object.") ||
type.name.includes("readonly [") ||
(!!type.typeArguments?.length && type.typeArguments.some(isImplicitType));
export const BUNDLE_PATH = NodePath.join(
__dirname,
"..",
"..",
"assets",
"bundle",
"api",
);
}
const BUNDLE_CHANGES: Record<string, [string, string][]> = {
"IConnection.ts": [
[
`export { IConnection } from "@nestia/fetcher"`,
`export type { IConnection } from "@nestia/fetcher"`,
],
],
"module.ts": [
[`export * from "./IConnection"`, `export type * from "./IConnection"`],
],
"Primitive.ts": [
[
`export { Primitive } from "@nestia/fetcher"`,
`export type { Primitive } from "typia"`,
],
[
`export type { Primitive } from "@nestia/fetcher"`,
`export type { Primitive } from "typia"`,
],
],
"Resolved.ts": [
[
`export { Resolved } from "@nestia/fetcher"`,
`export type { Resolved } from "typia"`,
],
[
`export type { Resolved } from "@nestia/fetcher"`,
`export type { Resolved } from "typia"`,
],
],
};