@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.
- package/README.md +154 -0
- package/dist/src/cli.d.ts +2 -0
- package/dist/src/cli.js +17 -0
- package/dist/src/commands/generate-effect.d.ts +2 -0
- package/dist/src/commands/generate-effect.js +73 -0
- package/dist/src/commands/generate-sql-schema.d.ts +2 -0
- package/dist/src/commands/generate-sql-schema.js +72 -0
- package/dist/src/commands/init.d.ts +4 -0
- package/dist/src/commands/init.js +102 -0
- package/dist/src/effect-prisma.d.ts +2 -0
- package/dist/src/effect-prisma.js +1771 -0
- package/dist/src/generators/prisma-effect-generator.d.ts +1 -0
- package/dist/src/generators/prisma-effect-generator.js +446 -0
- package/dist/src/generators/sql-schema-generator.d.ts +1 -0
- package/dist/src/generators/sql-schema-generator.js +58 -0
- package/dist/tsconfig.lib.tsbuildinfo +1 -0
- package/package.json +53 -0
- package/prisma/dev.db +0 -0
- package/prisma/generated/client.d.ts +1 -0
- package/prisma/generated/client.js +5 -0
- package/prisma/generated/default.d.ts +1 -0
- package/prisma/generated/default.js +5 -0
- package/prisma/generated/edge.d.ts +1 -0
- package/prisma/generated/edge.js +141 -0
- package/prisma/generated/effect/index.ts +397 -0
- package/prisma/generated/effect/prisma-repository.ts +954 -0
- package/prisma/generated/effect/prisma-schema.ts +94 -0
- package/prisma/generated/effect/schemas/enums.ts +6 -0
- package/prisma/generated/effect/schemas/index.ts +2 -0
- package/prisma/generated/effect/schemas/types.ts +40 -0
- package/prisma/generated/index-browser.js +172 -0
- package/prisma/generated/index.d.ts +2360 -0
- package/prisma/generated/index.js +141 -0
- package/prisma/generated/package.json +144 -0
- package/prisma/generated/query_compiler_bg.js +2 -0
- package/prisma/generated/query_compiler_bg.wasm +0 -0
- package/prisma/generated/query_compiler_bg.wasm-base64.js +2 -0
- package/prisma/generated/runtime/client.d.ts +3180 -0
- package/prisma/generated/runtime/client.js +86 -0
- package/prisma/generated/runtime/index-browser.d.ts +87 -0
- package/prisma/generated/runtime/index-browser.js +6 -0
- package/prisma/generated/runtime/wasm-compiler-edge.js +76 -0
- package/prisma/generated/schema.prisma +31 -0
- package/prisma/generated/wasm-edge-light-loader.mjs +5 -0
- package/prisma/generated/wasm-worker-loader.mjs +5 -0
- package/prisma/migrations/20250721164420_init/migration.sql +9 -0
- package/prisma/migrations/20250721191716_dumb/migration.sql +49 -0
- package/prisma/migrations/migration_lock.toml +3 -0
- package/prisma/schema.prisma +31 -0
- package/prisma.config.ts +8 -0
- package/project.json +48 -0
- package/scripts/cleanup-tests.ts +26 -0
- package/scripts/generate-test-files.ts +93 -0
- package/setup-tests.ts +10 -0
- package/src/cli.tsx +23 -0
- package/src/commands/generate-effect.ts +109 -0
- package/src/commands/generate-sql-schema.ts +109 -0
- package/src/commands/init.ts +155 -0
- package/src/effect-prisma.ts +1826 -0
- package/src/generators/prisma-effect-generator.ts +496 -0
- package/src/generators/sql-schema-generator.ts +75 -0
- package/test/prisma-model.test.ts +340 -0
- package/test/utils.ts +10 -0
- package/tsconfig.json +20 -0
- package/tsconfig.lib.json +24 -0
- package/tsconfig.spec.json +15 -0
- package/vitest.config.ts +23 -0
|
@@ -0,0 +1,496 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
#!/usr/bin/env tsx
|
|
2
|
+
// biome-ignore-all lint: Generated code
|
|
3
|
+
|
|
4
|
+
import { execSync } from 'node:child_process'
|
|
5
|
+
import fs from 'node:fs/promises'
|
|
6
|
+
import path from 'node:path'
|
|
7
|
+
import gh from '@prisma/generator-helper'
|
|
8
|
+
|
|
9
|
+
const header = `-- This file was generated by sql-schema-generator, do not edit manually.
|
|
10
|
+
-- Generated at: ${new Date().toISOString()}
|
|
11
|
+
|
|
12
|
+
`
|
|
13
|
+
|
|
14
|
+
// Types for generator options
|
|
15
|
+
type SqlGeneratorOptions = {
|
|
16
|
+
generator: {
|
|
17
|
+
output?: {
|
|
18
|
+
value: string
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Export the generator function for use in CLI
|
|
24
|
+
export async function generateSqlSchema(options: SqlGeneratorOptions) {
|
|
25
|
+
const outputDir = options.generator.output?.value || '../generated'
|
|
26
|
+
const datasourceUrl = 'prisma/dev.db'
|
|
27
|
+
|
|
28
|
+
if (!datasourceUrl) {
|
|
29
|
+
throw new Error('No datasource URL found')
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Ensure output directory exists
|
|
33
|
+
await fs.mkdir(outputDir, { recursive: true })
|
|
34
|
+
|
|
35
|
+
try {
|
|
36
|
+
// Extract the database path from the URL (assuming sqlite)
|
|
37
|
+
let dbPath = datasourceUrl
|
|
38
|
+
if (dbPath.startsWith('file:')) {
|
|
39
|
+
dbPath = dbPath.replace('file:', '')
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Make path relative to current working directory if needed
|
|
43
|
+
if (!path.isAbsolute(dbPath)) {
|
|
44
|
+
dbPath = path.resolve(process.cwd(), dbPath)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Use sqlite3 to dump only the schema (no data)
|
|
48
|
+
const schemaOutput = execSync(`sqlite3 "${dbPath}" ".schema"`, { encoding: 'utf8' })
|
|
49
|
+
|
|
50
|
+
// Write the schema to the output file
|
|
51
|
+
const outputPath = path.join(outputDir, 'schema.sql')
|
|
52
|
+
await fs.writeFile(outputPath, header + schemaOutput)
|
|
53
|
+
} catch (error) {
|
|
54
|
+
console.error('❌ Failed to generate schema dump:', error)
|
|
55
|
+
throw error
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
gh.generatorHandler({
|
|
60
|
+
onManifest() {
|
|
61
|
+
return {
|
|
62
|
+
defaultOutput: '../generated',
|
|
63
|
+
prettyName: 'SQL Schema Generator',
|
|
64
|
+
requiresEngines: [],
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
|
|
68
|
+
async onGenerate(options) {
|
|
69
|
+
await generateSqlSchema({
|
|
70
|
+
generator: {
|
|
71
|
+
output: options?.generator?.output ? { value: options.generator.output.value || '' } : undefined,
|
|
72
|
+
},
|
|
73
|
+
})
|
|
74
|
+
},
|
|
75
|
+
})
|