Last active
August 25, 2025 08:43
-
-
Save beeman/8489d84dda58e586e2e34a43b7bfa263 to your computer and use it in GitHub Desktop.
A Prisma generator that wraps operations in a tryCatch function
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env bun | |
| import gh from '@prisma/generator-helper' | |
| import fs from 'node:fs/promises' | |
| import path from 'node:path' | |
| import type { Model } from '@prisma/dmmf' | |
| const header = `// This file was generated by a generator. Do not edit manually.` | |
| // These are the operations we want to wrap in tryCatch. | |
| const operations = [ | |
| 'Aggregate', | |
| 'Count', | |
| 'Create', | |
| 'CreateMany', | |
| 'CreateManyAndReturn', | |
| 'Delete', | |
| 'DeleteMany', | |
| 'FindFirst', | |
| 'FindFirstOrThrow', | |
| 'FindMany', | |
| 'FindUnique', | |
| 'FindUniqueOrThrow', | |
| 'Update', | |
| 'UpdateMany', | |
| 'Upsert', | |
| ] | |
| // Utility function to convert PascalCase to camelCase | |
| function toCamelCase(str: string) { | |
| return str.charAt(0).toLowerCase() + str.slice(1) | |
| } | |
| gh.generatorHandler({ | |
| onManifest() { | |
| return { | |
| defaultOutput: '../src/generated/effect-prisma', | |
| prettyName: 'Prisma Generator', | |
| requiresEngines: ['queryEngine'], | |
| } | |
| }, | |
| async onGenerate(options) { | |
| const models = options.dmmf.datamodel.models | |
| const outputDir = options.generator.output?.value | |
| const generatedClients = '../generated/client.js' | |
| const generatedModels = '../generated/models.js' | |
| if (!outputDir) { | |
| throw new Error('No output directory specified') | |
| } | |
| await fs.rm(outputDir, { recursive: true, force: true }) | |
| await fs.mkdir(outputDir, { recursive: true }) | |
| await generate([...models], { generatedClients, generatedModels, outputDir }) | |
| }, | |
| }) | |
| async function generate( | |
| models: Model[], | |
| { | |
| generatedClients, | |
| generatedModels, | |
| outputDir, | |
| }: { | |
| generatedClients: string | |
| generatedModels: string | |
| outputDir: string | |
| }, | |
| ) { | |
| await fs.writeFile( | |
| path.join(outputDir, 'index.ts'), | |
| [ | |
| // Header and imports | |
| header, | |
| generateImportsClient(generatedClients), | |
| generateImportsModels(models, generatedModels), | |
| // Declarations | |
| generateTryCatchImpl(), | |
| // Operations | |
| generateTryCatchOperations(models), | |
| ].join('\n'), | |
| ) | |
| } | |
| function generateTryCatchOperations(models: Model[]) { | |
| return models | |
| .map((model) => { | |
| return [ | |
| `/**`, | |
| ` * ${model.name}`, | |
| ` */`, | |
| operations.map((operation) => modelOperationFunction({ model, operation })).join(''), | |
| ].join('\n') | |
| }) | |
| .join('\n\n') | |
| } | |
| function generateImportsClient(from: string) { | |
| return `import { type Prisma, PrismaClient } from "${from}"` | |
| } | |
| function generateImportsModels(models: Model[], from: string) { | |
| const imports = models.map((model) => `${operations.map((arg) => `${model.name}${arg}Args`).join(',\n ')}`).join(',') | |
| return `import type {\n ${imports} } from "${from}"` | |
| } | |
| function generateTryCatchImpl() { | |
| return ` | |
| type Success<T> = { | |
| data: T | |
| error: null | |
| } | |
| type Failure<E> = { | |
| data: null | |
| error: E | |
| } | |
| type Result<T, E = Error> = Success<T> | Failure<E> | |
| export async function tryCatch<T, E = Error>(promise: Promise<T>): Promise<Result<T, E>> { | |
| try { | |
| const data = await promise | |
| return { data, error: null } | |
| } catch (error) { | |
| return { data: null, error: error as E } | |
| } | |
| } | |
| ` | |
| } | |
| function modelOperationFunction({ | |
| model, | |
| operation, | |
| prefix = '', | |
| }: { | |
| model: Model | |
| operation: string | |
| prefix?: string | |
| }) { | |
| const optionalArgs = [ | |
| 'CreateMany', | |
| 'CreateManyAndReturn', | |
| 'DeleteMany', | |
| 'FindFirst', | |
| 'FindFirstOrThrow', | |
| 'FindMany', | |
| ].includes(operation) | |
| const setType = ['Aggregate'].includes(operation) ? 'Subset' : 'SelectSubset' | |
| return ` | |
| export function ${toCamelCase(`${prefix}${model.name}${operation}`)}<T extends ${model.name}${operation}Args>( | |
| prisma: PrismaClient, | |
| args${optionalArgs ? '?' : ''}: Prisma.${setType}<T, ${model.name}${operation}Args>, | |
| ) { | |
| return tryCatch(prisma.${toCamelCase(model.name)}.${toCamelCase(operation)}<T>(args)) | |
| }` | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment