UNPKG

@nexica/nestjs-trpc

Version:
339 lines (338 loc) 15.1 kB
"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