@nestia/sdk
Version:
Nestia SDK and Swagger generator
281 lines (265 loc) • 8.69 kB
text/typescript
import {
SyntaxKind,
type TypeElement,
type TypeNode,
factory,
} from "@ttsc/factory";
import { TypeFactory } from "../../factories/TypeFactory";
import { MetadataSchema, sizeOf } from "../../internal/legacy";
import { INestiaProject } from "../../structures/INestiaProject";
import { IReflectType } from "../../structures/IReflectType";
import { ITypedHttpRoute } from "../../structures/ITypedHttpRoute";
import { ITypedHttpRouteParameter } from "../../structures/ITypedHttpRouteParameter";
import { ITypedWebSocketRoute } from "../../structures/ITypedWebSocketRoute";
import { FilePrinter } from "./FilePrinter";
import { ImportDictionary } from "./ImportDictionary";
import { SdkHttpParameterProgrammer } from "./SdkHttpParameterProgrammer";
import { SdkTypeProgrammer } from "./SdkTypeProgrammer";
export namespace SdkAliasCollection {
export const name = ({ type }: { type: IReflectType }): TypeNode =>
factory.createTypeReferenceNode(
type.name,
type.typeArguments
? type.typeArguments.map((a) => name({ type: a }))
: undefined,
);
export const binaryChunk = (): TypeNode =>
factory.createTypeReferenceNode("Uint8Array", [
factory.createTypeReferenceNode("ArrayBufferLike"),
]);
export const binaryResponse = (): TypeNode =>
factory.createTypeReferenceNode("ReadableStream", [binaryChunk()]);
export const from =
(project: INestiaProject) =>
(importer: ImportDictionary) =>
(metadata: MetadataSchema): TypeNode =>
SdkTypeProgrammer.write(project)(importer)(metadata) as TypeNode;
export const httpProps =
(project: INestiaProject) =>
(importer: ImportDictionary) =>
(route: ITypedHttpRoute): TypeNode =>
factory.createTypeLiteralNode(
SdkHttpParameterProgrammer.getEntries({
project,
importer,
route,
body: true,
prefix: false,
})
.map((e) => {
const signature: TypeElement = factory.createPropertySignature(
undefined,
e.key,
e.required
? undefined
: factory.createToken(SyntaxKind.QuestionToken),
e.type,
);
const description: string | null =
e.parameter.description ??
route.jsDocTags
?.find(
(tag) =>
tag.name === "param" &&
tag.text?.[0]?.kind === "parameterName" &&
tag.text?.[0]?.text === e.key,
)
?.text?.find((t) => t.kind === "text")?.text ??
null;
return description?.length
? [
factory.createIdentifier("\n") as any,
FilePrinter.description(signature, description),
]
: [signature];
})
.flat(),
);
export const websocketProps = (route: ITypedWebSocketRoute): TypeNode =>
factory.createTypeLiteralNode([
...route.pathParameters.map((p) =>
factory.createPropertySignature(
undefined,
p.name,
undefined,
SdkAliasCollection.name(p),
),
),
...(route.query
? [
factory.createPropertySignature(
undefined,
"query",
undefined,
factory.createTypeReferenceNode("Query"),
),
]
: []),
factory.createPropertySignature(
undefined,
"provider",
undefined,
factory.createTypeReferenceNode("Provider"),
),
]);
export const headers =
(project: INestiaProject) =>
(importer: ImportDictionary) =>
(param: ITypedHttpRouteParameter.IHeaders): TypeNode => {
if (project.config.clone === true)
return from(project)(importer)(param.metadata);
const type: TypeNode = name(param);
if (project.config.primitive === false) return type;
return factory.createTypeReferenceNode(
importer.external({
file: "typia",
declaration: true,
type: "element",
name: "Resolved",
}),
[type],
);
};
export const query =
(project: INestiaProject) =>
(importer: ImportDictionary) =>
(param: ITypedHttpRouteParameter.IQuery): TypeNode => {
if (project.config.clone === true)
return from(project)(importer)(param.metadata);
const type: TypeNode = name(param);
if (project.config.primitive === false) return type;
return factory.createTypeReferenceNode(
importer.external({
file: "typia",
declaration: true,
type: "element",
name: "Resolved",
}),
[type],
);
};
export const body =
(project: INestiaProject) =>
(importer: ImportDictionary) =>
(param: ITypedHttpRouteParameter.IBody): TypeNode => {
if (project.config.clone === true) {
const type: TypeNode = from(project)(importer)(param.metadata);
return param.contentType === "multipart/form-data"
? formDataInput(importer)(type)
: type;
}
const type: TypeNode = name(param);
if (param.contentType === "multipart/form-data")
return formDataInput(importer)(type);
else if (project.config.primitive === false) return type;
return factory.createTypeReferenceNode(
importer.external({
file: "typia",
declaration: true,
type: "element",
name:
param.contentType === "application/json" || param.encrypted === true
? "Primitive"
: "Resolved",
}),
[type],
);
};
export const response =
(project: INestiaProject) =>
(importer: ImportDictionary) =>
(route: ITypedHttpRoute): TypeNode => {
const schema = (p: {
metadata: MetadataSchema;
type: IReflectType;
}): TypeNode =>
sizeOf(p.metadata) === 0
? TypeFactory.keyword("void")
: project.config.clone === true
? from(project)(importer)(p.metadata)
: project.config.primitive !== false
? factory.createTypeReferenceNode(
importer.external({
file: "typia",
declaration: true,
type: "element",
name:
route.success.contentType === "application/json" ||
route.success.encrypted === true
? "Primitive"
: "Resolved",
}),
[name(p)],
)
: name(p);
const success: TypeNode =
route.success.binary === true
? binaryResponse()
: schema(route.success);
if (project.config.propagate !== true) return success;
const branches: IBranch[] = [
{
status: String(
route.success.status ?? (route.method === "POST" ? 201 : 200),
),
type: success,
},
...Object.entries(route.exceptions).map(([status, value]) => ({
status,
type: schema(value),
})),
];
return factory.createTypeReferenceNode(
importer.external({
file: "@nestia/fetcher",
declaration: true,
type: "element",
name: "IPropagation",
}),
[
factory.createTypeLiteralNode(
branches.map((b) =>
factory.createPropertySignature(
undefined,
factory.createNumericLiteral(b.status),
undefined,
b.type,
),
),
),
...(route.success.status
? [
factory.createLiteralTypeNode(
factory.createNumericLiteral(route.success.status),
),
]
: []),
],
);
};
export const responseBody =
(project: INestiaProject) =>
(importer: ImportDictionary) =>
(route: ITypedHttpRoute): TypeNode =>
response({
...project,
config: {
...project.config,
propagate: false,
},
})(importer)(route);
const formDataInput = (importer: ImportDictionary) => (type: TypeNode) =>
factory.createTypeReferenceNode(
importer.external({
file: "@nestia/fetcher",
declaration: true,
type: "element",
name: "FormDataInput",
}),
[type],
);
}
interface IBranch {
status: string;
type: TypeNode;
}