@nexica/nestjs-trpc
Version:
NestJS TRPC Bridge
339 lines (338 loc) • 15.1 kB
JavaScript
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.RouterGenerator = void 0;
const common_1 = require("@nestjs/common");
const common_2 = require("@nestjs/common");
const common_3 = require("@nestjs/common");
const fs = require("fs");
const path = require("path");
const ts_morph_1 = require("ts-morph");
const constants_1 = require("../constants");
const trpc_factory_1 = require("../factory/trpc.factory");
const server_1 = require("@trpc/server");
const schema_generator_1 = require("./schema-generator");
const error_handler_1 = require("../utils/error-handler");
let RouterGenerator = class RouterGenerator {
constructor(moduleCallerFilePath = process.cwd(), trpcFactory) {
Object.defineProperty(this, "moduleCallerFilePath", {
enumerable: true,
configurable: true,
writable: true,
value: moduleCallerFilePath
});
Object.defineProperty(this, "trpcFactory", {
enumerable: true,
configurable: true,
writable: true,
value: trpcFactory
});
Object.defineProperty(this, "project", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "sourceFile", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "options", {
enumerable: true,
configurable: true,
writable: true,
value: {}
});
Object.defineProperty(this, "schemaGenerator", {
enumerable: true,
configurable: true,
writable: true,
value: new schema_generator_1.SchemaGenerator()
});
this.project = new ts_morph_1.Project({
compilerOptions: {
target: ts_morph_1.ts.ScriptTarget.ES2019,
module: ts_morph_1.ts.ModuleKind.CommonJS,
emitDecoratorMetadata: true,
experimentalDecorators: true,
esModuleInterop: true,
},
});
}
setOptions(options) {
this.options = options;
}
generate() {
return __awaiter(this, void 0, void 0, function* () {
const { outputPath, injectFiles } = this.options;
if (!outputPath) {
error_handler_1.ErrorHandler.logError('RouterGenerator', 'Output path is missing in options', this.options);
throw error_handler_1.ErrorHandler.createError('RouterGenerator', 'Output path is required');
}
const outputDir = path.dirname(outputPath);
if (!fs.existsSync(outputDir)) {
fs.mkdirSync(outputDir, { recursive: true });
}
this.sourceFile = this.project.createSourceFile(outputPath, '', { overwrite: true });
this.addBaseImports();
if (injectFiles && injectFiles.length > 0) {
this.injectFiles(injectFiles);
}
try {
const t = server_1.initTRPC.context().create();
if (!this.trpcFactory) {
throw error_handler_1.ErrorHandler.createError('RouterGenerator', 'TRPCFactory not available');
}
const appRouter = yield this.trpcFactory.createAppRouter(this.options, t.router, t.procedure);
yield this.generateCodeFromAppRouter(appRouter);
}
catch (error) {
error_handler_1.ErrorHandler.logError('RouterGenerator', 'Failed to generate app router with factory', error);
}
yield this.saveFile();
});
}
generateCodeFromAppRouter(appRouter) {
return __awaiter(this, void 0, void 0, function* () {
const routerStructure = yield Promise.resolve(this.analyzeRouterStructure(appRouter));
if (this.options.generateSchemas) {
this.schemaGenerator.clear();
for (const [routerName, router] of Object.entries(routerStructure.routers)) {
this.schemaGenerator.collectSchemas(routerName, router.procedures);
}
const sortedSchemas = this.schemaGenerator.topologicalSort();
for (const schemaName of sortedSchemas) {
const schemaInfo = this.schemaGenerator.schemaRegistry.get(schemaName);
if (schemaInfo && !this.schemaGenerator.processedSchemas.has(schemaName)) {
this.sourceFile.addStatements(`export const ${schemaName} = ${schemaInfo.definition}; \n`);
this.sourceFile.addStatements(`export type ${schemaInfo.typeName} = z.infer<typeof ${schemaName}>; \n`);
this.schemaGenerator.processedSchemas.add(schemaName);
}
}
}
for (const [routerName, router] of Object.entries(routerStructure.routers)) {
this.generateRouterFromStructure(routerName, router.procedures);
}
const routerNames = Object.keys(routerStructure.routers).map((name) => {
return ` ${name}Router,`;
});
this.sourceFile.addStatements(`
export const appRouter = router({
${routerNames.join('\n')}
});
export type AppRouter = typeof appRouter;
`);
});
}
analyzeRouterStructure(appRouter) {
const result = { routers: {} };
try {
for (const [routerName, router] of Object.entries(appRouter)) {
if (!routerName.endsWith('Router'))
continue;
const routerStructure = { procedures: {} };
for (const [procName, proc] of Object.entries(router)) {
const procedureDef = proc._def;
const input = procedureDef.inputs[0];
const output = procedureDef.output;
const inputName = procedureDef.inputName;
const outputName = procedureDef.outputName;
routerStructure.procedures[procName] = {
type: procedureDef.type,
input: input,
output: output,
inputName: inputName,
outputName: outputName,
};
}
result.routers[routerName.replace('Router', '')] = routerStructure;
}
}
catch (error) {
error_handler_1.ErrorHandler.logError('RouterGenerator', 'Error analyzing router structure', error);
}
return result;
}
generateRouterFromStructure(routerName, procedures) {
const proceduresCode = Object.entries(procedures)
.map(([procedureName, procedure]) => {
let inputSchemaName;
if (procedure.input) {
const transformationForm = this.schemaGenerator.getTransformationForm(procedure.input);
if (transformationForm) {
inputSchemaName = transformationForm;
}
else {
inputSchemaName = this.options.generateSchemas
? this.schemaGenerator.generateSchemaName(routerName, procedureName, 'Input')
: this.schemaGenerator.generateNestedSchemaNameSafeForRouter(procedure.input);
}
}
else {
inputSchemaName = 'z.unknown()';
}
let outputSchemaName;
if (procedure.output) {
const transformationForm = this.schemaGenerator.getTransformationForm(procedure.output);
if (transformationForm) {
outputSchemaName = transformationForm;
}
else {
outputSchemaName = this.options.generateSchemas
? this.schemaGenerator.generateSchemaName(routerName, procedureName, 'Output')
: this.schemaGenerator.generateNestedSchemaNameSafeForRouter(procedure.output);
}
}
else {
outputSchemaName = 'z.unknown()';
}
let implementation;
if (procedure.type === 'subscription') {
implementation = `async function* () {
yield {} as z.infer<typeof ${outputSchemaName}>;
}`;
}
else {
implementation = `async () => "" as any`;
}
let procedureChain = 'publicProcedure';
if (inputSchemaName !== 'z.unknown()') {
procedureChain += `.input(${inputSchemaName})`;
}
if (procedure.type !== 'subscription' && outputSchemaName !== 'z.unknown()') {
procedureChain += `.output(${outputSchemaName})`;
}
procedureChain += `.${procedure.type}(${implementation})`;
return ` ${procedureName}: ${procedureChain},`;
})
.join('\n');
this.sourceFile.addStatements(`
const ${routerName}Router = router({
${proceduresCode}
});
`);
}
addBaseImports() {
this.sourceFile.addImportDeclaration({
moduleSpecifier: '@trpc/server',
namedImports: ['initTRPC'],
});
if (this.options.generateSchemas) {
this.sourceFile.addImportDeclaration({
moduleSpecifier: 'zod/v4',
defaultImport: 'z',
});
}
let transformerSetup = undefined;
if (this.options.transformer) {
transformerSetup = this.generateTransformerSetup();
}
const initTRPCConfig = this.options.transformer ? `{ transformer: transformer }` : '';
this.sourceFile.addStatements(`
${transformerSetup}
const t = initTRPC.create(${initTRPCConfig});
const router = t.router;
const publicProcedure = t.procedure;
`);
}
generateTransformerSetup() {
if (!this.options.transformer)
return undefined;
switch (this.options.transformer) {
case 'superjson': {
this.sourceFile.addImportDeclaration({
moduleSpecifier: 'superjson',
defaultImport: 'superjson',
});
return `const transformer = superjson;`;
}
case 'devalue': {
this.sourceFile.addImportDeclaration({
moduleSpecifier: 'devalue',
namedImports: ['stringify', 'parse'],
});
return `const transformer = { stringify, parse };`;
}
default: {
error_handler_1.ErrorHandler.logWarning('RouterGenerator', 'Unknown transformer provided');
return undefined;
}
}
}
injectFiles(filePaths) {
for (const filePath of filePaths) {
this.sourceFile.addStatements(`// Injected file: ${filePath}`);
const content = fs.readFileSync(filePath, 'utf-8');
this.sourceFile.addStatements(content);
}
}
saveFile() {
return __awaiter(this, void 0, void 0, function* () {
try {
this.sourceFile.formatText({
indentSize: 2,
convertTabsToSpaces: true,
insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces: true,
insertSpaceAfterKeywordsInControlFlowStatements: true,
insertSpaceAfterFunctionKeywordForAnonymousFunctions: true,
insertSpaceAfterCommaDelimiter: true,
insertSpaceAfterSemicolonInForStatements: true,
placeOpenBraceOnNewLineForFunctions: false,
placeOpenBraceOnNewLineForControlBlocks: false,
});
yield this.sourceFile.save();
yield this.runPrettierIfAvailable();
}
catch (error) {
error_handler_1.ErrorHandler.logError('RouterGenerator', 'Error saving or formatting file', error);
}
});
}
runPrettierIfAvailable() {
return __awaiter(this, void 0, void 0, function* () {
try {
const { exec } = yield Promise.resolve().then(() => require('child_process'));
const { promisify } = yield Promise.resolve().then(() => require('util'));
const execAsync = promisify(exec);
const outputPath = this.options.outputPath;
if (!outputPath)
return;
yield execAsync(`npx prettier --write "${outputPath}"`);
}
catch (_a) {
return;
}
});
}
};
exports.RouterGenerator = RouterGenerator;
exports.RouterGenerator = RouterGenerator = __decorate([
(0, common_3.Injectable)(),
__param(0, (0, common_2.Inject)(constants_1.TRPC_MODULE_CALLER_FILE_PATH)),
__param(0, (0, common_1.Optional)()),
__param(1, (0, common_1.Optional)()),
__metadata("design:paramtypes", [String, trpc_factory_1.TRPCFactory])
], RouterGenerator);
//# sourceMappingURL=router-generator.js.map