@effectify/prisma 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,122 +1,8 @@
1
- #!/usr/bin/env -S pnpm dlx tsx
2
- /** biome-ignore-all lint/nursery/useMaxParams: TODO: refactor to reduce params */
3
- /** biome-ignore-all lint/nursery/noShadow: TODO: fix shadowing */
4
- /** biome-ignore-all lint/complexity/noExcessiveCognitiveComplexity: TODO: reduce complexity */
5
- import fs from 'node:fs/promises'
6
- import path from 'node:path'
7
- import type { DMMF } from '@prisma/client/runtime/client.js'
8
- import type { GeneratorOptions } from '@prisma/generator'
9
- import generatorHelper from '@prisma/generator-helper'
10
-
11
- const { generatorHandler } = generatorHelper
12
-
13
- const header = '// This file was generated by prisma-effect-generator, do not edit manually.\n'
14
-
15
- // Utility function to convert PascalCase to camelCase
16
-
17
- const prismaSchemaContent = `/**
18
- * @since 1.0.0
19
- */
20
- import * as Cause from 'effect/Cause'
21
- import * as Effect from 'effect/Effect'
22
- import type * as Option from 'effect/Option'
23
- import type { ParseError } from 'effect/ParseResult'
24
- import * as Schema from 'effect/Schema'
25
-
26
- /**
27
- * Run a sql query with a request schema and a result schema.
28
- *
29
- * @since 1.0.0
30
- * @category constructor
31
- */
32
- export const findAll = <IR, II, IA, AR, AI, A, R, E>(options: {
33
- readonly Request: Schema.Schema<IA, II, IR>
34
- readonly Result: Schema.Schema<A, AI, AR>
35
- readonly execute: (request: II) => Effect.Effect<ReadonlyArray<unknown>, E, R>
36
- }) => {
37
- const encodeRequest = Schema.encode(options.Request)
38
- const decode = Schema.decodeUnknown(Schema.Array(options.Result))
39
- return (request: IA): Effect.Effect<ReadonlyArray<A>, E | ParseError, R | IR | AR> =>
40
- Effect.flatMap(Effect.flatMap(encodeRequest(request), options.execute), decode)
41
- }
42
-
43
- const void_ = <IR, II, IA, R, E>(options: {
44
- readonly Request: Schema.Schema<IA, II, IR>
45
- readonly execute: (request: II) => Effect.Effect<unknown, E, R>
46
- }) => {
47
- const encode = Schema.encode(options.Request)
48
- return (request: IA): Effect.Effect<void, E | ParseError, R | IR> =>
49
- Effect.asVoid(Effect.flatMap(encode(request), options.execute))
50
- }
51
- export {
52
- /**
53
- * Run a sql query with a request schema and discard the result.
54
- *
55
- * @since 1.0.0
56
- * @category constructor
57
- */
58
- void_ as void,
59
- }
60
-
61
- /**
62
- * Run a sql query with a request schema and a result schema and return the first result.
63
- *
64
- * @since 1.0.0
65
- * @category constructor
66
- */
67
- export const findOne = <IR, II, IA, AR, AI, A, R, E>(options: {
68
- readonly Request: Schema.Schema<IA, II, IR>
69
- readonly Result: Schema.Schema<A, AI, AR>
70
- readonly execute: (request: II) => Effect.Effect<ReadonlyArray<unknown>, E, R>
71
- }) => {
72
- const encodeRequest = Schema.encode(options.Request)
73
- const decode = Schema.decodeUnknown(options.Result)
74
- return (request: IA): Effect.Effect<Option.Option<A>, E | ParseError, R | IR | AR> =>
75
- Effect.flatMap(Effect.flatMap(encodeRequest(request), options.execute), (arr) =>
76
- Array.isArray(arr) && arr.length > 0 ? Effect.asSome(decode(arr[0])) : Effect.succeedNone,
77
- )
78
- }
79
-
80
- /**
81
- * Run a sql query with a request schema and a result schema and return the first result.
82
- *
83
- * @since 1.0.0
84
- * @category constructor
85
- */
86
- export const single = <IR, II, IA, AR, AI, A, R, E>(options: {
87
- readonly Request: Schema.Schema<IA, II, IR>
88
- readonly Result: Schema.Schema<A, AI, AR>
89
- readonly execute: (request: II) => Effect.Effect<ReadonlyArray<unknown>, E, R>
90
- }) => {
91
- const encodeRequest = Schema.encode(options.Request)
92
- const decode = Schema.decodeUnknown(options.Result)
93
- return (request: IA): Effect.Effect<A, E | ParseError | Cause.NoSuchElementException, R | IR | AR> =>
94
- Effect.flatMap(
95
- Effect.flatMap(encodeRequest(request), options.execute),
96
- (arr): Effect.Effect<A, ParseError | Cause.NoSuchElementException, AR> =>
97
- Array.isArray(arr) && arr.length > 0 ? decode(arr[0]) : Effect.fail(new Cause.NoSuchElementException()),
98
- )
99
- }
100
-
101
- export const many = <IR, II, IA, AR, AI, A, R, E>(options: {
102
- readonly Request: Schema.Schema<IA, II, IR>
103
- readonly Result: Schema.Schema<A, AI, AR>
104
- readonly execute: (request: II) => Effect.Effect<Array<unknown>, E, R>
105
- }) => {
106
- const encodeRequest = Schema.encode(options.Request)
107
- const decode = Schema.decodeUnknown(Schema.Array(options.Result))
108
- return (request: IA): Effect.Effect<Array<A>, E | ParseError, R | IR | AR> =>
109
- Effect.map(Effect.flatMap(Effect.flatMap(encodeRequest(request), options.execute), decode), (arr) => [...arr])
110
- }
111
- `
112
-
113
- const getPrismaModelContent = (
114
- clientImportPath: string,
115
- ) => `/** biome-ignore-all lint/suspicious/noExplicitAny: <todo> */
1
+ /** biome-ignore-all lint/suspicious/noExplicitAny: <todo> */
116
2
  /** biome-ignore-all lint/style/useDefaultSwitchClause: <todo> */
117
3
 
118
4
  import * as VariantSchema from '@effect/experimental/VariantSchema'
119
- import { type PrismaClient as BasePrismaClient, Prisma as PrismaNamespace } from '${clientImportPath}'
5
+ import { type PrismaClient as BasePrismaClient, Prisma as PrismaNamespace } from '<%= it.clientImportPath %>'
120
6
  import { PrismaClient } from './index.js'
121
7
  import * as Data from 'effect/Data'
122
8
  import * as Effect from 'effect/Effect'
@@ -699,7 +585,7 @@ export const make = <S extends Any, M extends keyof BasePrismaClient>(
699
585
  ): Effect.Effect<Option.Option<S['Type']>, never, S['Context'] | S['findUnique']['Context']> =>
700
586
  findUniqueSchema((args as any).where).pipe(
701
587
  Effect.catchTag('ParseError', Effect.die),
702
- Effect.withSpan(\`\${options.spanPrefix}.findUnique\`, {
588
+ Effect.withSpan(`\${options.spanPrefix}.findUnique`, {
703
589
  captureStackTrace: false,
704
590
  attributes: { ...(args as any).where },
705
591
  }),
@@ -725,7 +611,7 @@ export const make = <S extends Any, M extends keyof BasePrismaClient>(
725
611
  Effect.map((result) => result as S['Type']),
726
612
  Effect.catchTag('ParseError', Effect.die),
727
613
  Effect.catchTag('NoSuchElementException', Effect.die),
728
- Effect.withSpan(\`\${options.spanPrefix}.findUniqueOrThrow\`, {
614
+ Effect.withSpan(`\${options.spanPrefix}.findUniqueOrThrow`, {
729
615
  captureStackTrace: false,
730
616
  attributes: { ...(args as any).where },
731
617
  }),
@@ -747,7 +633,7 @@ export const make = <S extends Any, M extends keyof BasePrismaClient>(
747
633
  ): Effect.Effect<Option.Option<S['Type']>, never, S['Context'] | S['findFirst']['Context']> =>
748
634
  findFirstSchema(args).pipe(
749
635
  Effect.catchTag('ParseError', Effect.die),
750
- Effect.withSpan(\`\${options.spanPrefix}.findFirst\`, {
636
+ Effect.withSpan(`\${options.spanPrefix}.findFirst`, {
751
637
  captureStackTrace: false,
752
638
  attributes: { ...(args as any) },
753
639
  }),
@@ -771,7 +657,7 @@ export const make = <S extends Any, M extends keyof BasePrismaClient>(
771
657
  Effect.map((result) => result as S['Type']),
772
658
  Effect.catchTag('ParseError', Effect.die),
773
659
  Effect.catchTag('NoSuchElementException', Effect.die),
774
- Effect.withSpan(\`\${options.spanPrefix}.findFirstOrThrow\`, {
660
+ Effect.withSpan(`\${options.spanPrefix}.findFirstOrThrow`, {
775
661
  captureStackTrace: false,
776
662
  attributes: { ...(args as any) },
777
663
  }),
@@ -793,7 +679,7 @@ export const make = <S extends Any, M extends keyof BasePrismaClient>(
793
679
  ): Effect.Effect<Array<S['Type']>, never, S['Context'] | S['findMany']['Context']> =>
794
680
  findManySchema(args).pipe(
795
681
  Effect.catchTag('ParseError', Effect.die),
796
- Effect.withSpan(\`\${options.spanPrefix}.findMany\`, {
682
+ Effect.withSpan(`\${options.spanPrefix}.findMany`, {
797
683
  captureStackTrace: false,
798
684
  attributes: { ...(args as any) },
799
685
  }),
@@ -815,7 +701,7 @@ export const make = <S extends Any, M extends keyof BasePrismaClient>(
815
701
  createSchema((args as any).data).pipe(
816
702
  Effect.catchTag('ParseError', Effect.die),
817
703
  Effect.catchTag('NoSuchElementException', Effect.die),
818
- Effect.withSpan(\`\${options.spanPrefix}.create\`, {
704
+ Effect.withSpan(`\${options.spanPrefix}.create`, {
819
705
  captureStackTrace: false,
820
706
  attributes: { ...(args as any).data },
821
707
  }),
@@ -839,7 +725,7 @@ export const make = <S extends Any, M extends keyof BasePrismaClient>(
839
725
  Effect.map((res) => res as unknown as PrismaNamespace.BatchPayload),
840
726
  Effect.catchTag('ParseError', Effect.die),
841
727
  Effect.catchTag('NoSuchElementException', Effect.die),
842
- Effect.withSpan(\`\${options.spanPrefix}.createMany\`, {
728
+ Effect.withSpan(`\${options.spanPrefix}.createMany`, {
843
729
  captureStackTrace: false,
844
730
  attributes: { ...(args as any).data },
845
731
  }),
@@ -863,7 +749,7 @@ export const make = <S extends Any, M extends keyof BasePrismaClient>(
863
749
  ): Effect.Effect<Array<S['Type']>, never, S['Context'] | S['createManyAndReturn']['Context']> =>
864
750
  createManyAndReturnSchema((args as any).data).pipe(
865
751
  Effect.catchTag('ParseError', Effect.die),
866
- Effect.withSpan(\`\${options.spanPrefix}.createManyAndReturn\`, {
752
+ Effect.withSpan(`\${options.spanPrefix}.createManyAndReturn`, {
867
753
  captureStackTrace: false,
868
754
  attributes: { ...(args as any).data },
869
755
  }),
@@ -885,7 +771,7 @@ export const make = <S extends Any, M extends keyof BasePrismaClient>(
885
771
  countSchema(args).pipe(
886
772
  Effect.catchTag('ParseError', Effect.die),
887
773
  Effect.catchTag('NoSuchElementException', Effect.die),
888
- Effect.withSpan(\`\${options.spanPrefix}.count\`, {
774
+ Effect.withSpan(`\${options.spanPrefix}.count`, {
889
775
  captureStackTrace: false,
890
776
  attributes: { ...(args as any) },
891
777
  }),
@@ -907,7 +793,7 @@ export const make = <S extends Any, M extends keyof BasePrismaClient>(
907
793
  updateSchema(args).pipe(
908
794
  Effect.catchTag('ParseError', Effect.die),
909
795
  Effect.catchTag('NoSuchElementException', Effect.die),
910
- Effect.withSpan(\`\${options.spanPrefix}.update\`, {
796
+ Effect.withSpan(`\${options.spanPrefix}.update`, {
911
797
  captureStackTrace: false,
912
798
  attributes: { ...(args as any) },
913
799
  }),
@@ -929,7 +815,7 @@ export const make = <S extends Any, M extends keyof BasePrismaClient>(
929
815
  deleteSchema(args).pipe(
930
816
  Effect.catchTag('ParseError', Effect.die),
931
817
  Effect.catchTag('NoSuchElementException', Effect.die),
932
- Effect.withSpan(\`\${options.spanPrefix}.delete\`, {
818
+ Effect.withSpan(`\${options.spanPrefix}.delete`, {
933
819
  captureStackTrace: false,
934
820
  attributes: { ...(args as any) },
935
821
  }),
@@ -951,7 +837,7 @@ export const make = <S extends Any, M extends keyof BasePrismaClient>(
951
837
  upsertSchema(args).pipe(
952
838
  Effect.catchTag('ParseError', Effect.die),
953
839
  Effect.catchTag('NoSuchElementException', Effect.die),
954
- Effect.withSpan(\`\${options.spanPrefix}.upsert\`, {
840
+ Effect.withSpan(`\${options.spanPrefix}.upsert`, {
955
841
  captureStackTrace: false,
956
842
  attributes: { ...(args as any) },
957
843
  }),
@@ -973,7 +859,7 @@ export const make = <S extends Any, M extends keyof BasePrismaClient>(
973
859
  aggregateSchema(args).pipe(
974
860
  Effect.catchTag('ParseError', Effect.die),
975
861
  Effect.catchTag('NoSuchElementException', Effect.die),
976
- Effect.withSpan(\`\${options.spanPrefix}.aggregate\`, {
862
+ Effect.withSpan(`\${options.spanPrefix}.aggregate`, {
977
863
  captureStackTrace: false,
978
864
  attributes: { ...(args as any) },
979
865
  }),
@@ -994,7 +880,7 @@ export const make = <S extends Any, M extends keyof BasePrismaClient>(
994
880
  ): Effect.Effect<unknown, never, S['Context']> =>
995
881
  groupBySchema(args).pipe(
996
882
  Effect.catchTag('ParseError', Effect.die),
997
- Effect.withSpan(\`\${options.spanPrefix}.groupBy\`, {
883
+ Effect.withSpan(`\${options.spanPrefix}.groupBy`, {
998
884
  captureStackTrace: false,
999
885
  attributes: { ...(args as any) },
1000
886
  }),
@@ -1017,7 +903,7 @@ export const make = <S extends Any, M extends keyof BasePrismaClient>(
1017
903
  Effect.map((res) => res as unknown as PrismaNamespace.BatchPayload),
1018
904
  Effect.catchTag('ParseError', Effect.die),
1019
905
  Effect.catchTag('NoSuchElementException', Effect.die),
1020
- Effect.withSpan(\`\${options.spanPrefix}.updateMany\`, {
906
+ Effect.withSpan(`\${options.spanPrefix}.updateMany`, {
1021
907
  captureStackTrace: false,
1022
908
  attributes: { ...(args as any) },
1023
909
  }),
@@ -1040,7 +926,7 @@ export const make = <S extends Any, M extends keyof BasePrismaClient>(
1040
926
  Effect.map((res) => res as unknown as PrismaNamespace.BatchPayload),
1041
927
  Effect.catchTag('ParseError', Effect.die),
1042
928
  Effect.catchTag('NoSuchElementException', Effect.die),
1043
- Effect.withSpan(\`\${options.spanPrefix}.deleteMany\`, {
929
+ Effect.withSpan(`\${options.spanPrefix}.deleteMany`, {
1044
930
  captureStackTrace: false,
1045
931
  attributes: { ...(args as any) },
1046
932
  }),
@@ -1066,761 +952,3 @@ export const make = <S extends Any, M extends keyof BasePrismaClient>(
1066
952
  } as const
1067
953
 
1068
954
  })
1069
- `
1070
-
1071
- generatorHandler({
1072
- onManifest() {
1073
- return {
1074
- defaultOutput: '../generated/effect',
1075
- prettyName: 'Prisma Effect Generator',
1076
- // No engines required - we only read the DMMF schema
1077
- requiresEngines: [],
1078
- }
1079
- },
1080
-
1081
- async onGenerate(options: GeneratorOptions) {
1082
- const models = options.dmmf.datamodel.models
1083
- const outputDir = options.generator.output?.value
1084
- const schemaDir = path.dirname(options.schemaPath)
1085
-
1086
- const configPath = options.generator.config.clientImportPath
1087
- const clientImportPath = Array.isArray(configPath) ? configPath[0] : (configPath ?? '@prisma/client')
1088
-
1089
- const errorConfigRaw = options.generator.config.errorImportPath
1090
- const errorImportPathRaw = Array.isArray(errorConfigRaw) ? errorConfigRaw[0] : errorConfigRaw
1091
-
1092
- const importExtConfigRaw = options.generator.config.importFileExtension
1093
- const importFileExtension = Array.isArray(importExtConfigRaw) ? importExtConfigRaw[0] : (importExtConfigRaw ?? '')
1094
-
1095
- if (!outputDir) {
1096
- throw new Error('No output directory specified')
1097
- }
1098
-
1099
- const addExtension = (filePath: string): string => {
1100
- if (!importFileExtension) {
1101
- return filePath
1102
- }
1103
- const ext = path.extname(filePath)
1104
- if (ext) {
1105
- return filePath
1106
- }
1107
- return `${filePath}.${importFileExtension}`
1108
- }
1109
-
1110
- let errorImportPath: string | undefined
1111
- if (errorImportPathRaw) {
1112
- const [modulePath, className] = errorImportPathRaw.split('#')
1113
- if (!(modulePath && className)) {
1114
- throw new Error(
1115
- `Invalid errorImportPath format: "${errorImportPathRaw}". Expected "path/to/module#ErrorClassName"`,
1116
- )
1117
- }
1118
-
1119
- if (modulePath.startsWith('.')) {
1120
- const absoluteErrorPath = path.resolve(schemaDir, modulePath)
1121
- const relativeToOutput = path.relative(outputDir, absoluteErrorPath)
1122
- const normalizedPath = relativeToOutput.startsWith('.') ? relativeToOutput : `./${relativeToOutput}`
1123
- const pathWithExtension = addExtension(normalizedPath)
1124
- errorImportPath = `${pathWithExtension}#${className}`
1125
- } else {
1126
- errorImportPath = errorImportPathRaw
1127
- }
1128
- }
1129
-
1130
- // Clean output directory
1131
- // await fs.rm(outputDir, { recursive: true, force: true })
1132
- await fs.mkdir(outputDir, { recursive: true })
1133
-
1134
- // Write static files
1135
- await fs.writeFile(path.join(outputDir, 'prisma-schema.ts'), prismaSchemaContent)
1136
- await fs.writeFile(path.join(outputDir, 'prisma-repository.ts'), getPrismaModelContent(clientImportPath))
1137
-
1138
- // Generate unified index file with PrismaService
1139
- await generateUnifiedService([...models], outputDir, clientImportPath, errorImportPath)
1140
-
1141
- // Fix imports in schemas/index.ts
1142
- await fixSchemaImports(outputDir)
1143
- },
1144
- })
1145
-
1146
- async function fixSchemaImports(outputDir: string) {
1147
- const schemasDir = path.join(outputDir, 'schemas')
1148
- const indexFile = path.join(schemasDir, 'index.ts')
1149
-
1150
- try {
1151
- const content = await fs.readFile(indexFile, 'utf-8')
1152
- const fixedContent = content
1153
- .replace(/export \* from '\.\/enums'/g, "export * from './enums.js'")
1154
- .replace(/export \* from '\.\/types'/g, "export * from './types.js'")
1155
-
1156
- if (content !== fixedContent) {
1157
- await fs.writeFile(indexFile, fixedContent)
1158
- }
1159
- } catch (_error) {
1160
- // Ignore if file doesn't exist
1161
- }
1162
- }
1163
-
1164
- type CustomErrorConfig = { path: string; className: string } | null
1165
-
1166
- function generateRawSqlOperations(customError: CustomErrorConfig) {
1167
- const errorType = customError ? customError.className : 'PrismaError'
1168
-
1169
- return `
1170
- $executeRaw: (args: PrismaNamespace.Sql | [PrismaNamespace.Sql, ...any[]]): Effect.Effect<number, ${errorType}, PrismaClient> =>
1171
- Effect.flatMap(PrismaClient, ({ tx: client }) =>
1172
- Effect.tryPromise({
1173
- try: () => (Array.isArray(args) ? client.$executeRaw(args[0], ...args.slice(1)) : client.$executeRaw(args)),
1174
- catch: (error) => mapError(error, "$executeRaw", "Prisma")
1175
- })
1176
- ),
1177
-
1178
- $executeRawUnsafe: (query: string, ...values: any[]): Effect.Effect<number, ${errorType}, PrismaClient> =>
1179
- Effect.flatMap(PrismaClient, ({ tx: client }) =>
1180
- Effect.tryPromise({
1181
- try: () => client.$executeRawUnsafe(query, ...values),
1182
- catch: (error) => mapError(error, "$executeRawUnsafe", "Prisma")
1183
- })
1184
- ),
1185
-
1186
- $queryRaw: <T = unknown>(args: PrismaNamespace.Sql | [PrismaNamespace.Sql, ...any[]]): Effect.Effect<T, ${errorType}, PrismaClient> =>
1187
- Effect.flatMap(PrismaClient, ({ tx: client }) =>
1188
- Effect.tryPromise({
1189
- try: () => (Array.isArray(args) ? client.$queryRaw(args[0], ...args.slice(1)) : client.$queryRaw(args)) as Promise<T>,
1190
- catch: (error) => mapError(error, "$queryRaw", "Prisma")
1191
- })
1192
- ),
1193
-
1194
- $queryRawUnsafe: <T = unknown>(query: string, ...values: any[]): Effect.Effect<T, ${errorType}, PrismaClient> =>
1195
- Effect.flatMap(PrismaClient, ({ tx: client }) =>
1196
- Effect.tryPromise({
1197
- try: () => client.$queryRawUnsafe(query, ...values) as Promise<T>,
1198
- catch: (error) => mapError(error, "$queryRawUnsafe", "Prisma")
1199
- })
1200
- ),`
1201
- }
1202
-
1203
- function generateModelClasses(models: DMMF.Model[]) {
1204
- const modelClasses = models
1205
- .map((model) => {
1206
- const modelName = model.name
1207
- const className = `${modelName}Model`
1208
- return `export class ${className} extends Model.Class<${className}>("${modelName}")({
1209
- ..._${modelName}.fields
1210
- }) {}`
1211
- })
1212
- .join('\n\n')
1213
-
1214
- return modelClasses
1215
- }
1216
-
1217
- // Parse error import path like "./errors#PrismaError" into { path, className }
1218
- function parseErrorImportPath(errorImportPath: string | undefined): { path: string; className: string } | null {
1219
- if (!errorImportPath) {
1220
- return null
1221
- }
1222
- const [path, className] = errorImportPath.split('#')
1223
- if (!(path && className)) {
1224
- throw new Error(`Invalid errorImportPath format: "${errorImportPath}". Expected "path/to/module#ErrorClassName"`)
1225
- }
1226
- return { path, className }
1227
- }
1228
-
1229
- async function generateUnifiedService(
1230
- models: DMMF.Model[],
1231
- outputDir: string,
1232
- clientImportPath: string,
1233
- errorImportPath: string | undefined,
1234
- ) {
1235
- const customError = parseErrorImportPath(errorImportPath)
1236
- const rawSqlOperations = generateRawSqlOperations(customError)
1237
- const modelClasses = generateModelClasses(models)
1238
-
1239
- // Generate different content based on whether custom error is configured
1240
- const serviceContent = customError
1241
- ? generateCustomErrorService(customError, clientImportPath, rawSqlOperations, modelClasses, models)
1242
- : generateDefaultErrorService(clientImportPath, rawSqlOperations, modelClasses, models)
1243
-
1244
- await fs.writeFile(path.join(outputDir, 'index.ts'), serviceContent)
1245
- }
1246
-
1247
- /**
1248
- * Generate service with custom user-provided error class.
1249
- */
1250
- function generateCustomErrorService(
1251
- customError: { path: string; className: string },
1252
- clientImportPath: string,
1253
- rawSqlOperations: string,
1254
- modelClasses: string,
1255
- models: DMMF.Model[],
1256
- ): string {
1257
- const schemaImports = models.map((m) => `_${m.name}`).join(', ')
1258
-
1259
- return `${header}
1260
- import { Context, Effect, Exit, Layer } from "effect"
1261
- import { Service } from "effect/Effect"
1262
- import { Prisma as PrismaNamespace, PrismaClient as BasePrismaClient } from "${clientImportPath}"
1263
- import { ${customError.className}, mapPrismaError } from "${customError.path}"
1264
- import * as Model from "./prisma-repository.js"
1265
- import { ${schemaImports} } from "./schemas/index.js"
1266
-
1267
- // Symbol used to identify intentional rollbacks vs actual errors
1268
- const ROLLBACK = Symbol.for("prisma.effect.rollback")
1269
-
1270
- // Type for the flat transaction client with commit/rollback control
1271
- type FlatTransactionClient = PrismaNamespace.TransactionClient & {
1272
- $commit: () => Promise<void>
1273
- $rollback: () => Promise<void>
1274
- }
1275
-
1276
- /** Transaction options for $transaction and $isolatedTransaction */
1277
- type TransactionOptions = {
1278
- maxWait?: number
1279
- timeout?: number
1280
- isolationLevel?: PrismaNamespace.TransactionIsolationLevel
1281
- }
1282
-
1283
- /**
1284
- * Context tag for the Prisma client instance.
1285
- * Holds the transaction client (tx) and root client.
1286
- */
1287
- export class PrismaClient extends Context.Tag("PrismaClient")<
1288
- PrismaClient,
1289
- {
1290
- tx: BasePrismaClient | PrismaNamespace.TransactionClient
1291
- client: BasePrismaClient
1292
- }
1293
- >() {
1294
- static layer = (
1295
- ...args: ConstructorParameters<typeof BasePrismaClient>
1296
- ) => Layer.scoped(
1297
- PrismaClient,
1298
- Effect.gen(function* () {
1299
- const prisma = new BasePrismaClient(...args)
1300
- yield* Effect.addFinalizer(() => Effect.promise(() => prisma.$disconnect()))
1301
- return { tx: prisma, client: prisma }
1302
- })
1303
- )
1304
-
1305
- static layerEffect = <R, E>(
1306
- optionsEffect: Effect.Effect<ConstructorParameters<typeof BasePrismaClient>[0], E, R>
1307
- ) => Layer.scoped(
1308
- PrismaClient,
1309
- Effect.gen(function* () {
1310
- const options = yield* optionsEffect
1311
- const prisma = new BasePrismaClient(options)
1312
- yield* Effect.addFinalizer(() => Effect.promise(() => prisma.$disconnect()))
1313
- return { tx: prisma, client: prisma }
1314
- })
1315
- )
1316
- }
1317
-
1318
- // Re-export the custom error type for convenience
1319
- export { ${customError.className} }
1320
-
1321
- // Use the user-provided error mapper
1322
- const mapError = mapPrismaError
1323
-
1324
- /**
1325
- * Internal helper to begin a callback-free interactive transaction.
1326
- */
1327
- const $begin = (
1328
- client: BasePrismaClient,
1329
- options?: {
1330
- maxWait?: number
1331
- timeout?: number
1332
- isolationLevel?: PrismaNamespace.TransactionIsolationLevel
1333
- }
1334
- ): Effect.Effect<FlatTransactionClient, ${customError.className}> =>
1335
- Effect.async<FlatTransactionClient, ${customError.className}>((resume) => {
1336
- let setTxClient: (txClient: PrismaNamespace.TransactionClient) => void
1337
- let commit: () => void
1338
- let rollback: () => void
1339
-
1340
- const txClientPromise = new Promise<PrismaNamespace.TransactionClient>((res) => {
1341
- setTxClient = res
1342
- })
1343
-
1344
- const txPromise = new Promise<void>((_res, _rej) => {
1345
- commit = () => _res(undefined)
1346
- rollback = () => _rej(ROLLBACK)
1347
- })
1348
-
1349
- const tx = client.$transaction((txClient) => {
1350
- setTxClient(txClient)
1351
- return txPromise
1352
- }, options).catch((e) => {
1353
- if (e === ROLLBACK) return
1354
- throw e
1355
- })
1356
-
1357
- txClientPromise.then((innerTx) => {
1358
- const proxy = new Proxy(innerTx, {
1359
- get(target, prop) {
1360
- if (prop === "$commit") return () => { commit(); return tx }
1361
- if (prop === "$rollback") return () => { rollback(); return tx }
1362
- return target[prop as keyof typeof target]
1363
- },
1364
- }) as FlatTransactionClient
1365
- resume(Effect.succeed(proxy))
1366
- }).catch((error) => {
1367
- resume(Effect.fail(mapError(error, "$transaction", "Prisma")))
1368
- })
1369
- })
1370
-
1371
- /**
1372
- * The main Prisma service with all database operations.
1373
- * Provides type-safe, effectful access to your Prisma models.
1374
- */
1375
- export class Prisma extends Service<Prisma>()("Prisma", {
1376
- effect: Effect.gen(function* () {
1377
- return {
1378
- $transaction: <R, E, A>(
1379
- effect: Effect.Effect<A, E, R>,
1380
- options?: TransactionOptions
1381
- ) =>
1382
- Effect.flatMap(
1383
- PrismaClient,
1384
- ({ client, tx }): Effect.Effect<A, E | ${customError.className}, R> => {
1385
- const isRootClient = "$transaction" in tx
1386
- if (!isRootClient) {
1387
- return effect
1388
- }
1389
-
1390
- return Effect.acquireUseRelease(
1391
- $begin(client, options),
1392
- (txClient) =>
1393
- effect.pipe(
1394
- Effect.provideService(PrismaClient, { tx: txClient, client })
1395
- ),
1396
- (txClient, exit) =>
1397
- Exit.isSuccess(exit)
1398
- ? Effect.promise(() => txClient.$commit())
1399
- : Effect.promise(() => txClient.$rollback())
1400
- )
1401
- }
1402
- ),
1403
-
1404
- $isolatedTransaction: <R, E, A>(
1405
- effect: Effect.Effect<A, E, R>,
1406
- options?: TransactionOptions
1407
- ) =>
1408
- Effect.flatMap(
1409
- PrismaClient,
1410
- ({ client }): Effect.Effect<A, E | ${customError.className}, R> => {
1411
- return Effect.acquireUseRelease(
1412
- $begin(client, options),
1413
- (txClient) =>
1414
- effect.pipe(
1415
- Effect.provideService(PrismaClient, { tx: txClient, client })
1416
- ),
1417
- (txClient, exit) =>
1418
- Exit.isSuccess(exit)
1419
- ? Effect.promise(() => txClient.$commit())
1420
- : Effect.promise(() => txClient.$rollback())
1421
- )
1422
- }
1423
- ),
1424
- ${rawSqlOperations}
1425
- }
1426
- })
1427
- }) {
1428
- static layer = (
1429
- ...args: ConstructorParameters<typeof BasePrismaClient>
1430
- ) => Layer.merge(PrismaClient.layer(...args), Prisma.Default)
1431
-
1432
- static layerEffect = <R, E>(
1433
- optionsEffect: Effect.Effect<ConstructorParameters<typeof BasePrismaClient>[0], E, R>
1434
- ) => Layer.merge(PrismaClient.layerEffect(optionsEffect), Prisma.Default)
1435
-
1436
- }
1437
-
1438
- // ============================================================================
1439
- // Deprecated aliases for backward compatibility
1440
- // ============================================================================
1441
-
1442
- export const PrismaClientService = PrismaClient
1443
- export const PrismaService = Prisma
1444
- export const makePrismaLayer = PrismaClient.layer
1445
- export const makePrismaLayerEffect = PrismaClient.layerEffect
1446
-
1447
- ${modelClasses}
1448
- `
1449
- }
1450
-
1451
- /**
1452
- * Generate service with default tagged error classes.
1453
- */
1454
- function generateDefaultErrorService(
1455
- clientImportPath: string,
1456
- rawSqlOperations: string,
1457
- modelClasses: string,
1458
- models: DMMF.Model[],
1459
- ): string {
1460
- const schemaImports = models.map((m) => `_${m.name}`).join(', ')
1461
-
1462
- return `${header}
1463
- import { Context, Data, Effect, Exit, Layer } from "effect"
1464
- import { Service } from "effect/Effect"
1465
- import { Prisma as PrismaNamespace, PrismaClient as BasePrismaClient } from "${clientImportPath}"
1466
- import * as Model from "./prisma-repository.js"
1467
- import { ${schemaImports} } from "./schemas/index.js"
1468
-
1469
- // Symbol used to identify intentional rollbacks vs actual errors
1470
- const ROLLBACK = Symbol.for("prisma.effect.rollback")
1471
-
1472
- // Type for the flat transaction client with commit/rollback control
1473
- type FlatTransactionClient = PrismaNamespace.TransactionClient & {
1474
- $commit: () => Promise<void>
1475
- $rollback: () => Promise<void>
1476
- }
1477
-
1478
- /** Transaction options for $transaction and $isolatedTransaction */
1479
- type TransactionOptions = {
1480
- maxWait?: number
1481
- timeout?: number
1482
- isolationLevel?: PrismaNamespace.TransactionIsolationLevel
1483
- }
1484
-
1485
- /**
1486
- * Context tag for the Prisma client instance.
1487
- * Holds the transaction client (tx) and root client.
1488
- */
1489
- export class PrismaClient extends Context.Tag("PrismaClient")<
1490
- PrismaClient,
1491
- {
1492
- tx: BasePrismaClient | PrismaNamespace.TransactionClient
1493
- client: BasePrismaClient
1494
- }
1495
- >() {
1496
- static layer = (
1497
- ...args: ConstructorParameters<typeof BasePrismaClient>
1498
- ) => Layer.scoped(
1499
- PrismaClient,
1500
- Effect.gen(function* () {
1501
- const prisma = new BasePrismaClient(...args)
1502
- yield* Effect.addFinalizer(() => Effect.promise(() => prisma.$disconnect()))
1503
- return { tx: prisma, client: prisma }
1504
- })
1505
- )
1506
-
1507
- static layerEffect = <R, E>(
1508
- optionsEffect: Effect.Effect<ConstructorParameters<typeof BasePrismaClient>[0], E, R>
1509
- ) => Layer.scoped(
1510
- PrismaClient,
1511
- Effect.gen(function* () {
1512
- const options = yield* optionsEffect
1513
- const prisma = new BasePrismaClient(options)
1514
- yield* Effect.addFinalizer(() => Effect.promise(() => prisma.$disconnect()))
1515
- return { tx: prisma, client: prisma }
1516
- })
1517
- )
1518
- }
1519
-
1520
- export class PrismaUniqueConstraintError extends Data.TaggedError("PrismaUniqueConstraintError")<{
1521
- cause: PrismaNamespace.PrismaClientKnownRequestError
1522
- operation: string
1523
- model: string
1524
- }> {}
1525
-
1526
- export class PrismaForeignKeyConstraintError extends Data.TaggedError("PrismaForeignKeyConstraintError")<{
1527
- cause: PrismaNamespace.PrismaClientKnownRequestError
1528
- operation: string
1529
- model: string
1530
- }> {}
1531
-
1532
- export class PrismaRecordNotFoundError extends Data.TaggedError("PrismaRecordNotFoundError")<{
1533
- cause: PrismaNamespace.PrismaClientKnownRequestError
1534
- operation: string
1535
- model: string
1536
- }> {}
1537
-
1538
- export class PrismaRelationViolationError extends Data.TaggedError("PrismaRelationViolationError")<{
1539
- cause: PrismaNamespace.PrismaClientKnownRequestError
1540
- operation: string
1541
- model: string
1542
- }> {}
1543
-
1544
- export class PrismaRelatedRecordNotFoundError extends Data.TaggedError("PrismaRelatedRecordNotFoundError")<{
1545
- cause: PrismaNamespace.PrismaClientKnownRequestError
1546
- operation: string
1547
- model: string
1548
- }> {}
1549
-
1550
- export class PrismaTransactionConflictError extends Data.TaggedError("PrismaTransactionConflictError")<{
1551
- cause: PrismaNamespace.PrismaClientKnownRequestError
1552
- operation: string
1553
- model: string
1554
- }> {}
1555
-
1556
- export class PrismaValueTooLongError extends Data.TaggedError("PrismaValueTooLongError")<{
1557
- cause: PrismaNamespace.PrismaClientKnownRequestError
1558
- operation: string
1559
- model: string
1560
- }> {}
1561
-
1562
- export class PrismaValueOutOfRangeError extends Data.TaggedError("PrismaValueOutOfRangeError")<{
1563
- cause: PrismaNamespace.PrismaClientKnownRequestError
1564
- operation: string
1565
- model: string
1566
- }> {}
1567
-
1568
- export class PrismaDbConstraintError extends Data.TaggedError("PrismaDbConstraintError")<{
1569
- cause: PrismaNamespace.PrismaClientKnownRequestError
1570
- operation: string
1571
- model: string
1572
- }> {}
1573
-
1574
- export class PrismaConnectionError extends Data.TaggedError("PrismaConnectionError")<{
1575
- cause: PrismaNamespace.PrismaClientKnownRequestError
1576
- operation: string
1577
- model: string
1578
- }> {}
1579
-
1580
- export class PrismaMissingRequiredValueError extends Data.TaggedError("PrismaMissingRequiredValueError")<{
1581
- cause: PrismaNamespace.PrismaClientKnownRequestError
1582
- operation: string
1583
- model: string
1584
- }> {}
1585
-
1586
- export class PrismaInputValidationError extends Data.TaggedError("PrismaInputValidationError")<{
1587
- cause: PrismaNamespace.PrismaClientKnownRequestError
1588
- operation: string
1589
- model: string
1590
- }> {}
1591
-
1592
- export type PrismaCreateError =
1593
- | PrismaValueTooLongError
1594
- | PrismaUniqueConstraintError
1595
- | PrismaForeignKeyConstraintError
1596
- | PrismaDbConstraintError
1597
- | PrismaInputValidationError
1598
- | PrismaMissingRequiredValueError
1599
- | PrismaRelatedRecordNotFoundError
1600
- | PrismaValueOutOfRangeError
1601
- | PrismaConnectionError
1602
- | PrismaTransactionConflictError
1603
-
1604
- export type PrismaUpdateError =
1605
- | PrismaValueTooLongError
1606
- | PrismaUniqueConstraintError
1607
- | PrismaForeignKeyConstraintError
1608
- | PrismaDbConstraintError
1609
- | PrismaInputValidationError
1610
- | PrismaMissingRequiredValueError
1611
- | PrismaRelationViolationError
1612
- | PrismaRelatedRecordNotFoundError
1613
- | PrismaValueOutOfRangeError
1614
- | PrismaConnectionError
1615
- | PrismaRecordNotFoundError
1616
- | PrismaTransactionConflictError
1617
-
1618
- export type PrismaDeleteError =
1619
- | PrismaForeignKeyConstraintError
1620
- | PrismaRelationViolationError
1621
- | PrismaConnectionError
1622
- | PrismaRecordNotFoundError
1623
- | PrismaTransactionConflictError
1624
-
1625
- export type PrismaFindOrThrowError =
1626
- | PrismaConnectionError
1627
- | PrismaRecordNotFoundError
1628
-
1629
- export type PrismaFindError =
1630
- | PrismaConnectionError
1631
-
1632
- export type PrismaDeleteManyError =
1633
- | PrismaForeignKeyConstraintError
1634
- | PrismaRelationViolationError
1635
- | PrismaConnectionError
1636
- | PrismaTransactionConflictError
1637
-
1638
- export type PrismaUpdateManyError =
1639
- | PrismaValueTooLongError
1640
- | PrismaUniqueConstraintError
1641
- | PrismaForeignKeyConstraintError
1642
- | PrismaDbConstraintError
1643
- | PrismaInputValidationError
1644
- | PrismaMissingRequiredValueError
1645
- | PrismaValueOutOfRangeError
1646
- | PrismaConnectionError
1647
- | PrismaTransactionConflictError
1648
-
1649
- export type PrismaError =
1650
- | PrismaValueTooLongError
1651
- | PrismaUniqueConstraintError
1652
- | PrismaForeignKeyConstraintError
1653
- | PrismaDbConstraintError
1654
- | PrismaInputValidationError
1655
- | PrismaMissingRequiredValueError
1656
- | PrismaRelationViolationError
1657
- | PrismaRelatedRecordNotFoundError
1658
- | PrismaValueOutOfRangeError
1659
- | PrismaConnectionError
1660
- | PrismaRecordNotFoundError
1661
- | PrismaTransactionConflictError
1662
-
1663
- // Generic mapper for raw operations and fallback
1664
- const mapError = (error: unknown, operation: string, model: string): PrismaError => {
1665
- if (error instanceof PrismaNamespace.PrismaClientKnownRequestError) {
1666
- switch (error.code) {
1667
- case "P2000":
1668
- return new PrismaValueTooLongError({ cause: error, operation, model });
1669
- case "P2002":
1670
- return new PrismaUniqueConstraintError({ cause: error, operation, model });
1671
- case "P2003":
1672
- return new PrismaForeignKeyConstraintError({ cause: error, operation, model });
1673
- case "P2004":
1674
- return new PrismaDbConstraintError({ cause: error, operation, model });
1675
- case "P2005":
1676
- case "P2006":
1677
- case "P2019":
1678
- return new PrismaInputValidationError({ cause: error, operation, model });
1679
- case "P2011":
1680
- case "P2012":
1681
- return new PrismaMissingRequiredValueError({ cause: error, operation, model });
1682
- case "P2014":
1683
- return new PrismaRelationViolationError({ cause: error, operation, model });
1684
- case "P2015":
1685
- case "P2018":
1686
- return new PrismaRelatedRecordNotFoundError({ cause: error, operation, model });
1687
- case "P2020":
1688
- return new PrismaValueOutOfRangeError({ cause: error, operation, model });
1689
- case "P2024":
1690
- return new PrismaConnectionError({ cause: error, operation, model });
1691
- case "P2025":
1692
- return new PrismaRecordNotFoundError({ cause: error, operation, model });
1693
- case "P2034":
1694
- return new PrismaTransactionConflictError({ cause: error, operation, model });
1695
- }
1696
- }
1697
- // Unknown errors are not handled and will be treated as defects
1698
- throw error;
1699
- }
1700
-
1701
- /**
1702
- * Internal helper to begin a callback-free interactive transaction.
1703
- */
1704
- const $begin = (
1705
- client: BasePrismaClient,
1706
- options?: {
1707
- maxWait?: number
1708
- timeout?: number
1709
- isolationLevel?: PrismaNamespace.TransactionIsolationLevel
1710
- }
1711
- ): Effect.Effect<FlatTransactionClient, PrismaError> =>
1712
- Effect.async<FlatTransactionClient, PrismaError>((resume) => {
1713
- let setTxClient: (txClient: PrismaNamespace.TransactionClient) => void
1714
- let commit: () => void
1715
- let rollback: () => void
1716
-
1717
- const txClientPromise = new Promise<PrismaNamespace.TransactionClient>((res) => {
1718
- setTxClient = res
1719
- })
1720
-
1721
- const txPromise = new Promise<void>((_res, _rej) => {
1722
- commit = () => _res(undefined)
1723
- rollback = () => _rej(ROLLBACK)
1724
- })
1725
-
1726
- const tx = client.$transaction((txClient) => {
1727
- setTxClient(txClient)
1728
- return txPromise
1729
- }, options).catch((e) => {
1730
- if (e === ROLLBACK) return
1731
- throw e
1732
- })
1733
-
1734
- txClientPromise.then((innerTx) => {
1735
- const proxy = new Proxy(innerTx, {
1736
- get(target, prop) {
1737
- if (prop === "$commit") return () => { commit(); return tx }
1738
- if (prop === "$rollback") return () => { rollback(); return tx }
1739
- return target[prop as keyof typeof target]
1740
- },
1741
- }) as FlatTransactionClient
1742
- resume(Effect.succeed(proxy))
1743
- }).catch((error) => {
1744
- resume(Effect.fail(mapError(error, "$transaction", "Prisma")))
1745
- })
1746
- })
1747
-
1748
- /**
1749
- * The main Prisma service with all database operations.
1750
- * Provides type-safe, effectful access to your Prisma models.
1751
- */
1752
- export class Prisma extends Service<Prisma>()("Prisma", {
1753
- effect: Effect.gen(function* () {
1754
- return {
1755
- $transaction: <R, E, A>(
1756
- effect: Effect.Effect<A, E, R>,
1757
- options?: TransactionOptions
1758
- ) =>
1759
- Effect.flatMap(
1760
- PrismaClient,
1761
- ({ client, tx }): Effect.Effect<A, E | PrismaError, R> => {
1762
- const isRootClient = "$transaction" in tx
1763
- if (!isRootClient) {
1764
- return effect
1765
- }
1766
-
1767
- return Effect.acquireUseRelease(
1768
- $begin(client, options),
1769
- (txClient) =>
1770
- effect.pipe(
1771
- Effect.provideService(PrismaClient, { tx: txClient, client })
1772
- ),
1773
- (txClient, exit) =>
1774
- Exit.isSuccess(exit)
1775
- ? Effect.promise(() => txClient.$commit())
1776
- : Effect.promise(() => txClient.$rollback())
1777
- )
1778
- }
1779
- ),
1780
-
1781
- $isolatedTransaction: <R, E, A>(
1782
- effect: Effect.Effect<A, E, R>,
1783
- options?: TransactionOptions
1784
- ) =>
1785
- Effect.flatMap(
1786
- PrismaClient,
1787
- ({ client }): Effect.Effect<A, E | PrismaError, R> => {
1788
- return Effect.acquireUseRelease(
1789
- $begin(client, options),
1790
- (txClient) =>
1791
- effect.pipe(
1792
- Effect.provideService(PrismaClient, { tx: txClient, client })
1793
- ),
1794
- (txClient, exit) =>
1795
- Exit.isSuccess(exit)
1796
- ? Effect.promise(() => txClient.$commit())
1797
- : Effect.promise(() => txClient.$rollback())
1798
- )
1799
- }
1800
- ),
1801
- ${rawSqlOperations}
1802
- }
1803
- })
1804
- }) {
1805
- static layer = (
1806
- ...args: ConstructorParameters<typeof BasePrismaClient>
1807
- ) => Layer.merge(PrismaClient.layer(...args), Prisma.Default)
1808
-
1809
- static layerEffect = <R, E>(
1810
- optionsEffect: Effect.Effect<ConstructorParameters<typeof BasePrismaClient>[0], E, R>
1811
- ) => Layer.merge(PrismaClient.layerEffect(optionsEffect), Prisma.Default)
1812
-
1813
- }
1814
-
1815
- // ============================================================================
1816
- // Deprecated aliases for backward compatibility
1817
- // ============================================================================
1818
-
1819
- export const PrismaClientService = PrismaClient
1820
- export const PrismaService = Prisma
1821
- export const makePrismaLayer = PrismaClient.layer
1822
- export const makePrismaLayerEffect = PrismaClient.layerEffect
1823
-
1824
- ${modelClasses}
1825
- `
1826
- }