@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,496 +0,0 @@
1
- #!/usr/bin/env tsx
2
- // biome-ignore-all lint: Generated code
3
-
4
- import fs from 'node:fs/promises'
5
- import path from 'node:path'
6
- import gh, { type GeneratorOptions } from '@prisma/generator-helper'
7
-
8
- const header = '// This file was generated by prisma-effect-generator, do not edit manually.\n'
9
-
10
- // Utility function to convert PascalCase to camelCase
11
- function toCamelCase(str: string) {
12
- return str.charAt(0).toLowerCase() + str.slice(1)
13
- }
14
-
15
- // Export the generator function for use in CLI
16
- export async function generateEffectPrisma(options: GeneratorOptions) {
17
- const models = options.dmmf.datamodel.models
18
- const outputDir = options.generator.output?.value
19
-
20
- if (!outputDir) {
21
- throw new Error('No output directory specified')
22
- }
23
-
24
- // Clean output directory
25
- await fs.rm(outputDir, { recursive: true, force: true })
26
- await fs.mkdir(outputDir, { recursive: true })
27
-
28
- // Generate unified index file with PrismaService
29
- await generateUnifiedService(models, outputDir)
30
-
31
- // Generate types file
32
- await generateTypes(models, outputDir)
33
- }
34
-
35
- gh.generatorHandler({
36
- onManifest() {
37
- return {
38
- defaultOutput: '../src/generated/effect-prisma',
39
- prettyName: 'Prisma Effect Generator',
40
- requiresEngines: ['queryEngine'] as any,
41
- }
42
- },
43
-
44
- async onGenerate(options) {
45
- await generateEffectPrisma(options)
46
- },
47
- })
48
-
49
- function generateErrorTypes(models: readonly any[]) {
50
- const modelNames = models.map((model) => `"${model.name}"`).join(' | ')
51
- const operationNames = [
52
- '"findMany"',
53
- '"findUnique"',
54
- '"findFirst"',
55
- '"findUniqueOrThrow"',
56
- '"findFirstOrThrow"',
57
- '"create"',
58
- '"createMany"',
59
- '"createManyAndReturn"',
60
- '"update"',
61
- '"updateMany"',
62
- '"upsert"',
63
- '"delete"',
64
- '"deleteMany"',
65
- '"count"',
66
- '"aggregate"',
67
- '"groupBy"',
68
- '"$executeRaw"',
69
- '"$executeRawUnsafe"',
70
- '"$queryRaw"',
71
- '"$queryRawUnsafe"',
72
- ].join(' | ')
73
-
74
- return `
75
- // String literal types for exhaustive error handling
76
- export type ModelName = ${modelNames}
77
- export type OperationName = ${operationNames}
78
-
79
- // Specific Prisma error types for exhaustive error handling
80
- export class PrismaRecordNotFoundError extends Data.TaggedError("PrismaRecordNotFound")<{
81
- model: ModelName | "Prisma"
82
- operation: OperationName
83
- where?: unknown
84
- }> {}
85
-
86
- export class PrismaUniqueConstraintError extends Data.TaggedError("PrismaUniqueConstraint")<{
87
- model: ModelName | "Prisma"
88
- operation: OperationName
89
- field?: string
90
- value?: unknown
91
- }> {}
92
-
93
- export class PrismaForeignKeyConstraintError extends Data.TaggedError("PrismaForeignKeyConstraint")<{
94
- model: ModelName | "Prisma"
95
- operation: OperationName
96
- field?: string
97
- referencedModel?: string
98
- }> {}
99
-
100
- export class PrismaRequiredFieldError extends Data.TaggedError("PrismaRequiredField")<{
101
- model: ModelName | "Prisma"
102
- operation: OperationName
103
- field: string
104
- }> {}
105
-
106
- export class PrismaConnectionError extends Data.TaggedError("PrismaConnection")<{
107
- model: ModelName | "Prisma"
108
- operation: OperationName
109
- message?: string
110
- }> {}
111
-
112
- export class PrismaValidationError extends Data.TaggedError("PrismaValidation")<{
113
- model: ModelName | "Prisma"
114
- operation: OperationName
115
- message: string
116
- }> {}
117
-
118
- export class PrismaUnknownError extends Data.TaggedError("PrismaUnknown")<{
119
- model: ModelName | "Prisma"
120
- operation: OperationName
121
- originalError: unknown
122
- }> {}
123
-
124
- // Error conversion function
125
- function convertPrismaError(error: unknown, model: ModelName | "Prisma", operation: OperationName):
126
- | PrismaRecordNotFoundError
127
- | PrismaUniqueConstraintError
128
- | PrismaForeignKeyConstraintError
129
- | PrismaRequiredFieldError
130
- | PrismaConnectionError
131
- | PrismaValidationError
132
- | PrismaUnknownError {
133
-
134
- // Handle Prisma-specific errors
135
- if (error && typeof error === 'object') {
136
- const prismaError = error as any
137
-
138
- // PrismaClientKnownRequestError
139
- if (prismaError.code) {
140
- switch (prismaError.code) {
141
- case 'P2002': // Unique constraint violation
142
- return new PrismaUniqueConstraintError({
143
- model,
144
- operation,
145
- field: prismaError.meta?.target?.[0] as string,
146
- value: prismaError.meta?.value
147
- })
148
-
149
- case 'P2025': // Record not found
150
- case 'P2016': // Query interpretation error (record not found)
151
- return new PrismaRecordNotFoundError({
152
- model,
153
- operation,
154
- where: prismaError.meta
155
- })
156
-
157
- case 'P2003': // Foreign key constraint violation
158
- case 'P2014': // Required relation violation
159
- return new PrismaForeignKeyConstraintError({
160
- model,
161
- operation,
162
- field: prismaError.meta?.field_name as string,
163
- referencedModel: prismaError.meta?.model_name as string
164
- })
165
-
166
- case 'P2012': // Missing required field
167
- case 'P2011': // Null constraint violation
168
- return new PrismaRequiredFieldError({
169
- model,
170
- operation,
171
- field: prismaError.meta?.field as string
172
- })
173
-
174
- case 'P1000': // Authentication failed
175
- case 'P1001': // Can't reach database server
176
- case 'P1002': // Database server timeout
177
- case 'P1008': // Operations timed out
178
- return new PrismaConnectionError({
179
- model,
180
- operation,
181
- message: prismaError.message as string
182
- })
183
- }
184
- }
185
-
186
- // PrismaClientValidationError
187
- if (prismaError.name === 'PrismaClientValidationError') {
188
- return new PrismaValidationError({
189
- model,
190
- operation,
191
- message: prismaError.message as string
192
- })
193
- }
194
-
195
- // PrismaClientInitializationError
196
- if (prismaError.name === 'PrismaClientInitializationError') {
197
- return new PrismaConnectionError({
198
- model,
199
- operation,
200
- message: prismaError.message as string
201
- })
202
- }
203
- }
204
-
205
- // Fallback to unknown error
206
- return new PrismaUnknownError({
207
- model,
208
- operation,
209
- originalError: error
210
- })
211
- }
212
- `
213
- }
214
-
215
- function generateErrorUnionTypes(models: readonly any[]) {
216
- return models
217
- .map((model: { name: any }) => {
218
- const modelName = model.name
219
- return `
220
- // Error unions for ${modelName}
221
- export type ${modelName}FindErrors = PrismaConnectionError | PrismaUnknownError
222
- export type ${modelName}FindOrThrowErrors = PrismaRecordNotFoundError | PrismaConnectionError | PrismaUnknownError
223
- export type ${modelName}CreateErrors = PrismaUniqueConstraintError | PrismaForeignKeyConstraintError | PrismaRequiredFieldError | PrismaValidationError | PrismaConnectionError | PrismaUnknownError
224
- export type ${modelName}UpdateErrors = PrismaRecordNotFoundError | PrismaUniqueConstraintError | PrismaForeignKeyConstraintError | PrismaValidationError | PrismaConnectionError | PrismaUnknownError
225
- export type ${modelName}UpsertErrors = PrismaUniqueConstraintError | PrismaForeignKeyConstraintError | PrismaRequiredFieldError | PrismaValidationError | PrismaConnectionError | PrismaUnknownError
226
- export type ${modelName}DeleteErrors = PrismaRecordNotFoundError | PrismaForeignKeyConstraintError | PrismaConnectionError | PrismaUnknownError
227
- export type ${modelName}AggregateErrors = PrismaConnectionError | PrismaValidationError | PrismaUnknownError`
228
- })
229
- .join('\n')
230
- }
231
-
232
- function generateRawSqlOperations() {
233
- return `
234
- // eslint-disable-next-line @typescript-eslint/array-type
235
- $executeRaw: (args: Prisma.Sql | [Prisma.Sql, ...any[]]) =>
236
- Effect.tryPromise({
237
- try: () => (Array.isArray(args) ? client.$executeRaw(args[0], ...args.slice(1)) : client.$executeRaw(args)),
238
- catch: (error) => convertPrismaError(error, "Prisma", "$executeRaw")
239
- }),
240
-
241
- // eslint-disable-next-line @typescript-eslint/array-type
242
- $executeRawUnsafe: (query: string, ...values: any[]) =>
243
- Effect.tryPromise({
244
- try: () => client.$executeRawUnsafe(query, ...values),
245
- catch: (error) => convertPrismaError(error, "Prisma", "$executeRawUnsafe")
246
- }),
247
-
248
- // eslint-disable-next-line @typescript-eslint/array-type
249
- $queryRaw: (args: Prisma.Sql | [Prisma.Sql, ...any[]]) =>
250
- Effect.tryPromise({
251
- try: () => (Array.isArray(args) ? client.$queryRaw(args[0], ...args.slice(1)) : client.$queryRaw(args)),
252
- catch: (error) => convertPrismaError(error, "Prisma", "$queryRaw")
253
- }),
254
-
255
- // eslint-disable-next-line @typescript-eslint/array-type
256
- $queryRawUnsafe: (query: string, ...values: any[]) =>
257
- Effect.tryPromise({
258
- try: () => client.$queryRawUnsafe(query, ...values),
259
- catch: (error) => convertPrismaError(error, "Prisma", "$queryRawUnsafe")
260
- }),`
261
- }
262
-
263
- function generateModelOperations(models: readonly any[]) {
264
- return models
265
- .map((model: { name: any }) => {
266
- const modelName = model.name
267
- const modelNameCamel = toCamelCase(modelName)
268
-
269
- return ` ${modelNameCamel}: {
270
- // Find operations
271
- findMany: (args?: Prisma.${modelName}FindManyArgs) =>
272
- Effect.tryPromise({
273
- try: () => client.${modelNameCamel}.findMany(args),
274
- catch: (error) => convertPrismaError(error, "${modelName}", "findMany")
275
- }),
276
-
277
- findUnique: (args: Prisma.${modelName}FindUniqueArgs) =>
278
- Effect.tryPromise({
279
- try: () => client.${modelNameCamel}.findUnique(args),
280
- catch: (error) => convertPrismaError(error, "${modelName}", "findUnique")
281
- }),
282
-
283
- findFirst: (args?: Prisma.${modelName}FindFirstArgs) =>
284
- Effect.tryPromise({
285
- try: () => client.${modelNameCamel}.findFirst(args),
286
- catch: (error) => convertPrismaError(error, "${modelName}", "findFirst")
287
- }),
288
-
289
- findUniqueOrThrow: (args: Prisma.${modelName}FindUniqueOrThrowArgs) =>
290
- Effect.tryPromise({
291
- try: () => client.${modelNameCamel}.findUniqueOrThrow(args),
292
- catch: (error) => convertPrismaError(error, "${modelName}", "findUniqueOrThrow")
293
- }),
294
-
295
- findFirstOrThrow: (args?: Prisma.${modelName}FindFirstOrThrowArgs) =>
296
- Effect.tryPromise({
297
- try: () => client.${modelNameCamel}.findFirstOrThrow(args),
298
- catch: (error) => convertPrismaError(error, "${modelName}", "findFirstOrThrow")
299
- }),
300
-
301
- // Create operations
302
- create: (args: Prisma.${modelName}CreateArgs) =>
303
- Effect.tryPromise({
304
- try: () => client.${modelNameCamel}.create(args),
305
- catch: (error) => convertPrismaError(error, "${modelName}", "create")
306
- }),
307
-
308
- createMany: (args: Prisma.${modelName}CreateManyArgs) =>
309
- Effect.tryPromise({
310
- try: () => client.${modelNameCamel}.createMany(args),
311
- catch: (error) => convertPrismaError(error, "${modelName}", "createMany")
312
- }),
313
-
314
- createManyAndReturn: (args: Prisma.${modelName}CreateManyAndReturnArgs) =>
315
- Effect.tryPromise({
316
- try: () => client.${modelNameCamel}.createManyAndReturn(args),
317
- catch: (error) => convertPrismaError(error, "${modelName}", "createManyAndReturn")
318
- }),
319
-
320
- // Update operations
321
- update: (args: Prisma.${modelName}UpdateArgs) =>
322
- Effect.tryPromise({
323
- try: () => client.${modelNameCamel}.update(args),
324
- catch: (error) => convertPrismaError(error, "${modelName}", "update")
325
- }),
326
-
327
- updateMany: (args: Prisma.${modelName}UpdateManyArgs) =>
328
- Effect.tryPromise({
329
- try: () => client.${modelNameCamel}.updateMany(args),
330
- catch: (error) => convertPrismaError(error, "${modelName}", "updateMany")
331
- }),
332
-
333
- upsert: (args: Prisma.${modelName}UpsertArgs) =>
334
- Effect.tryPromise({
335
- try: () => client.${modelNameCamel}.upsert(args),
336
- catch: (error) => convertPrismaError(error, "${modelName}", "upsert")
337
- }),
338
-
339
- // Delete operations
340
- delete: (args: Prisma.${modelName}DeleteArgs) =>
341
- Effect.tryPromise({
342
- try: () => client.${modelNameCamel}.delete(args),
343
- catch: (error) => convertPrismaError(error, "${modelName}", "delete")
344
- }),
345
-
346
- deleteMany: (args?: Prisma.${modelName}DeleteManyArgs) =>
347
- Effect.tryPromise({
348
- try: () => client.${modelNameCamel}.deleteMany(args),
349
- catch: (error) => convertPrismaError(error, "${modelName}", "deleteMany")
350
- }),
351
-
352
- // Aggregate operations
353
- count: (args?: Prisma.${modelName}CountArgs) =>
354
- Effect.tryPromise({
355
- try: () => client.${modelNameCamel}.count(args),
356
- catch: (error) => convertPrismaError(error, "${modelName}", "count")
357
- }),
358
-
359
- aggregate: (args: Prisma.${modelName}AggregateArgs) =>
360
- Effect.tryPromise({
361
- try: () => client.${modelNameCamel}.aggregate(args),
362
- catch: (error) => convertPrismaError(error, "${modelName}", "aggregate")
363
- }),
364
-
365
- groupBy: <T extends Prisma.${modelName}GroupByArgs>(args: T) =>
366
- Effect.tryPromise({
367
- try: () => client.${modelNameCamel}.groupBy(args as any),
368
- catch: (error) => convertPrismaError(error, "${modelName}", "groupBy")
369
- })
370
- }`
371
- })
372
- .join(',\n\n')
373
- }
374
-
375
- async function generateUnifiedService(models: readonly any[], outputDir: string) {
376
- const errorTypes = generateErrorTypes(models)
377
- const errorUnions = generateErrorUnionTypes(models)
378
- const rawSqlOperations = generateRawSqlOperations()
379
- const modelOperations = generateModelOperations(models)
380
-
381
- const serviceContent = `${header}
382
- import { Context, Data, Effect, Layer } from "effect"
383
- import { Service } from "effect/Effect"
384
- import { type Prisma, PrismaClient } from "../prisma/index.js"
385
-
386
- export class PrismaClientService extends Context.Tag("PrismaClientService")<
387
- PrismaClientService,
388
- {
389
- tx: PrismaClient | Prisma.TransactionClient
390
- client: PrismaClient
391
- }
392
- >() {}
393
-
394
- export const LivePrismaLayer = Layer.effect(
395
- PrismaClientService,
396
- Effect.sync(() => {
397
- const prisma = new PrismaClient()
398
- return {
399
- // The \`tx\` property (transaction) can be shared and overridden,
400
- // but the \`client\` property must always be a PrismaClient instance.
401
- tx: prisma,
402
- client: prisma
403
- }
404
- })
405
- )
406
-
407
- ${errorTypes}
408
-
409
- ${errorUnions}
410
-
411
- export class PrismaService extends Service<PrismaService>()("PrismaService", {
412
- effect: Effect.gen(function* () {
413
- const { tx: client } = yield* PrismaClientService
414
- return {
415
- ${rawSqlOperations}
416
-
417
- ${modelOperations}
418
- }
419
- })
420
- }) {}
421
- `
422
-
423
- await fs.writeFile(path.join(outputDir, 'index.ts'), serviceContent)
424
- }
425
-
426
- async function generateTypes(models: readonly any[], outputDir: string) {
427
- const modelTypeDefinitions = models
428
- .map((model: { name: any }) => {
429
- const modelName = model.name
430
- const modelNameCamel = toCamelCase(modelName)
431
-
432
- return ` ${modelNameCamel}: {
433
- findMany: (args?: Prisma.${modelName}FindManyArgs) => Effect.Effect<Array<${modelName}>, ${modelName}FindErrors>
434
- findUnique: (args: Prisma.${modelName}FindUniqueArgs) => Effect.Effect<${modelName} | null, ${modelName}FindErrors>
435
- findFirst: (args?: Prisma.${modelName}FindFirstArgs) => Effect.Effect<${modelName} | null, ${modelName}FindErrors>
436
- findUniqueOrThrow: (args: Prisma.${modelName}FindUniqueOrThrowArgs) => Effect.Effect<${modelName}, ${modelName}FindOrThrowErrors>
437
- findFirstOrThrow: (args?: Prisma.${modelName}FindFirstOrThrowArgs) => Effect.Effect<${modelName}, ${modelName}FindOrThrowErrors>
438
- create: (args: Prisma.${modelName}CreateArgs) => Effect.Effect<${modelName}, ${modelName}CreateErrors>
439
- createMany: (args: Prisma.${modelName}CreateManyArgs) => Effect.Effect<Prisma.BatchPayload, ${modelName}CreateErrors>
440
- createManyAndReturn: (args: Prisma.${modelName}CreateManyAndReturnArgs) => Effect.Effect<Array<${modelName}>, ${modelName}CreateErrors>
441
- update: (args: Prisma.${modelName}UpdateArgs) => Effect.Effect<${modelName}, ${modelName}UpdateErrors>
442
- updateMany: (args: Prisma.${modelName}UpdateManyArgs) => Effect.Effect<Prisma.BatchPayload, ${modelName}UpdateErrors>
443
- upsert: (args: Prisma.${modelName}UpsertArgs) => Effect.Effect<${modelName}, ${modelName}UpsertErrors>
444
- delete: (args: Prisma.${modelName}DeleteArgs) => Effect.Effect<${modelName}, ${modelName}DeleteErrors>
445
- deleteMany: (args?: Prisma.${modelName}DeleteManyArgs) => Effect.Effect<Prisma.BatchPayload, ${modelName}DeleteErrors>
446
- count: (args?: Prisma.${modelName}CountArgs) => Effect.Effect<number, ${modelName}AggregateErrors>
447
- aggregate: (args: Prisma.${modelName}AggregateArgs) => Effect.Effect<any, ${modelName}AggregateErrors>
448
- groupBy: <T extends Prisma.${modelName}GroupByArgs>(args: T) => Effect.Effect<any, ${modelName}AggregateErrors>
449
- }`
450
- })
451
- .join('\n')
452
-
453
- const rawSqlErrorType = 'PrismaConnectionError | PrismaValidationError | PrismaUnknownError'
454
-
455
- const typeContent = `${header}
456
- import type { Effect } from "effect"
457
- import type { Prisma } from "../prisma/index.js"
458
- import type {
459
- ModelName,
460
- OperationName,
461
- PrismaRecordNotFoundError,
462
- PrismaUniqueConstraintError,
463
- PrismaForeignKeyConstraintError,
464
- PrismaRequiredFieldError,
465
- PrismaConnectionError,
466
- PrismaValidationError,
467
- PrismaUnknownError,
468
- ${models.map((model: { name: any }) => ` ${model.name}FindErrors,\n ${model.name}FindOrThrowErrors,\n ${model.name}CreateErrors,\n ${model.name}UpdateErrors,\n ${model.name}UpsertErrors,\n ${model.name}DeleteErrors,\n ${model.name}AggregateErrors`).join(',\n')}
469
- } from "./index.js"
470
-
471
- export type EffectPrismaService = {
472
- // eslint-disable-next-line @typescript-eslint/array-type
473
- $executeRaw: (args: Prisma.Sql | [Prisma.Sql, ...any[]]) => Effect.Effect<number, ${rawSqlErrorType}>
474
- // eslint-disable-next-line @typescript-eslint/array-type
475
- $executeRawUnsafe: (query: string, ...values: any[]) => Effect.Effect<number, ${rawSqlErrorType}>
476
- // eslint-disable-next-line @typescript-eslint/array-type
477
- $queryRaw: (args: Prisma.Sql | [Prisma.Sql, ...any[]]) => Effect.Effect<unknown, ${rawSqlErrorType}>
478
- // eslint-disable-next-line @typescript-eslint/array-type
479
- $queryRawUnsafe: (query: string, ...values: any[]) => Effect.Effect<unknown, ${rawSqlErrorType}>
480
- ${modelTypeDefinitions}
481
- }
482
-
483
- // Individual model types
484
- ${models
485
- .map(
486
- (model: { name: any }) => `
487
-
488
- // eslint-disable-next-line @typescript-eslint/no-empty-object-type
489
- export type ${model.name} = Prisma.${model.name}GetPayload<{}>
490
- `,
491
- )
492
- .join('\n')}
493
- `
494
-
495
- await fs.writeFile(path.join(outputDir, 'types.ts'), typeContent)
496
- }