@effectify/prisma 0.1.1

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.
Files changed (67) hide show
  1. package/README.md +154 -0
  2. package/dist/src/cli.d.ts +2 -0
  3. package/dist/src/cli.js +17 -0
  4. package/dist/src/commands/generate-effect.d.ts +2 -0
  5. package/dist/src/commands/generate-effect.js +73 -0
  6. package/dist/src/commands/generate-sql-schema.d.ts +2 -0
  7. package/dist/src/commands/generate-sql-schema.js +72 -0
  8. package/dist/src/commands/init.d.ts +4 -0
  9. package/dist/src/commands/init.js +102 -0
  10. package/dist/src/effect-prisma.d.ts +2 -0
  11. package/dist/src/effect-prisma.js +1771 -0
  12. package/dist/src/generators/prisma-effect-generator.d.ts +1 -0
  13. package/dist/src/generators/prisma-effect-generator.js +446 -0
  14. package/dist/src/generators/sql-schema-generator.d.ts +1 -0
  15. package/dist/src/generators/sql-schema-generator.js +58 -0
  16. package/dist/tsconfig.lib.tsbuildinfo +1 -0
  17. package/package.json +53 -0
  18. package/prisma/dev.db +0 -0
  19. package/prisma/generated/client.d.ts +1 -0
  20. package/prisma/generated/client.js +5 -0
  21. package/prisma/generated/default.d.ts +1 -0
  22. package/prisma/generated/default.js +5 -0
  23. package/prisma/generated/edge.d.ts +1 -0
  24. package/prisma/generated/edge.js +141 -0
  25. package/prisma/generated/effect/index.ts +397 -0
  26. package/prisma/generated/effect/prisma-repository.ts +954 -0
  27. package/prisma/generated/effect/prisma-schema.ts +94 -0
  28. package/prisma/generated/effect/schemas/enums.ts +6 -0
  29. package/prisma/generated/effect/schemas/index.ts +2 -0
  30. package/prisma/generated/effect/schemas/types.ts +40 -0
  31. package/prisma/generated/index-browser.js +172 -0
  32. package/prisma/generated/index.d.ts +2360 -0
  33. package/prisma/generated/index.js +141 -0
  34. package/prisma/generated/package.json +144 -0
  35. package/prisma/generated/query_compiler_bg.js +2 -0
  36. package/prisma/generated/query_compiler_bg.wasm +0 -0
  37. package/prisma/generated/query_compiler_bg.wasm-base64.js +2 -0
  38. package/prisma/generated/runtime/client.d.ts +3180 -0
  39. package/prisma/generated/runtime/client.js +86 -0
  40. package/prisma/generated/runtime/index-browser.d.ts +87 -0
  41. package/prisma/generated/runtime/index-browser.js +6 -0
  42. package/prisma/generated/runtime/wasm-compiler-edge.js +76 -0
  43. package/prisma/generated/schema.prisma +31 -0
  44. package/prisma/generated/wasm-edge-light-loader.mjs +5 -0
  45. package/prisma/generated/wasm-worker-loader.mjs +5 -0
  46. package/prisma/migrations/20250721164420_init/migration.sql +9 -0
  47. package/prisma/migrations/20250721191716_dumb/migration.sql +49 -0
  48. package/prisma/migrations/migration_lock.toml +3 -0
  49. package/prisma/schema.prisma +31 -0
  50. package/prisma.config.ts +8 -0
  51. package/project.json +48 -0
  52. package/scripts/cleanup-tests.ts +26 -0
  53. package/scripts/generate-test-files.ts +93 -0
  54. package/setup-tests.ts +10 -0
  55. package/src/cli.tsx +23 -0
  56. package/src/commands/generate-effect.ts +109 -0
  57. package/src/commands/generate-sql-schema.ts +109 -0
  58. package/src/commands/init.ts +155 -0
  59. package/src/effect-prisma.ts +1826 -0
  60. package/src/generators/prisma-effect-generator.ts +496 -0
  61. package/src/generators/sql-schema-generator.ts +75 -0
  62. package/test/prisma-model.test.ts +340 -0
  63. package/test/utils.ts +10 -0
  64. package/tsconfig.json +20 -0
  65. package/tsconfig.lib.json +24 -0
  66. package/tsconfig.spec.json +15 -0
  67. package/vitest.config.ts +23 -0
@@ -0,0 +1,1826 @@
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> */
116
+ /** biome-ignore-all lint/style/useDefaultSwitchClause: <todo> */
117
+
118
+ import * as VariantSchema from '@effect/experimental/VariantSchema'
119
+ import { type PrismaClient as BasePrismaClient, Prisma as PrismaNamespace } from '${clientImportPath}'
120
+ import { PrismaClient } from './index.js'
121
+ import * as Data from 'effect/Data'
122
+ import * as Effect from 'effect/Effect'
123
+ import type * as Option from 'effect/Option'
124
+ import * as Schema from 'effect/Schema'
125
+ import * as SqlSchema from './prisma-schema.js'
126
+
127
+ export class PrismaUniqueConstraintError extends Data.TaggedError('PrismaUniqueConstraintError')<{
128
+ cause: PrismaNamespace.PrismaClientKnownRequestError
129
+ operation: string
130
+ model: string
131
+ }> {}
132
+
133
+ export class PrismaForeignKeyConstraintError extends Data.TaggedError('PrismaForeignKeyConstraintError')<{
134
+ cause: PrismaNamespace.PrismaClientKnownRequestError
135
+ operation: string
136
+ model: string
137
+ }> {}
138
+
139
+ export class PrismaRecordNotFoundError extends Data.TaggedError('PrismaRecordNotFoundError')<{
140
+ cause: PrismaNamespace.PrismaClientKnownRequestError
141
+ operation: string
142
+ model: string
143
+ }> {}
144
+
145
+ export class PrismaRelationViolationError extends Data.TaggedError('PrismaRelationViolationError')<{
146
+ cause: PrismaNamespace.PrismaClientKnownRequestError
147
+ operation: string
148
+ model: string
149
+ }> {}
150
+
151
+ export class PrismaRelatedRecordNotFoundError extends Data.TaggedError('PrismaRelatedRecordNotFoundError')<{
152
+ cause: PrismaNamespace.PrismaClientKnownRequestError
153
+ operation: string
154
+ model: string
155
+ }> {}
156
+
157
+ export class PrismaTransactionConflictError extends Data.TaggedError('PrismaTransactionConflictError')<{
158
+ cause: PrismaNamespace.PrismaClientKnownRequestError
159
+ operation: string
160
+ model: string
161
+ }> {}
162
+
163
+ export class PrismaValueTooLongError extends Data.TaggedError('PrismaValueTooLongError')<{
164
+ cause: PrismaNamespace.PrismaClientKnownRequestError
165
+ operation: string
166
+ model: string
167
+ }> {}
168
+
169
+ export class PrismaValueOutOfRangeError extends Data.TaggedError('PrismaValueOutOfRangeError')<{
170
+ cause: PrismaNamespace.PrismaClientKnownRequestError
171
+ operation: string
172
+ model: string
173
+ }> {}
174
+
175
+ export class PrismaDbConstraintError extends Data.TaggedError('PrismaDbConstraintError')<{
176
+ cause: PrismaNamespace.PrismaClientKnownRequestError
177
+ operation: string
178
+ model: string
179
+ }> {}
180
+
181
+ export class PrismaConnectionError extends Data.TaggedError('PrismaConnectionError')<{
182
+ cause: PrismaNamespace.PrismaClientKnownRequestError
183
+ operation: string
184
+ model: string
185
+ }> {}
186
+
187
+ export class PrismaMissingRequiredValueError extends Data.TaggedError('PrismaMissingRequiredValueError')<{
188
+ cause: PrismaNamespace.PrismaClientKnownRequestError
189
+ operation: string
190
+ model: string
191
+ }> {}
192
+
193
+ export class PrismaInputValidationError extends Data.TaggedError('PrismaInputValidationError')<{
194
+ cause: PrismaNamespace.PrismaClientKnownRequestError
195
+ operation: string
196
+ model: string
197
+ }> {}
198
+
199
+ export type PrismaCreateError =
200
+ | PrismaValueTooLongError
201
+ | PrismaUniqueConstraintError
202
+ | PrismaForeignKeyConstraintError
203
+ | PrismaDbConstraintError
204
+ | PrismaInputValidationError
205
+ | PrismaMissingRequiredValueError
206
+ | PrismaRelatedRecordNotFoundError
207
+ | PrismaValueOutOfRangeError
208
+ | PrismaConnectionError
209
+ | PrismaTransactionConflictError
210
+
211
+ export type PrismaUpdateError =
212
+ | PrismaValueTooLongError
213
+ | PrismaUniqueConstraintError
214
+ | PrismaForeignKeyConstraintError
215
+ | PrismaDbConstraintError
216
+ | PrismaInputValidationError
217
+ | PrismaMissingRequiredValueError
218
+ | PrismaRelationViolationError
219
+ | PrismaRelatedRecordNotFoundError
220
+ | PrismaValueOutOfRangeError
221
+ | PrismaConnectionError
222
+ | PrismaRecordNotFoundError
223
+ | PrismaTransactionConflictError
224
+
225
+ export type PrismaDeleteError =
226
+ | PrismaForeignKeyConstraintError
227
+ | PrismaRelationViolationError
228
+ | PrismaConnectionError
229
+ | PrismaRecordNotFoundError
230
+ | PrismaTransactionConflictError
231
+
232
+ export type PrismaFindOrThrowError =
233
+ | PrismaConnectionError
234
+ | PrismaRecordNotFoundError
235
+
236
+ export type PrismaFindError =
237
+ | PrismaConnectionError
238
+
239
+ export type PrismaDeleteManyError =
240
+ | PrismaForeignKeyConstraintError
241
+ | PrismaRelationViolationError
242
+ | PrismaConnectionError
243
+ | PrismaTransactionConflictError
244
+
245
+ export type PrismaUpdateManyError =
246
+ | PrismaValueTooLongError
247
+ | PrismaUniqueConstraintError
248
+ | PrismaForeignKeyConstraintError
249
+ | PrismaDbConstraintError
250
+ | PrismaInputValidationError
251
+ | PrismaMissingRequiredValueError
252
+ | PrismaValueOutOfRangeError
253
+ | PrismaConnectionError
254
+ | PrismaTransactionConflictError
255
+
256
+ // Create, Upsert
257
+ export const mapCreateError = (error: unknown, operation: string, model: string): PrismaCreateError => {
258
+ if (error instanceof PrismaNamespace.PrismaClientKnownRequestError) {
259
+ switch (error.code) {
260
+ case 'P2000':
261
+ return new PrismaValueTooLongError({ cause: error, operation, model })
262
+ case 'P2002':
263
+ return new PrismaUniqueConstraintError({ cause: error, operation, model })
264
+ case 'P2003':
265
+ return new PrismaForeignKeyConstraintError({ cause: error, operation, model })
266
+ case 'P2004':
267
+ return new PrismaDbConstraintError({ cause: error, operation, model })
268
+ case 'P2005':
269
+ case 'P2006':
270
+ case 'P2019':
271
+ return new PrismaInputValidationError({ cause: error, operation, model })
272
+ case 'P2011':
273
+ case 'P2012':
274
+ return new PrismaMissingRequiredValueError({ cause: error, operation, model })
275
+ case 'P2015':
276
+ case 'P2018':
277
+ return new PrismaRelatedRecordNotFoundError({ cause: error, operation, model })
278
+ case 'P2020':
279
+ return new PrismaValueOutOfRangeError({ cause: error, operation, model })
280
+ case 'P2024':
281
+ return new PrismaConnectionError({ cause: error, operation, model })
282
+ case 'P2034':
283
+ return new PrismaTransactionConflictError({ cause: error, operation, model })
284
+ }
285
+ }
286
+ throw error
287
+ }
288
+
289
+ // Update
290
+ export const mapUpdateError = (error: unknown, operation: string, model: string): PrismaUpdateError => {
291
+ if (error instanceof PrismaNamespace.PrismaClientKnownRequestError) {
292
+ switch (error.code) {
293
+ case "P2000":
294
+ return new PrismaValueTooLongError({ cause: error, operation, model });
295
+ case "P2002":
296
+ return new PrismaUniqueConstraintError({ cause: error, operation, model });
297
+ case "P2003":
298
+ return new PrismaForeignKeyConstraintError({ cause: error, operation, model });
299
+ case "P2004":
300
+ return new PrismaDbConstraintError({ cause: error, operation, model });
301
+ case "P2005":
302
+ case "P2006":
303
+ case "P2019":
304
+ return new PrismaInputValidationError({ cause: error, operation, model });
305
+ case "P2011":
306
+ case "P2012":
307
+ return new PrismaMissingRequiredValueError({ cause: error, operation, model });
308
+ case "P2014":
309
+ return new PrismaRelationViolationError({ cause: error, operation, model });
310
+ case "P2015":
311
+ case "P2018":
312
+ return new PrismaRelatedRecordNotFoundError({ cause: error, operation, model });
313
+ case "P2020":
314
+ return new PrismaValueOutOfRangeError({ cause: error, operation, model });
315
+ case "P2024":
316
+ return new PrismaConnectionError({ cause: error, operation, model });
317
+ case "P2025":
318
+ return new PrismaRecordNotFoundError({ cause: error, operation, model });
319
+ case "P2034":
320
+ return new PrismaTransactionConflictError({ cause: error, operation, model });
321
+ }
322
+ }
323
+ throw error;
324
+ }
325
+
326
+ // Delete
327
+ export const mapDeleteError = (error: unknown, operation: string, model: string): PrismaDeleteError => {
328
+ if (error instanceof PrismaNamespace.PrismaClientKnownRequestError) {
329
+ switch (error.code) {
330
+ case "P2003":
331
+ return new PrismaForeignKeyConstraintError({ cause: error, operation, model });
332
+ case "P2014":
333
+ return new PrismaRelationViolationError({ cause: error, operation, model });
334
+ case "P2024":
335
+ return new PrismaConnectionError({ cause: error, operation, model });
336
+ case "P2025":
337
+ return new PrismaRecordNotFoundError({ cause: error, operation, model });
338
+ case "P2034":
339
+ return new PrismaTransactionConflictError({ cause: error, operation, model });
340
+ }
341
+ }
342
+ throw error;
343
+ }
344
+
345
+ // FindOrThrow
346
+ export const mapFindOrThrowError = (error: unknown, operation: string, model: string): PrismaFindOrThrowError => {
347
+ if (error instanceof PrismaNamespace.PrismaClientKnownRequestError) {
348
+ switch (error.code) {
349
+ case "P2024":
350
+ return new PrismaConnectionError({ cause: error, operation, model });
351
+ case "P2025":
352
+ return new PrismaRecordNotFoundError({ cause: error, operation, model });
353
+ }
354
+ }
355
+ throw error;
356
+ }
357
+
358
+ // Find
359
+ export const mapFindError = (error: unknown, operation: string, model: string): PrismaFindError => {
360
+ if (error instanceof PrismaNamespace.PrismaClientKnownRequestError) {
361
+ switch (error.code) {
362
+ case "P2024":
363
+ return new PrismaConnectionError({ cause: error, operation, model });
364
+ }
365
+ }
366
+ throw error;
367
+ }
368
+
369
+ // DeleteMany
370
+ export const mapDeleteManyError = (error: unknown, operation: string, model: string): PrismaDeleteManyError => {
371
+ if (error instanceof PrismaNamespace.PrismaClientKnownRequestError) {
372
+ switch (error.code) {
373
+ case "P2003":
374
+ return new PrismaForeignKeyConstraintError({ cause: error, operation, model });
375
+ case "P2014":
376
+ return new PrismaRelationViolationError({ cause: error, operation, model });
377
+ case "P2024":
378
+ return new PrismaConnectionError({ cause: error, operation, model });
379
+ case "P2034":
380
+ return new PrismaTransactionConflictError({ cause: error, operation, model });
381
+ }
382
+ }
383
+ throw error;
384
+ }
385
+
386
+ // UpdateMany
387
+ export const mapUpdateManyError = (error: unknown, operation: string, model: string): PrismaUpdateManyError => {
388
+ if (error instanceof PrismaNamespace.PrismaClientKnownRequestError) {
389
+ switch (error.code) {
390
+ case "P2000":
391
+ return new PrismaValueTooLongError({ cause: error, operation, model });
392
+ case "P2002":
393
+ return new PrismaUniqueConstraintError({ cause: error, operation, model });
394
+ case "P2003":
395
+ return new PrismaForeignKeyConstraintError({ cause: error, operation, model });
396
+ case "P2004":
397
+ return new PrismaDbConstraintError({ cause: error, operation, model });
398
+ case "P2005":
399
+ case "P2006":
400
+ case "P2019":
401
+ return new PrismaInputValidationError({ cause: error, operation, model });
402
+ case "P2011":
403
+ case "P2012":
404
+ return new PrismaMissingRequiredValueError({ cause: error, operation, model });
405
+ case "P2020":
406
+ return new PrismaValueOutOfRangeError({ cause: error, operation, model });
407
+ case "P2024":
408
+ return new PrismaConnectionError({ cause: error, operation, model });
409
+ case "P2034":
410
+ return new PrismaTransactionConflictError({ cause: error, operation, model });
411
+ }
412
+ }
413
+ throw error;
414
+ }
415
+
416
+ const { Class, Field, FieldExcept, FieldOnly, Struct, Union, extract, fieldEvolve, fieldFromKey } = VariantSchema.make({
417
+ variants: [
418
+ 'findUnique',
419
+ 'findUniqueOrThrow',
420
+ 'findFirst',
421
+ 'findFirstOrThrow',
422
+ 'findMany',
423
+ 'create',
424
+ 'createMany',
425
+ 'createManyAndReturn',
426
+ 'update',
427
+ 'json',
428
+ 'jsonCreate',
429
+ 'jsonUpdate',
430
+ ],
431
+ defaultVariant: 'findUnique',
432
+ })
433
+
434
+ /**
435
+ * @since 1.0.0
436
+ * @category models
437
+ */
438
+ export type Any = Schema.Schema.Any & {
439
+ readonly fields: Schema.Struct.Fields
440
+ readonly findUnique: Schema.Schema.Any
441
+ readonly findUniqueOrThrow: Schema.Schema.Any
442
+ readonly findFirst: Schema.Schema.Any
443
+ readonly findFirstOrThrow: Schema.Schema.Any
444
+ readonly findMany: Schema.Schema.Any
445
+ readonly create: Schema.Schema.Any
446
+ readonly createMany: Schema.Schema.Any
447
+ readonly createManyAndReturn: Schema.Schema.Any
448
+ readonly update: Schema.Schema.Any
449
+ readonly json: Schema.Schema.Any
450
+ readonly jsonCreate: Schema.Schema.Any
451
+ readonly jsonUpdate: Schema.Schema.Any
452
+ }
453
+
454
+ /**
455
+ * @since 1.0.0
456
+ * @category models
457
+ */
458
+ export type AnyNoContext = Schema.Schema.AnyNoContext & {
459
+ readonly fields: Schema.Struct.Fields
460
+ readonly findUnique: Schema.Schema.AnyNoContext
461
+ readonly findUniqueOrThrow: Schema.Schema.AnyNoContext
462
+ readonly findFirst: Schema.Schema.AnyNoContext
463
+ readonly findFirstOrThrow: Schema.Schema.AnyNoContext
464
+ readonly findMany: Schema.Schema.AnyNoContext
465
+ readonly create: Schema.Schema.AnyNoContext
466
+ readonly createMany: Schema.Schema.AnyNoContext
467
+ readonly createManyAndReturn: Schema.Schema.AnyNoContext
468
+ readonly update: Schema.Schema.AnyNoContext
469
+ readonly json: Schema.Schema.AnyNoContext
470
+ readonly jsonCreate: Schema.Schema.AnyNoContext
471
+ readonly jsonUpdate: Schema.Schema.AnyNoContext
472
+ }
473
+
474
+ /**
475
+ * @since 1.0.0
476
+ * @category models
477
+ */
478
+ export type VariantsDatabase =
479
+ | 'findUnique'
480
+ | 'findUniqueOrThrow'
481
+ | 'findFirst'
482
+ | 'findFirstOrThrow'
483
+ | 'findMany'
484
+ | 'create'
485
+ | 'createMany'
486
+ | 'createManyAndReturn'
487
+ | 'update'
488
+
489
+ /**
490
+ * @since 1.0.0
491
+ * @category models
492
+ */
493
+ export type VariantsJson = 'json' | 'jsonCreate' | 'jsonUpdate'
494
+
495
+ export {
496
+ /**
497
+ * A base class used for creating domain model schemas.
498
+ *
499
+ * It supports common variants for database and JSON apis.
500
+ *
501
+ * @since 1.0.0
502
+ * @category constructors
503
+ * @example
504
+ * \`\`\`ts
505
+ * import { Schema } from "effect"
506
+ * import { Model } from "@effect/sql"
507
+ *
508
+ * export const GroupId = Schema.Number.pipe(Schema.brand("GroupId"))
509
+ *
510
+ * export class Group extends Model.Class<Group>("Group")({
511
+ * id: Model.Generated(GroupId),
512
+ * name: Schema.NonEmptyTrimmedString,
513
+ * createdAt: Model.DateTimeInsertFromDate,
514
+ * updatedAt: Model.DateTimeUpdateFromDate
515
+ * }) {}
516
+ *
517
+ * // schema used for selects
518
+ * Group
519
+ *
520
+ * // schema used for inserts
521
+ * Group.insert
522
+ *
523
+ * // schema used for updates
524
+ * Group.update
525
+ *
526
+ * // schema used for json api
527
+ * Group.json
528
+ * Group.jsonCreate
529
+ * Group.jsonUpdate
530
+ *
531
+ * // you can also turn them into classes
532
+ * class GroupJson extends Schema.Class<GroupJson>("GroupJson")(Group.json) {
533
+ * get upperName() {
534
+ * return this.name.toUpperCase()
535
+ * }
536
+ * }
537
+ * \`\`\`
538
+ */
539
+ Class,
540
+ /**
541
+ * @since 1.0.0
542
+ * @category extraction
543
+ */
544
+ extract,
545
+ /**
546
+ * @since 1.0.0
547
+ * @category fields
548
+ */
549
+ Field,
550
+ /**
551
+ * @since 1.0.0
552
+ * @category fields
553
+ */
554
+ fieldEvolve,
555
+ /**
556
+ * @since 1.0.0
557
+ * @category fields
558
+ */
559
+ FieldExcept,
560
+ /**
561
+ * @since 1.0.0
562
+ * @category fields
563
+ */
564
+ fieldFromKey,
565
+ /**
566
+ * @since 1.0.0
567
+ * @category fields
568
+ */
569
+ FieldOnly,
570
+ /**
571
+ * @since 1.0.0
572
+ * @category constructors
573
+ */
574
+ Struct,
575
+ /**
576
+ * @since 1.0.0
577
+ * @category constructors
578
+ */
579
+ Union,
580
+ }
581
+
582
+ /**
583
+ * Create a simple CRUD repository from a model.
584
+ *
585
+ * @since 1.0.0
586
+ * @category repository
587
+ */
588
+ export const make = <S extends Any, M extends keyof BasePrismaClient>(
589
+ Model: S,
590
+ options: {
591
+ readonly modelName: M extends string ? M : string
592
+ readonly spanPrefix: string
593
+ readonly uniqueKey?: string | ReadonlyArray<string>
594
+ },
595
+ ): Effect.Effect<
596
+ {
597
+ readonly findUnique: <A extends PrismaNamespace.Args<BasePrismaClient[M], 'findUnique'>>(
598
+ args: PrismaNamespace.Exact<A, PrismaNamespace.Args<BasePrismaClient[M], 'findUnique'>>,
599
+ ) => Effect.Effect<Option.Option<S['Type']>, PrismaFindError, S['Context'] | S['findUnique']['Context']>
600
+
601
+ readonly findUniqueOrThrow: <A extends PrismaNamespace.Args<BasePrismaClient[M], 'findUnique'>>(
602
+ args: PrismaNamespace.Exact<A, PrismaNamespace.Args<BasePrismaClient[M], 'findUnique'>>,
603
+ ) => Effect.Effect<S['Type'], PrismaFindOrThrowError, S['Context'] | S['findUniqueOrThrow']['Context']>
604
+
605
+ readonly findFirst: <A extends PrismaNamespace.Args<BasePrismaClient[M], 'findFirst'>>(
606
+ args?: PrismaNamespace.Exact<A, PrismaNamespace.Args<BasePrismaClient[M], 'findFirst'>>
607
+ ) => Effect.Effect<Option.Option<S['Type']>, PrismaFindError, S['Context'] | S['findFirst']['Context']>
608
+
609
+ readonly findFirstOrThrow: <A extends PrismaNamespace.Args<BasePrismaClient[M], 'findFirstOrThrow'>>(
610
+ args?: PrismaNamespace.Exact<A, PrismaNamespace.Args<BasePrismaClient[M], 'findFirstOrThrow'>>
611
+ ) => Effect.Effect<S['Type'], PrismaFindOrThrowError, S['Context'] | S['findFirstOrThrow']['Context']>
612
+
613
+ readonly findMany: <A extends PrismaNamespace.Args<BasePrismaClient[M], 'findMany'>>(
614
+ args?: PrismaNamespace.Exact<A, PrismaNamespace.Args<BasePrismaClient[M], 'findMany'>>
615
+ ) => Effect.Effect<Array<S['Type']>, PrismaFindError, S['Context'] | S['findMany']['Context']>
616
+
617
+ readonly create: <A extends PrismaNamespace.Args<BasePrismaClient[M], 'create'>>(
618
+ args: PrismaNamespace.Exact<A, PrismaNamespace.Args<BasePrismaClient[M], 'create'>>,
619
+ ) => Effect.Effect<S['Type'], PrismaCreateError, S['Context'] | S['create']['Context']>
620
+
621
+ readonly createMany: <A extends PrismaNamespace.Args<BasePrismaClient[M], 'createMany'>>(
622
+ args: PrismaNamespace.Exact<A, PrismaNamespace.Args<BasePrismaClient[M], 'createMany'>>,
623
+ ) => Effect.Effect<PrismaNamespace.BatchPayload, PrismaCreateError, S['Context'] | S['createMany']['Context']>
624
+
625
+ readonly createManyAndReturn: <A extends PrismaNamespace.Args<BasePrismaClient[M], 'createManyAndReturn'>>(
626
+ args: PrismaNamespace.Exact<A, PrismaNamespace.Args<BasePrismaClient[M], 'createManyAndReturn'>>,
627
+ ) => Effect.Effect<Array<S['Type']>, PrismaCreateError, S['Context'] | S['createManyAndReturn']['Context']>
628
+
629
+ readonly update: <A extends PrismaNamespace.Args<BasePrismaClient[M], 'update'>>(
630
+ args: PrismaNamespace.Exact<A, PrismaNamespace.Args<BasePrismaClient[M], 'update'>>
631
+ ) => Effect.Effect<S['Type'], PrismaUpdateError, S['Context'] | S['update']['Context']>
632
+
633
+ readonly updateMany: <A extends PrismaNamespace.Args<BasePrismaClient[M], 'updateMany'>>(
634
+ args: PrismaNamespace.Exact<A, PrismaNamespace.Args<BasePrismaClient[M], 'updateMany'>>
635
+ ) => Effect.Effect<PrismaNamespace.BatchPayload, PrismaUpdateManyError, S['Context'] | S['update']['Context']>
636
+
637
+ readonly delete: <A extends PrismaNamespace.Args<BasePrismaClient[M], 'delete'>>(
638
+ args: PrismaNamespace.Exact<A, PrismaNamespace.Args<BasePrismaClient[M], 'delete'>>
639
+ ) => Effect.Effect<S['Type'], PrismaDeleteError, S['Context']>
640
+
641
+ readonly deleteMany: <A extends PrismaNamespace.Args<BasePrismaClient[M], 'deleteMany'>>(
642
+ args?: PrismaNamespace.Exact<A, PrismaNamespace.Args<BasePrismaClient[M], 'deleteMany'>>
643
+ ) => Effect.Effect<PrismaNamespace.BatchPayload, PrismaDeleteManyError, S['Context']>
644
+
645
+ readonly upsert: <A extends PrismaNamespace.Args<BasePrismaClient[M], 'upsert'>>(
646
+ args: PrismaNamespace.Exact<A, PrismaNamespace.Args<BasePrismaClient[M], 'upsert'>>
647
+ ) => Effect.Effect<S['Type'], PrismaCreateError, S['Context']>
648
+
649
+ readonly count: <A extends PrismaNamespace.Args<BasePrismaClient[M], 'count'>>(
650
+ args?: PrismaNamespace.Exact<A, PrismaNamespace.Args<BasePrismaClient[M], 'count'>>
651
+ ) => Effect.Effect<unknown, PrismaFindError, S['Context']>
652
+
653
+ readonly aggregate: <A extends PrismaNamespace.Args<BasePrismaClient[M], 'aggregate'>>(
654
+ args: PrismaNamespace.Exact<A, PrismaNamespace.Args<BasePrismaClient[M], 'aggregate'>>
655
+ ) => Effect.Effect<unknown, PrismaFindError, S['Context']>
656
+
657
+ readonly groupBy: <A extends PrismaNamespace.Args<BasePrismaClient[M], 'groupBy'>>(
658
+ args: PrismaNamespace.Exact<A, PrismaNamespace.Args<BasePrismaClient[M], 'groupBy'>>
659
+ ) => Effect.Effect<unknown, PrismaFindError, S['Context']>
660
+ },
661
+ never,
662
+ PrismaClient
663
+ > =>
664
+ Effect.gen(function* () {
665
+ const prisma = yield* PrismaClient
666
+
667
+ // Construye el schema del where para findUnique usando uniqueKey en modo builder
668
+ let findUniqueRequestSchema: Schema.Schema<any, any, any> = Schema.Unknown
669
+ if (options.uniqueKey) {
670
+ const keys = Array.isArray(options.uniqueKey) ? options.uniqueKey : [options.uniqueKey]
671
+ const shape: Record<string, Schema.Schema.Any> = {}
672
+ let valid = true
673
+ for (const key of keys) {
674
+ const field = (Model as any).fields?.[key]
675
+ if (!field) {
676
+ valid = false
677
+ break
678
+ }
679
+ shape[key] = field
680
+ }
681
+ if (valid) {
682
+ findUniqueRequestSchema = Schema.Struct(shape as any)
683
+ }
684
+ }
685
+
686
+ const findUniqueSchema = SqlSchema.findOne({
687
+ Request: findUniqueRequestSchema,
688
+ Result: Model,
689
+ execute: (request) =>
690
+ Effect.tryPromise({
691
+ try: () =>
692
+ ((prisma.tx as BasePrismaClient)[options.modelName as M] as any).findUnique({ where: request }) as any,
693
+ catch: (error) => mapFindError(error, 'findUnique', options.modelName),
694
+ }).pipe(Effect.map((result) => (result ? [result] : []))),
695
+ })
696
+
697
+ const findUnique = <A extends PrismaNamespace.Args<BasePrismaClient[M], 'findUnique'>>(
698
+ args: PrismaNamespace.Exact<A, PrismaNamespace.Args<BasePrismaClient[M], 'findUnique'>>,
699
+ ): Effect.Effect<Option.Option<S['Type']>, never, S['Context'] | S['findUnique']['Context']> =>
700
+ findUniqueSchema((args as any).where).pipe(
701
+ Effect.catchTag('ParseError', Effect.die),
702
+ Effect.withSpan(\`\${options.spanPrefix}.findUnique\`, {
703
+ captureStackTrace: false,
704
+ attributes: { ...(args as any).where },
705
+ }),
706
+ ) as any
707
+
708
+ const findUniqueOrThrowSchema = SqlSchema.single({
709
+ Request: findUniqueRequestSchema,
710
+ Result: Model,
711
+ execute: (request) =>
712
+ Effect.tryPromise({
713
+ try: () =>
714
+ ((prisma.tx as BasePrismaClient)[options.modelName as M] as any).findUniqueOrThrow({
715
+ where: request,
716
+ }) as any,
717
+ catch: (error) => mapFindOrThrowError(error, 'findUniqueOrThrow', options.modelName),
718
+ }).pipe(Effect.map((result) => [result] as any)),
719
+ })
720
+
721
+ const findUniqueOrThrow = <A extends PrismaNamespace.Args<BasePrismaClient[M], 'findUnique'>>(
722
+ args: PrismaNamespace.Exact<A, PrismaNamespace.Args<BasePrismaClient[M], 'findUnique'>>,
723
+ ): Effect.Effect<S['Type'], never, S['Context'] | S['findUniqueOrThrow']['Context']> =>
724
+ findUniqueOrThrowSchema((args as any).where).pipe(
725
+ Effect.map((result) => result as S['Type']),
726
+ Effect.catchTag('ParseError', Effect.die),
727
+ Effect.catchTag('NoSuchElementException', Effect.die),
728
+ Effect.withSpan(\`\${options.spanPrefix}.findUniqueOrThrow\`, {
729
+ captureStackTrace: false,
730
+ attributes: { ...(args as any).where },
731
+ }),
732
+ ) as any
733
+
734
+ const findFirstSchema = SqlSchema.findOne({
735
+ Request: Schema.Unknown,
736
+ Result: Model,
737
+ execute: (request) =>
738
+ Effect.tryPromise({
739
+ try: () =>
740
+ ((prisma.tx as BasePrismaClient)[options.modelName as M] as any).findFirst(request) as any,
741
+ catch: (error) => mapFindError(error, 'findFirst', options.modelName),
742
+ }).pipe(Effect.map((result) => (result ? [result] : []))),
743
+ })
744
+
745
+ const findFirst = <A extends PrismaNamespace.Args<BasePrismaClient[M], 'findFirst'>>(
746
+ args?: PrismaNamespace.Exact<A, PrismaNamespace.Args<BasePrismaClient[M], 'findFirst'>>
747
+ ): Effect.Effect<Option.Option<S['Type']>, never, S['Context'] | S['findFirst']['Context']> =>
748
+ findFirstSchema(args).pipe(
749
+ Effect.catchTag('ParseError', Effect.die),
750
+ Effect.withSpan(\`\${options.spanPrefix}.findFirst\`, {
751
+ captureStackTrace: false,
752
+ attributes: { ...(args as any) },
753
+ }),
754
+ ) as any
755
+
756
+ const findFirstOrThrowSchema = SqlSchema.single({
757
+ Request: Schema.Unknown,
758
+ Result: Model,
759
+ execute: (request) =>
760
+ Effect.tryPromise({
761
+ try: () =>
762
+ ((prisma.tx as BasePrismaClient)[options.modelName as M] as any).findFirstOrThrow(request) as any,
763
+ catch: (error) => mapFindOrThrowError(error, 'findFirstOrThrow', options.modelName),
764
+ }).pipe(Effect.map((result) => [result] as any)),
765
+ })
766
+
767
+ const findFirstOrThrow = <A extends PrismaNamespace.Args<BasePrismaClient[M], 'findFirstOrThrow'>>(
768
+ args?: PrismaNamespace.Exact<A, PrismaNamespace.Args<BasePrismaClient[M], 'findFirstOrThrow'>>
769
+ ): Effect.Effect<S['Type'], never, S['Context'] | S['findFirstOrThrow']['Context']> =>
770
+ findFirstOrThrowSchema(args).pipe(
771
+ Effect.map((result) => result as S['Type']),
772
+ Effect.catchTag('ParseError', Effect.die),
773
+ Effect.catchTag('NoSuchElementException', Effect.die),
774
+ Effect.withSpan(\`\${options.spanPrefix}.findFirstOrThrow\`, {
775
+ captureStackTrace: false,
776
+ attributes: { ...(args as any) },
777
+ }),
778
+ ) as any
779
+
780
+ const findManySchema = SqlSchema.many({
781
+ Request: Schema.Unknown,
782
+ Result: Model,
783
+ execute: (request) =>
784
+ Effect.tryPromise({
785
+ try: () =>
786
+ ((prisma.tx as BasePrismaClient)[options.modelName as M] as any).findMany(request) as any,
787
+ catch: (error) => mapFindError(error, 'findMany', options.modelName),
788
+ }).pipe(Effect.map((result) => result as any)),
789
+ })
790
+
791
+ const findMany = <A extends PrismaNamespace.Args<BasePrismaClient[M], 'findMany'>>(
792
+ args?: PrismaNamespace.Exact<A, PrismaNamespace.Args<BasePrismaClient[M], 'findMany'>>
793
+ ): Effect.Effect<Array<S['Type']>, never, S['Context'] | S['findMany']['Context']> =>
794
+ findManySchema(args).pipe(
795
+ Effect.catchTag('ParseError', Effect.die),
796
+ Effect.withSpan(\`\${options.spanPrefix}.findMany\`, {
797
+ captureStackTrace: false,
798
+ attributes: { ...(args as any) },
799
+ }),
800
+ ) as any
801
+
802
+ const createSchema = SqlSchema.single({
803
+ Request: Model.create,
804
+ Result: Model,
805
+ execute: (request) =>
806
+ Effect.tryPromise({
807
+ try: () => ((prisma.tx as BasePrismaClient)[options.modelName as M] as any).create({ data: request }),
808
+ catch: (error) => mapCreateError(error, 'create', options.modelName),
809
+ }).pipe(Effect.map((result) => [result] as any)),
810
+ })
811
+
812
+ const create = <A extends PrismaNamespace.Args<BasePrismaClient[M], 'create'>>(
813
+ args: PrismaNamespace.Exact<A, PrismaNamespace.Args<BasePrismaClient[M], 'create'>>,
814
+ ): Effect.Effect<S['Type'], never, S['Context'] | S['create']['Context']> =>
815
+ createSchema((args as any).data).pipe(
816
+ Effect.catchTag('ParseError', Effect.die),
817
+ Effect.catchTag('NoSuchElementException', Effect.die),
818
+ Effect.withSpan(\`\${options.spanPrefix}.create\`, {
819
+ captureStackTrace: false,
820
+ attributes: { ...(args as any).data },
821
+ }),
822
+ ) as any
823
+
824
+ const createManySchema = SqlSchema.single({
825
+ Request: Schema.Array(Model.createMany),
826
+ Result: Schema.Unknown,
827
+ execute: (request) =>
828
+ Effect.tryPromise({
829
+ try: () =>
830
+ ((prisma.tx as BasePrismaClient)[options.modelName as M] as any).createMany({ data: request as any }),
831
+ catch: (error) => mapCreateError(error, 'createMany', options.modelName),
832
+ }).pipe(Effect.map((result) => [result] as any)),
833
+ })
834
+
835
+ const createMany = <A extends PrismaNamespace.Args<BasePrismaClient[M], 'createMany'>>(
836
+ args: PrismaNamespace.Exact<A, PrismaNamespace.Args<BasePrismaClient[M], 'createMany'>>,
837
+ ): Effect.Effect<PrismaNamespace.BatchPayload, never, S['Context'] | S['createMany']['Context']> =>
838
+ createManySchema((args as any).data).pipe(
839
+ Effect.map((res) => res as unknown as PrismaNamespace.BatchPayload),
840
+ Effect.catchTag('ParseError', Effect.die),
841
+ Effect.catchTag('NoSuchElementException', Effect.die),
842
+ Effect.withSpan(\`\${options.spanPrefix}.createMany\`, {
843
+ captureStackTrace: false,
844
+ attributes: { ...(args as any).data },
845
+ }),
846
+ ) as any
847
+
848
+ const createManyAndReturnSchema = SqlSchema.many({
849
+ Request: Schema.Array(Model.createMany),
850
+ Result: Model,
851
+ execute: (request) =>
852
+ Effect.tryPromise({
853
+ try: () =>
854
+ ((prisma.tx as BasePrismaClient)[options.modelName as M] as any).createManyAndReturn({
855
+ data: request as any,
856
+ }),
857
+ catch: (error) => mapCreateError(error, 'createManyAndReturn', options.modelName),
858
+ }),
859
+ })
860
+
861
+ const createManyAndReturn = <A extends PrismaNamespace.Args<BasePrismaClient[M], 'createManyAndReturn'>>(
862
+ args: PrismaNamespace.Exact<A, PrismaNamespace.Args<BasePrismaClient[M], 'createManyAndReturn'>>,
863
+ ): Effect.Effect<Array<S['Type']>, never, S['Context'] | S['createManyAndReturn']['Context']> =>
864
+ createManyAndReturnSchema((args as any).data).pipe(
865
+ Effect.catchTag('ParseError', Effect.die),
866
+ Effect.withSpan(\`\${options.spanPrefix}.createManyAndReturn\`, {
867
+ captureStackTrace: false,
868
+ attributes: { ...(args as any).data },
869
+ }),
870
+ ) as any
871
+
872
+ const countSchema = SqlSchema.single({
873
+ Request: Schema.Unknown,
874
+ Result: Schema.Unknown,
875
+ execute: (request) =>
876
+ Effect.tryPromise({
877
+ try: () => ((prisma.tx as BasePrismaClient)[options.modelName as M] as any).count(request),
878
+ catch: (error) => mapFindError(error, 'count', options.modelName),
879
+ }).pipe(Effect.map((result) => [result] as any)),
880
+ })
881
+
882
+ const count = <A extends PrismaNamespace.Args<BasePrismaClient[M], 'count'>>(
883
+ args?: PrismaNamespace.Exact<A, PrismaNamespace.Args<BasePrismaClient[M], 'count'>>
884
+ ): Effect.Effect<unknown, never, S['Context']> =>
885
+ countSchema(args).pipe(
886
+ Effect.catchTag('ParseError', Effect.die),
887
+ Effect.catchTag('NoSuchElementException', Effect.die),
888
+ Effect.withSpan(\`\${options.spanPrefix}.count\`, {
889
+ captureStackTrace: false,
890
+ attributes: { ...(args as any) },
891
+ }),
892
+ ) as any
893
+
894
+ const updateSchema = SqlSchema.single({
895
+ Request: Schema.Unknown,
896
+ Result: Model,
897
+ execute: (request) =>
898
+ Effect.tryPromise({
899
+ try: () => ((prisma.tx as BasePrismaClient)[options.modelName as M] as any).update(request),
900
+ catch: (error) => mapUpdateError(error, 'update', options.modelName),
901
+ }).pipe(Effect.map((result) => [result] as any)),
902
+ })
903
+
904
+ const update = <A extends PrismaNamespace.Args<BasePrismaClient[M], 'update'>>(
905
+ args: PrismaNamespace.Exact<A, PrismaNamespace.Args<BasePrismaClient[M], 'update'>>
906
+ ): Effect.Effect<S['Type'], never, S['Context'] | S['update']['Context']> =>
907
+ updateSchema(args).pipe(
908
+ Effect.catchTag('ParseError', Effect.die),
909
+ Effect.catchTag('NoSuchElementException', Effect.die),
910
+ Effect.withSpan(\`\${options.spanPrefix}.update\`, {
911
+ captureStackTrace: false,
912
+ attributes: { ...(args as any) },
913
+ }),
914
+ ) as any
915
+
916
+ const deleteSchema = SqlSchema.single({
917
+ Request: Schema.Unknown,
918
+ Result: Model,
919
+ execute: (request) =>
920
+ Effect.tryPromise({
921
+ try: () => ((prisma.tx as BasePrismaClient)[options.modelName as M] as any).delete(request),
922
+ catch: (error) => mapDeleteError(error, 'delete', options.modelName),
923
+ }).pipe(Effect.map((result) => [result] as any)),
924
+ })
925
+
926
+ const delete_ = <A extends PrismaNamespace.Args<BasePrismaClient[M], 'delete'>>(
927
+ args: PrismaNamespace.Exact<A, PrismaNamespace.Args<BasePrismaClient[M], 'delete'>>
928
+ ): Effect.Effect<S['Type'], never, S['Context']> =>
929
+ deleteSchema(args).pipe(
930
+ Effect.catchTag('ParseError', Effect.die),
931
+ Effect.catchTag('NoSuchElementException', Effect.die),
932
+ Effect.withSpan(\`\${options.spanPrefix}.delete\`, {
933
+ captureStackTrace: false,
934
+ attributes: { ...(args as any) },
935
+ }),
936
+ ) as any
937
+
938
+ const upsertSchema = SqlSchema.single({
939
+ Request: Schema.Unknown,
940
+ Result: Model,
941
+ execute: (request) =>
942
+ Effect.tryPromise({
943
+ try: () => ((prisma.tx as BasePrismaClient)[options.modelName as M] as any).upsert(request),
944
+ catch: (error) => mapCreateError(error, 'upsert', options.modelName),
945
+ }).pipe(Effect.map((result) => [result] as any)),
946
+ })
947
+
948
+ const upsert = <A extends PrismaNamespace.Args<BasePrismaClient[M], 'upsert'>>(
949
+ args: PrismaNamespace.Exact<A, PrismaNamespace.Args<BasePrismaClient[M], 'upsert'>>
950
+ ): Effect.Effect<S['Type'], never, S['Context']> =>
951
+ upsertSchema(args).pipe(
952
+ Effect.catchTag('ParseError', Effect.die),
953
+ Effect.catchTag('NoSuchElementException', Effect.die),
954
+ Effect.withSpan(\`\${options.spanPrefix}.upsert\`, {
955
+ captureStackTrace: false,
956
+ attributes: { ...(args as any) },
957
+ }),
958
+ ) as any
959
+
960
+ const aggregateSchema = SqlSchema.single({
961
+ Request: Schema.Unknown,
962
+ Result: Schema.Unknown,
963
+ execute: (request) =>
964
+ Effect.tryPromise({
965
+ try: () => ((prisma.tx as BasePrismaClient)[options.modelName as M] as any).aggregate(request),
966
+ catch: (error) => mapFindError(error, 'aggregate', options.modelName),
967
+ }).pipe(Effect.map((result) => [result] as any)),
968
+ })
969
+
970
+ const aggregate = <A extends PrismaNamespace.Args<BasePrismaClient[M], 'aggregate'>>(
971
+ args: PrismaNamespace.Exact<A, PrismaNamespace.Args<BasePrismaClient[M], 'aggregate'>>
972
+ ): Effect.Effect<unknown, never, S['Context']> =>
973
+ aggregateSchema(args).pipe(
974
+ Effect.catchTag('ParseError', Effect.die),
975
+ Effect.catchTag('NoSuchElementException', Effect.die),
976
+ Effect.withSpan(\`\${options.spanPrefix}.aggregate\`, {
977
+ captureStackTrace: false,
978
+ attributes: { ...(args as any) },
979
+ }),
980
+ ) as any
981
+
982
+ const groupBySchema = SqlSchema.many({
983
+ Request: Schema.Unknown,
984
+ Result: Schema.Unknown,
985
+ execute: (request) =>
986
+ Effect.tryPromise({
987
+ try: () => ((prisma.tx as BasePrismaClient)[options.modelName as M] as any).groupBy(request),
988
+ catch: (error) => mapFindError(error, 'groupBy', options.modelName),
989
+ }).pipe(Effect.map((result) => result as any)),
990
+ })
991
+
992
+ const groupBy = <A extends PrismaNamespace.Args<BasePrismaClient[M], 'groupBy'>>(
993
+ args: PrismaNamespace.Exact<A, PrismaNamespace.Args<BasePrismaClient[M], 'groupBy'>>
994
+ ): Effect.Effect<unknown, never, S['Context']> =>
995
+ groupBySchema(args).pipe(
996
+ Effect.catchTag('ParseError', Effect.die),
997
+ Effect.withSpan(\`\${options.spanPrefix}.groupBy\`, {
998
+ captureStackTrace: false,
999
+ attributes: { ...(args as any) },
1000
+ }),
1001
+ ) as any
1002
+
1003
+ const updateManySchema = SqlSchema.single({
1004
+ Request: Schema.Unknown,
1005
+ Result: Schema.Unknown,
1006
+ execute: (request) =>
1007
+ Effect.tryPromise({
1008
+ try: () => ((prisma.tx as BasePrismaClient)[options.modelName as M] as any).updateMany(request),
1009
+ catch: (error) => mapUpdateManyError(error, 'updateMany', options.modelName),
1010
+ }).pipe(Effect.map((result) => [result] as any)),
1011
+ })
1012
+
1013
+ const updateMany = <A extends PrismaNamespace.Args<BasePrismaClient[M], 'updateMany'>>(
1014
+ args: PrismaNamespace.Exact<A, PrismaNamespace.Args<BasePrismaClient[M], 'updateMany'>>
1015
+ ): Effect.Effect<PrismaNamespace.BatchPayload, never, S['Context'] | S['update']['Context']> =>
1016
+ updateManySchema(args).pipe(
1017
+ Effect.map((res) => res as unknown as PrismaNamespace.BatchPayload),
1018
+ Effect.catchTag('ParseError', Effect.die),
1019
+ Effect.catchTag('NoSuchElementException', Effect.die),
1020
+ Effect.withSpan(\`\${options.spanPrefix}.updateMany\`, {
1021
+ captureStackTrace: false,
1022
+ attributes: { ...(args as any) },
1023
+ }),
1024
+ ) as any
1025
+
1026
+ const deleteManySchema = SqlSchema.single({
1027
+ Request: Schema.Unknown,
1028
+ Result: Schema.Unknown,
1029
+ execute: (request) =>
1030
+ Effect.tryPromise({
1031
+ try: () => ((prisma.tx as BasePrismaClient)[options.modelName as M] as any).deleteMany(request),
1032
+ catch: (error) => mapDeleteManyError(error, 'deleteMany', options.modelName),
1033
+ }).pipe(Effect.map((result) => [result] as any)),
1034
+ })
1035
+
1036
+ const deleteMany = <A extends PrismaNamespace.Args<BasePrismaClient[M], 'deleteMany'>>(
1037
+ args?: PrismaNamespace.Exact<A, PrismaNamespace.Args<BasePrismaClient[M], 'deleteMany'>>
1038
+ ): Effect.Effect<PrismaNamespace.BatchPayload, never, S['Context']> =>
1039
+ deleteManySchema(args).pipe(
1040
+ Effect.map((res) => res as unknown as PrismaNamespace.BatchPayload),
1041
+ Effect.catchTag('ParseError', Effect.die),
1042
+ Effect.catchTag('NoSuchElementException', Effect.die),
1043
+ Effect.withSpan(\`\${options.spanPrefix}.deleteMany\`, {
1044
+ captureStackTrace: false,
1045
+ attributes: { ...(args as any) },
1046
+ }),
1047
+ ) as any
1048
+
1049
+ return {
1050
+ findUnique,
1051
+ findUniqueOrThrow,
1052
+ findFirst,
1053
+ findFirstOrThrow,
1054
+ findMany,
1055
+ create,
1056
+ createMany,
1057
+ createManyAndReturn,
1058
+ update,
1059
+ updateMany,
1060
+ delete: delete_,
1061
+ deleteMany,
1062
+ upsert,
1063
+ count,
1064
+ aggregate,
1065
+ groupBy
1066
+ } as const
1067
+
1068
+ })
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
+ }