prisma-generator-effect 1.1.2-beta.1 → 1.1.3-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +102 -0
- package/dist/index.js +202 -43
- package/package.json +1 -1
- package/dist/src/index.js +0 -1976
package/README.md
CHANGED
|
@@ -8,6 +8,7 @@ A Prisma generator that creates a fully-typed, Effect-based service wrapper for
|
|
|
8
8
|
- 🛡️ **Type Safety**: Full TypeScript support with generated types matching your Prisma schema.
|
|
9
9
|
- 🧩 **Dependency Injection**: Integrates seamlessly with Effect's `Layer` and `Context` system.
|
|
10
10
|
- 🔍 **Error Handling**: Automatically catches and wraps Prisma errors into typed `PrismaError` variants.
|
|
11
|
+
- 🔀 **Read Replicas**: Optional `PrismaReplicas` service automatically routes reads to replicas, with `$primary()` / `$replica()` escape hatches for explicit routing.
|
|
11
12
|
|
|
12
13
|
## Installation
|
|
13
14
|
|
|
@@ -605,3 +606,104 @@ await Effect.runPromise(
|
|
|
605
606
|
```
|
|
606
607
|
|
|
607
608
|
For long-running applications (like servers), you typically provide the layer once at startup and it stays connected for the lifetime of the application.
|
|
609
|
+
|
|
610
|
+
### Read Replicas
|
|
611
|
+
|
|
612
|
+
The generator emits an optional `PrismaReplicas` service tag that, when provided in
|
|
613
|
+
the layer context, causes read operations to be routed to a randomly selected replica
|
|
614
|
+
instead of the primary client. This is inspired by
|
|
615
|
+
[`@prisma/extension-read-replicas`](https://github.com/prisma/extension-read-replicas)
|
|
616
|
+
but implemented natively at the Effect service level (no extension runtime dependency).
|
|
617
|
+
|
|
618
|
+
#### Layer setup
|
|
619
|
+
|
|
620
|
+
Provide replicas alongside the primary `Prisma.layer(...)`:
|
|
621
|
+
|
|
622
|
+
```typescript
|
|
623
|
+
import { Layer } from "effect"
|
|
624
|
+
import { Prisma, PrismaReplicas } from "./generated/effect"
|
|
625
|
+
|
|
626
|
+
const AppLayer = Layer.mergeAll(
|
|
627
|
+
// Primary - handles writes, transactions, raw SQL
|
|
628
|
+
Prisma.layer({ adapter: primaryAdapter }),
|
|
629
|
+
// Replicas - handle reads (optional)
|
|
630
|
+
PrismaReplicas.layer([
|
|
631
|
+
{ adapter: replica1Adapter },
|
|
632
|
+
{ adapter: replica2Adapter },
|
|
633
|
+
]),
|
|
634
|
+
)
|
|
635
|
+
```
|
|
636
|
+
|
|
637
|
+
`PrismaReplicas` also exposes a `layerEffect` constructor (for when the replica configs
|
|
638
|
+
come from an Effect, e.g. a config service) and a `layerFromClients` helper (for passing
|
|
639
|
+
already-constructed `PrismaClient` instances whose lifecycle you manage yourself).
|
|
640
|
+
|
|
641
|
+
Each replica client created by `PrismaReplicas.layer` is automatically disconnected when
|
|
642
|
+
the layer scope ends (identical to `PrismaClient.layer`).
|
|
643
|
+
|
|
644
|
+
#### Routing rules
|
|
645
|
+
|
|
646
|
+
With `PrismaReplicas` present:
|
|
647
|
+
|
|
648
|
+
| Operation | Routed to |
|
|
649
|
+
|---|---|
|
|
650
|
+
| `findUnique`, `findUniqueOrThrow`, `findFirst`, `findFirstOrThrow`, `findMany` | Random replica |
|
|
651
|
+
| `count`, `aggregate`, `groupBy` | Random replica |
|
|
652
|
+
| `create`, `createMany`, `createManyAndReturn`, `upsert` | Primary |
|
|
653
|
+
| `update`, `updateMany`, `updateManyAndReturn` | Primary |
|
|
654
|
+
| `delete`, `deleteMany` | Primary |
|
|
655
|
+
| `$transaction`, `$transactionWith`, `$isolatedTransaction*` | Primary |
|
|
656
|
+
| `$executeRaw*`, `$queryRaw*` | Primary (by default - SQL intent cannot be detected automatically) |
|
|
657
|
+
| Any operation inside an active `$transaction` | Transaction client (replicas ignored) |
|
|
658
|
+
|
|
659
|
+
Without `PrismaReplicas`, every operation is routed to the primary - behavior is identical
|
|
660
|
+
to a service constructed without replicas, so you can adopt this feature incrementally.
|
|
661
|
+
|
|
662
|
+
#### `$primary()` / `$replica()` escape hatches
|
|
663
|
+
|
|
664
|
+
Two escape hatches are available on the generated service to force routing:
|
|
665
|
+
|
|
666
|
+
- `prisma.$primary()` returns a sub-service where **all** operations (including reads)
|
|
667
|
+
are pinned to the primary client. Useful for read-your-own-writes scenarios or when you
|
|
668
|
+
need strong consistency.
|
|
669
|
+
- `prisma.$replica()` returns a sub-service where **all** operations (including writes
|
|
670
|
+
and raw SQL) are pinned to a random replica. If no replicas are configured, falls back
|
|
671
|
+
to the primary.
|
|
672
|
+
|
|
673
|
+
```typescript
|
|
674
|
+
const program = Effect.gen(function* () {
|
|
675
|
+
const prisma = yield* Prisma
|
|
676
|
+
|
|
677
|
+
// Auto-routed: reads go to a random replica
|
|
678
|
+
const users = yield* prisma.user.findMany()
|
|
679
|
+
|
|
680
|
+
// Force the primary (e.g. after a recent write where replication lag matters)
|
|
681
|
+
const fresh = yield* prisma.$primary().user.findMany()
|
|
682
|
+
|
|
683
|
+
// Force a replica, even for raw SQL
|
|
684
|
+
const stats = yield* prisma.$replica().$queryRawUnsafe<Array<{ n: number }>>(
|
|
685
|
+
"SELECT count(*) as n FROM User",
|
|
686
|
+
)
|
|
687
|
+
})
|
|
688
|
+
```
|
|
689
|
+
|
|
690
|
+
Sub-services returned by `$primary()` / `$replica()` implement the full `IPrismaService`
|
|
691
|
+
interface and themselves expose `$primary()` / `$replica()`, so chaining type-checks:
|
|
692
|
+
|
|
693
|
+
```typescript
|
|
694
|
+
// These all type-check and resolve to the expected pinned sub-service:
|
|
695
|
+
prisma.$primary().$replica().user.findMany()
|
|
696
|
+
prisma.$replica().$primary().$transaction(/* ... */)
|
|
697
|
+
```
|
|
698
|
+
|
|
699
|
+
#### Notes
|
|
700
|
+
|
|
701
|
+
- Transactions always execute against the primary - replicas cannot participate in a
|
|
702
|
+
multi-statement transaction. Inside a `$transaction` callback, every operation (reads
|
|
703
|
+
included) uses the transaction client.
|
|
704
|
+
- Raw SQL (`$executeRaw*` / `$queryRaw*`) defaults to the primary because the generator
|
|
705
|
+
cannot inspect the SQL to decide whether it is a read or a write. Use `$replica()` to
|
|
706
|
+
opt a specific raw query into replica routing.
|
|
707
|
+
- Replica selection is uniformly random. If you need weighted or round-robin selection,
|
|
708
|
+
provide your own `PrismaClient` instances via `PrismaReplicas.layerFromClients` and wrap
|
|
709
|
+
the selection in your own logic, or open an issue.
|
package/dist/index.js
CHANGED
|
@@ -115,7 +115,7 @@ function generateRawSqlOperations(customError, enableTelemetry) {
|
|
|
115
115
|
: `Effect.fnUntraced(${generatorFn})`;
|
|
116
116
|
return `
|
|
117
117
|
$executeRaw: ${wrapTelemetry("Prisma.$executeRaw", `function* (args) {
|
|
118
|
-
const actualClient = yield*
|
|
118
|
+
const actualClient = yield* writeClient(client, pin);
|
|
119
119
|
return yield* Effect.tryPromise<any, ${errorType}>({
|
|
120
120
|
try: () => (Array.isArray(args) ? actualClient.$executeRaw(args[0], ...args.slice(1)) : actualClient.$executeRaw(args)) as any,
|
|
121
121
|
catch: (error) => mapError(error, "$executeRaw", "Prisma")
|
|
@@ -123,7 +123,7 @@ function generateRawSqlOperations(customError, enableTelemetry) {
|
|
|
123
123
|
}`)},
|
|
124
124
|
|
|
125
125
|
$executeRawUnsafe: ${wrapTelemetry("Prisma.$executeRawUnsafe", `function* (query, ...values) {
|
|
126
|
-
const actualClient = yield*
|
|
126
|
+
const actualClient = yield* writeClient(client, pin);
|
|
127
127
|
return yield* Effect.tryPromise<any, ${errorType}>({
|
|
128
128
|
try: () => actualClient.$executeRawUnsafe(query, ...values) as any,
|
|
129
129
|
catch: (error) => mapError(error, "$executeRawUnsafe", "Prisma")
|
|
@@ -131,7 +131,7 @@ function generateRawSqlOperations(customError, enableTelemetry) {
|
|
|
131
131
|
}`)},
|
|
132
132
|
|
|
133
133
|
$queryRaw: ${wrapTelemetry("Prisma.$queryRaw", `function* (args) {
|
|
134
|
-
const actualClient = yield*
|
|
134
|
+
const actualClient = yield* writeClient(client, pin);
|
|
135
135
|
return yield* Effect.tryPromise<any, ${errorType}>({
|
|
136
136
|
try: () => (Array.isArray(args) ? actualClient.$queryRaw(args[0], ...args.slice(1)) : actualClient.$queryRaw(args)) as any,
|
|
137
137
|
catch: (error) => mapError(error, "$queryRaw", "Prisma")
|
|
@@ -139,7 +139,7 @@ function generateRawSqlOperations(customError, enableTelemetry) {
|
|
|
139
139
|
}`)},
|
|
140
140
|
|
|
141
141
|
$queryRawUnsafe: ${wrapTelemetry("Prisma.$queryRawUnsafe", `function* (query, ...values) {
|
|
142
|
-
const actualClient = yield*
|
|
142
|
+
const actualClient = yield* writeClient(client, pin);
|
|
143
143
|
return yield* Effect.tryPromise<any, ${errorType}>({
|
|
144
144
|
try: () => actualClient.$queryRawUnsafe(query, ...values) as any,
|
|
145
145
|
catch: (error) => mapError(error, "$queryRawUnsafe", "Prisma")
|
|
@@ -306,6 +306,17 @@ function generatePrismaInterface(models, customError, supportsManyAndReturn) {
|
|
|
306
306
|
*/
|
|
307
307
|
export interface IPrismaService {
|
|
308
308
|
client: BasePrismaClient
|
|
309
|
+
// Read-replica routing escape hatches
|
|
310
|
+
/**
|
|
311
|
+
* Return a sub-service where all operations (including reads) are pinned to the primary client.
|
|
312
|
+
* Useful when you need strong consistency for a specific read (e.g. read-your-own-writes).
|
|
313
|
+
*/
|
|
314
|
+
$primary: () => IPrismaService
|
|
315
|
+
/**
|
|
316
|
+
* Return a sub-service where all operations (including writes and raw queries) are pinned to a random replica.
|
|
317
|
+
* If no replicas are configured, falls back to the primary client.
|
|
318
|
+
*/
|
|
319
|
+
$replica: () => IPrismaService
|
|
309
320
|
// Transaction operations
|
|
310
321
|
$transaction: <R, E, A>(
|
|
311
322
|
effect: EffectType<A, E, R>
|
|
@@ -352,6 +363,150 @@ ${modelInterfaces}
|
|
|
352
363
|
function capitalize(str) {
|
|
353
364
|
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
354
365
|
}
|
|
366
|
+
/**
|
|
367
|
+
* Generate the PrismaReplicas Context.Service class declaration.
|
|
368
|
+
* This is an optional service that, when provided in the layer context, causes read
|
|
369
|
+
* operations to be routed to a randomly selected replica instead of the primary client.
|
|
370
|
+
*/
|
|
371
|
+
function generatePrismaReplicasClass() {
|
|
372
|
+
return `/**
|
|
373
|
+
* Context tag for read replica clients.
|
|
374
|
+
*
|
|
375
|
+
* When provided alongside \`PrismaClient\`, read operations (\`findUnique\`, \`findFirst\`,
|
|
376
|
+
* \`findMany\`, \`count\`, \`aggregate\`, \`groupBy\`, etc.) are routed to a random replica.
|
|
377
|
+
* Write operations, transactions, and raw SQL always use the primary client by default.
|
|
378
|
+
*
|
|
379
|
+
* Use \`prisma.$primary()\` / \`prisma.$replica()\` on the service to force routing.
|
|
380
|
+
*
|
|
381
|
+
* @example
|
|
382
|
+
* const AppLayer = Layer.mergeAll(
|
|
383
|
+
* Prisma.layer({ adapter: primaryAdapter }),
|
|
384
|
+
* PrismaReplicas.layer([{ adapter: replica1Adapter }, { adapter: replica2Adapter }])
|
|
385
|
+
* )
|
|
386
|
+
*/
|
|
387
|
+
export class PrismaReplicas extends Context.Service<PrismaReplicas, ReadonlyArray<BasePrismaClient>>()("PrismaReplicas") {
|
|
388
|
+
/**
|
|
389
|
+
* Create a PrismaReplicas layer from an array of PrismaClient constructor options.
|
|
390
|
+
* Each replica client will be automatically disconnected when the layer scope ends.
|
|
391
|
+
*/
|
|
392
|
+
static layer: (
|
|
393
|
+
configs: ReadonlyArray<ConstructorParameters<typeof BasePrismaClient>[0]>
|
|
394
|
+
) => Layer.Layer<PrismaReplicas, never, never> = (configs) => Layer.effect(
|
|
395
|
+
PrismaReplicas,
|
|
396
|
+
Effect.gen(function* () {
|
|
397
|
+
const clients: ReadonlyArray<BasePrismaClient> = configs.map((c) => new BasePrismaClient(c))
|
|
398
|
+
yield* Effect.addFinalizer(() =>
|
|
399
|
+
Effect.promise(() =>
|
|
400
|
+
Promise.all(clients.map((c) => c.$disconnect())).then(() => undefined)
|
|
401
|
+
)
|
|
402
|
+
)
|
|
403
|
+
return clients
|
|
404
|
+
})
|
|
405
|
+
)
|
|
406
|
+
|
|
407
|
+
/**
|
|
408
|
+
* Create a PrismaReplicas layer from an Effect that yields the replica configs.
|
|
409
|
+
* Useful when replica configuration depends on an Effect service (e.g. config lookup).
|
|
410
|
+
*/
|
|
411
|
+
static layerEffect: <R, E>(
|
|
412
|
+
configsEffect: EffectType<ReadonlyArray<ConstructorParameters<typeof BasePrismaClient>[0]>, E, R>
|
|
413
|
+
) => Layer.Layer<PrismaReplicas, E, Exclude<R, Scope.Scope>> = (configsEffect) => Layer.effect(
|
|
414
|
+
PrismaReplicas,
|
|
415
|
+
Effect.gen(function* () {
|
|
416
|
+
const configs = yield* configsEffect
|
|
417
|
+
const clients: ReadonlyArray<BasePrismaClient> = configs.map((c) => new BasePrismaClient(c))
|
|
418
|
+
yield* Effect.addFinalizer(() =>
|
|
419
|
+
Effect.promise(() =>
|
|
420
|
+
Promise.all(clients.map((c) => c.$disconnect())).then(() => undefined)
|
|
421
|
+
)
|
|
422
|
+
)
|
|
423
|
+
return clients
|
|
424
|
+
})
|
|
425
|
+
)
|
|
426
|
+
|
|
427
|
+
/**
|
|
428
|
+
* Create a PrismaReplicas layer from an array of already-constructed PrismaClient instances.
|
|
429
|
+
* The caller is responsible for managing the lifecycle of the provided clients.
|
|
430
|
+
*/
|
|
431
|
+
static layerFromClients: (
|
|
432
|
+
clients: ReadonlyArray<BasePrismaClient>
|
|
433
|
+
) => Layer.Layer<PrismaReplicas, never, never> = (clients) => Layer.succeed(PrismaReplicas, clients)
|
|
434
|
+
}`;
|
|
435
|
+
}
|
|
436
|
+
/**
|
|
437
|
+
* Generate the read/write routing helpers.
|
|
438
|
+
* These helpers consult the current transaction context and the optional PrismaReplicas service
|
|
439
|
+
* to decide which underlying client should handle a given operation.
|
|
440
|
+
*/
|
|
441
|
+
function generateRoutingHelpers() {
|
|
442
|
+
return `/** Pin parameter controlling replica routing for a sub-service. */
|
|
443
|
+
type PrismaRoutingPin = "primary" | "replica" | undefined
|
|
444
|
+
|
|
445
|
+
/** Pick a random replica from the provided array. Caller ensures non-empty. */
|
|
446
|
+
const pickReplica = (replicas: ReadonlyArray<BasePrismaClient>): BasePrismaClient =>
|
|
447
|
+
replicas[Math.floor(Math.random() * replicas.length)] as BasePrismaClient
|
|
448
|
+
|
|
449
|
+
/**
|
|
450
|
+
* Resolve the client to use for a READ operation.
|
|
451
|
+
*
|
|
452
|
+
* Priority:
|
|
453
|
+
* 1. Transaction client (if inside \`$transaction\`)
|
|
454
|
+
* 2. Primary client (if pin === "primary")
|
|
455
|
+
* 3. Random replica (if replicas are configured and non-empty)
|
|
456
|
+
* 4. Primary client (fallback)
|
|
457
|
+
*/
|
|
458
|
+
const readClient = (client: BasePrismaClient, pin: PrismaRoutingPin) => Effect.gen(function* () {
|
|
459
|
+
const tx = yield* Effect.serviceOption(PrismaTransactionClientService)
|
|
460
|
+
if (Option.isSome(tx)) return tx.value as BasePrismaClient
|
|
461
|
+
if (pin === "primary") return client
|
|
462
|
+
const replicas = yield* Effect.serviceOption(PrismaReplicas)
|
|
463
|
+
if (Option.isNone(replicas) || replicas.value.length === 0) return client
|
|
464
|
+
return pickReplica(replicas.value)
|
|
465
|
+
})
|
|
466
|
+
|
|
467
|
+
/**
|
|
468
|
+
* Resolve the client to use for a WRITE or raw SQL operation.
|
|
469
|
+
*
|
|
470
|
+
* Priority:
|
|
471
|
+
* 1. Transaction client (if inside \`$transaction\`)
|
|
472
|
+
* 2. Random replica (only if pin === "replica" AND replicas are configured)
|
|
473
|
+
* 3. Primary client (default - writes/raw always go here unless explicitly pinned to replica)
|
|
474
|
+
*/
|
|
475
|
+
const writeClient = (client: BasePrismaClient, pin: PrismaRoutingPin) => Effect.gen(function* () {
|
|
476
|
+
const tx = yield* Effect.serviceOption(PrismaTransactionClientService)
|
|
477
|
+
if (Option.isSome(tx)) return tx.value as BasePrismaClient
|
|
478
|
+
if (pin === "replica") {
|
|
479
|
+
const replicas = yield* Effect.serviceOption(PrismaReplicas)
|
|
480
|
+
if (Option.isSome(replicas) && replicas.value.length > 0) {
|
|
481
|
+
return pickReplica(replicas.value)
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
return client
|
|
485
|
+
})`;
|
|
486
|
+
}
|
|
487
|
+
/**
|
|
488
|
+
* Wrap a service body string in the buildService(pin) closure that also constructs
|
|
489
|
+
* the primary and replica sub-services. This is shared between both error-service variants.
|
|
490
|
+
*
|
|
491
|
+
* The generated scaffolding:
|
|
492
|
+
* 1. Declares \`primaryService\` / \`replicaService\` with definite-assignment assertions.
|
|
493
|
+
* 2. Defines \`buildService(pin)\` - captures \`pin\` so every read/write helper call
|
|
494
|
+
* resolves to the correct client at runtime.
|
|
495
|
+
* 3. Builds the primary and replica sub-services first, then returns the main service
|
|
496
|
+
* with pin=undefined (auto-routing).
|
|
497
|
+
*/
|
|
498
|
+
function wrapBuildService(body) {
|
|
499
|
+
return `let primaryService!: IPrismaService;
|
|
500
|
+
let replicaService!: IPrismaService;
|
|
501
|
+
|
|
502
|
+
const buildService = (pin: PrismaRoutingPin): IPrismaService => ({
|
|
503
|
+
${body}
|
|
504
|
+
});
|
|
505
|
+
|
|
506
|
+
primaryService = buildService("primary");
|
|
507
|
+
replicaService = buildService("replica");
|
|
508
|
+
return buildService(undefined);`;
|
|
509
|
+
}
|
|
355
510
|
function generateModelOperations(models, customError, enableTelemetry, supportsManyAndReturn) {
|
|
356
511
|
return models
|
|
357
512
|
.map((model) => {
|
|
@@ -383,7 +538,7 @@ function generateModelOperations(models, customError, enableTelemetry, supportsM
|
|
|
383
538
|
// Implementation without explicit types - the IPrismaService interface provides all type checking
|
|
384
539
|
return ` ${modelNameCamel}: {
|
|
385
540
|
findUnique: ${wrapTelemetry(`Prisma.${modelNameCamel}.findUnique`, `function* (args) {
|
|
386
|
-
const actualClient = yield*
|
|
541
|
+
const actualClient = yield* readClient(client, pin);
|
|
387
542
|
return yield* Effect.tryPromise<any, ${errorType("PrismaFindError")}>({
|
|
388
543
|
try: () => actualClient.${modelNameCamel}.findUnique(args as any)${promiseCast()},
|
|
389
544
|
catch: (error) => ${mapperFn("mapFindError")}(error, "findUnique", "${modelName}")
|
|
@@ -391,7 +546,7 @@ function generateModelOperations(models, customError, enableTelemetry, supportsM
|
|
|
391
546
|
}`)},
|
|
392
547
|
|
|
393
548
|
findUniqueOrThrow: ${wrapTelemetry(`Prisma.${modelNameCamel}.findUniqueOrThrow`, `function* (args) {
|
|
394
|
-
const actualClient = yield*
|
|
549
|
+
const actualClient = yield* readClient(client, pin);
|
|
395
550
|
return yield* Effect.tryPromise<any, ${errorType("PrismaFindOrThrowError")}>({
|
|
396
551
|
try: () => actualClient.${modelNameCamel}.findUniqueOrThrow(args as any)${promiseCast()},
|
|
397
552
|
catch: (error) => ${mapperFn("mapFindOrThrowError")}(error, "findUniqueOrThrow", "${modelName}")
|
|
@@ -399,7 +554,7 @@ function generateModelOperations(models, customError, enableTelemetry, supportsM
|
|
|
399
554
|
}`)},
|
|
400
555
|
|
|
401
556
|
findFirst: ${wrapTelemetry(`Prisma.${modelNameCamel}.findFirst`, `function* (args) {
|
|
402
|
-
const actualClient = yield*
|
|
557
|
+
const actualClient = yield* readClient(client, pin);
|
|
403
558
|
return yield* Effect.tryPromise<any, ${errorType("PrismaFindError")}>({
|
|
404
559
|
try: () => actualClient.${modelNameCamel}.findFirst(args as any)${promiseCast()},
|
|
405
560
|
catch: (error) => ${mapperFn("mapFindError")}(error, "findFirst", "${modelName}")
|
|
@@ -407,7 +562,7 @@ function generateModelOperations(models, customError, enableTelemetry, supportsM
|
|
|
407
562
|
}`)},
|
|
408
563
|
|
|
409
564
|
findFirstOrThrow: ${wrapTelemetry(`Prisma.${modelNameCamel}.findFirstOrThrow`, `function* (args) {
|
|
410
|
-
const actualClient = yield*
|
|
565
|
+
const actualClient = yield* readClient(client, pin);
|
|
411
566
|
return yield* Effect.tryPromise<any, ${errorType("PrismaFindOrThrowError")}>({
|
|
412
567
|
try: () => actualClient.${modelNameCamel}.findFirstOrThrow(args as any)${promiseCast()},
|
|
413
568
|
catch: (error) => ${mapperFn("mapFindOrThrowError")}(error, "findFirstOrThrow", "${modelName}")
|
|
@@ -415,7 +570,7 @@ function generateModelOperations(models, customError, enableTelemetry, supportsM
|
|
|
415
570
|
}`)},
|
|
416
571
|
|
|
417
572
|
findMany: ${wrapTelemetry(`Prisma.${modelNameCamel}.findMany`, `function* (args) {
|
|
418
|
-
const actualClient = yield*
|
|
573
|
+
const actualClient = yield* readClient(client, pin);
|
|
419
574
|
return yield* Effect.tryPromise<any, ${errorType("PrismaFindError")}>({
|
|
420
575
|
try: () => actualClient.${modelNameCamel}.findMany(args as any)${promiseCast()},
|
|
421
576
|
catch: (error) => ${mapperFn("mapFindError")}(error, "findMany", "${modelName}")
|
|
@@ -423,7 +578,7 @@ function generateModelOperations(models, customError, enableTelemetry, supportsM
|
|
|
423
578
|
}`)},
|
|
424
579
|
|
|
425
580
|
create: ${wrapTelemetry(`Prisma.${modelNameCamel}.create`, `function* (args) {
|
|
426
|
-
const actualClient = yield*
|
|
581
|
+
const actualClient = yield* writeClient(client, pin);
|
|
427
582
|
return yield* Effect.tryPromise<any, ${errorType("PrismaCreateError")}>({
|
|
428
583
|
try: () => actualClient.${modelNameCamel}.create(args as any)${promiseCast()},
|
|
429
584
|
catch: (error) => ${mapperFn("mapCreateError")}(error, "create", "${modelName}")
|
|
@@ -431,7 +586,7 @@ function generateModelOperations(models, customError, enableTelemetry, supportsM
|
|
|
431
586
|
}`)},
|
|
432
587
|
|
|
433
588
|
createMany: ${wrapTelemetry(`Prisma.${modelNameCamel}.createMany`, `function* (args) {
|
|
434
|
-
const actualClient = yield*
|
|
589
|
+
const actualClient = yield* writeClient(client, pin);
|
|
435
590
|
return yield* Effect.tryPromise<any, ${errorType("PrismaCreateError")}>({
|
|
436
591
|
try: () => actualClient.${modelNameCamel}.createMany(args as any),
|
|
437
592
|
catch: (error) => ${mapperFn("mapCreateError")}(error, "createMany", "${modelName}")
|
|
@@ -440,7 +595,7 @@ function generateModelOperations(models, customError, enableTelemetry, supportsM
|
|
|
440
595
|
? `
|
|
441
596
|
|
|
442
597
|
createManyAndReturn: ${wrapTelemetry(`Prisma.${modelNameCamel}.createManyAndReturn`, `function* (args) {
|
|
443
|
-
const actualClient = yield*
|
|
598
|
+
const actualClient = yield* writeClient(client, pin);
|
|
444
599
|
return yield* Effect.tryPromise<any, ${errorType("PrismaCreateError")}>({
|
|
445
600
|
try: () => actualClient.${modelNameCamel}.createManyAndReturn(args as any)${promiseCast()},
|
|
446
601
|
catch: (error) => ${mapperFn("mapCreateError")}(error, "createManyAndReturn", "${modelName}")
|
|
@@ -449,7 +604,7 @@ function generateModelOperations(models, customError, enableTelemetry, supportsM
|
|
|
449
604
|
: ""}
|
|
450
605
|
|
|
451
606
|
delete: ${wrapTelemetry(`Prisma.${modelNameCamel}.delete`, `function* (args) {
|
|
452
|
-
const actualClient = yield*
|
|
607
|
+
const actualClient = yield* writeClient(client, pin);
|
|
453
608
|
return yield* Effect.tryPromise<any, ${errorType("PrismaDeleteError")}>({
|
|
454
609
|
try: () => actualClient.${modelNameCamel}.delete(args as any)${promiseCast()},
|
|
455
610
|
catch: (error) => ${mapperFn("mapDeleteError")}(error, "delete", "${modelName}")
|
|
@@ -457,7 +612,7 @@ function generateModelOperations(models, customError, enableTelemetry, supportsM
|
|
|
457
612
|
}`)},
|
|
458
613
|
|
|
459
614
|
update: ${wrapTelemetry(`Prisma.${modelNameCamel}.update`, `function* (args) {
|
|
460
|
-
const actualClient = yield*
|
|
615
|
+
const actualClient = yield* writeClient(client, pin);
|
|
461
616
|
return yield* Effect.tryPromise<any, ${errorType("PrismaUpdateError")}>({
|
|
462
617
|
try: () => actualClient.${modelNameCamel}.update(args as any)${promiseCast()},
|
|
463
618
|
catch: (error) => ${mapperFn("mapUpdateError")}(error, "update", "${modelName}")
|
|
@@ -465,7 +620,7 @@ function generateModelOperations(models, customError, enableTelemetry, supportsM
|
|
|
465
620
|
}`)},
|
|
466
621
|
|
|
467
622
|
deleteMany: ${wrapTelemetry(`Prisma.${modelNameCamel}.deleteMany`, `function* (args) {
|
|
468
|
-
const actualClient = yield*
|
|
623
|
+
const actualClient = yield* writeClient(client, pin);
|
|
469
624
|
return yield* Effect.tryPromise<any, ${errorType("PrismaDeleteManyError")}>({
|
|
470
625
|
try: () => actualClient.${modelNameCamel}.deleteMany(args as any),
|
|
471
626
|
catch: (error) => ${mapperFn("mapDeleteManyError")}(error, "deleteMany", "${modelName}")
|
|
@@ -473,7 +628,7 @@ function generateModelOperations(models, customError, enableTelemetry, supportsM
|
|
|
473
628
|
}`)},
|
|
474
629
|
|
|
475
630
|
updateMany: ${wrapTelemetry(`Prisma.${modelNameCamel}.updateMany`, `function* (args) {
|
|
476
|
-
const actualClient = yield*
|
|
631
|
+
const actualClient = yield* writeClient(client, pin);
|
|
477
632
|
return yield* Effect.tryPromise<any, ${errorType("PrismaUpdateManyError")}>({
|
|
478
633
|
try: () => actualClient.${modelNameCamel}.updateMany(args as any),
|
|
479
634
|
catch: (error) => ${mapperFn("mapUpdateManyError")}(error, "updateMany", "${modelName}")
|
|
@@ -482,7 +637,7 @@ function generateModelOperations(models, customError, enableTelemetry, supportsM
|
|
|
482
637
|
? `
|
|
483
638
|
|
|
484
639
|
updateManyAndReturn: ${wrapTelemetry(`Prisma.${modelNameCamel}.updateManyAndReturn`, `function* (args) {
|
|
485
|
-
const actualClient = yield*
|
|
640
|
+
const actualClient = yield* writeClient(client, pin);
|
|
486
641
|
return yield* Effect.tryPromise<any, ${errorType("PrismaUpdateManyError")}>({
|
|
487
642
|
try: () => actualClient.${modelNameCamel}.updateManyAndReturn(args as any)${promiseCast()},
|
|
488
643
|
catch: (error) => ${mapperFn("mapUpdateManyError")}(error, "updateManyAndReturn", "${modelName}")
|
|
@@ -491,7 +646,7 @@ function generateModelOperations(models, customError, enableTelemetry, supportsM
|
|
|
491
646
|
: ""}
|
|
492
647
|
|
|
493
648
|
upsert: ${wrapTelemetry(`Prisma.${modelNameCamel}.upsert`, `function* (args) {
|
|
494
|
-
const actualClient = yield*
|
|
649
|
+
const actualClient = yield* writeClient(client, pin);
|
|
495
650
|
return yield* Effect.tryPromise<any, ${errorType("PrismaCreateError")}>({
|
|
496
651
|
try: () => actualClient.${modelNameCamel}.upsert(args as any)${promiseCast()},
|
|
497
652
|
catch: (error) => ${mapperFn("mapCreateError")}(error, "upsert", "${modelName}")
|
|
@@ -500,7 +655,7 @@ function generateModelOperations(models, customError, enableTelemetry, supportsM
|
|
|
500
655
|
|
|
501
656
|
// Aggregation operations
|
|
502
657
|
count: ${wrapTelemetry(`Prisma.${modelNameCamel}.count`, `function* (args) {
|
|
503
|
-
const actualClient = yield*
|
|
658
|
+
const actualClient = yield* readClient(client, pin);
|
|
504
659
|
return yield* Effect.tryPromise<any, ${errorType("PrismaFindError")}>({
|
|
505
660
|
try: () => actualClient.${modelNameCamel}.count(args as any)${promiseCast()},
|
|
506
661
|
catch: (error) => ${mapperFn("mapFindError")}(error, "count", "${modelName}")
|
|
@@ -508,7 +663,7 @@ function generateModelOperations(models, customError, enableTelemetry, supportsM
|
|
|
508
663
|
}`)},
|
|
509
664
|
|
|
510
665
|
aggregate: ${wrapTelemetry(`Prisma.${modelNameCamel}.aggregate`, `function* (args) {
|
|
511
|
-
const actualClient = yield*
|
|
666
|
+
const actualClient = yield* readClient(client, pin);
|
|
512
667
|
return yield* Effect.tryPromise<any, ${errorType("PrismaFindError")}>({
|
|
513
668
|
try: () => actualClient.${modelNameCamel}.aggregate(args as any)${strongPromiseCast()},
|
|
514
669
|
catch: (error) => ${mapperFn("mapFindError")}(error, "aggregate", "${modelName}")
|
|
@@ -516,7 +671,7 @@ function generateModelOperations(models, customError, enableTelemetry, supportsM
|
|
|
516
671
|
}`)},
|
|
517
672
|
|
|
518
673
|
groupBy: ${wrapTelemetry(`Prisma.${modelNameCamel}.groupBy`, `function* (args) {
|
|
519
|
-
const actualClient = yield*
|
|
674
|
+
const actualClient = yield* readClient(client, pin);
|
|
520
675
|
return yield* Effect.tryPromise<any, ${errorType("PrismaFindError")}>({
|
|
521
676
|
try: () => actualClient.${modelNameCamel}.groupBy(args as any)${strongPromiseCast()},
|
|
522
677
|
catch: (error) => ${mapperFn("mapFindError")}(error, "groupBy", "${modelName}")
|
|
@@ -681,20 +836,15 @@ export class PrismaClient extends Context.Service<PrismaClient, BasePrismaClient
|
|
|
681
836
|
*/
|
|
682
837
|
export class PrismaTransactionClientService extends Context.Service<PrismaTransactionClientService, PrismaNamespace.TransactionClient>()("PrismaTransactionClientService") {}
|
|
683
838
|
|
|
839
|
+
${generatePrismaReplicasClass()}
|
|
840
|
+
|
|
684
841
|
// Re-export the custom error type for convenience
|
|
685
842
|
export { ${customError.className} }
|
|
686
843
|
|
|
687
844
|
// Use the user-provided error mapper
|
|
688
845
|
const mapError = mapPrismaError
|
|
689
846
|
|
|
690
|
-
|
|
691
|
-
* Helper to get the current client - either the transaction client if in a transaction,
|
|
692
|
-
* or the root client if not. Uses Effect.serviceOption to detect transaction context.
|
|
693
|
-
*/
|
|
694
|
-
const clientOrTx = (client: BasePrismaClient) => Effect.map(
|
|
695
|
-
Effect.serviceOption(PrismaTransactionClientService),
|
|
696
|
-
Option.getOrElse(() => client),
|
|
697
|
-
);
|
|
847
|
+
${generateRoutingHelpers()}
|
|
698
848
|
|
|
699
849
|
/**
|
|
700
850
|
* Like Effect.acquireUseRelease, but allows the release function to fail.
|
|
@@ -804,8 +954,13 @@ const $begin = (
|
|
|
804
954
|
const makePrismaService = Effect.gen(function* () {
|
|
805
955
|
const client = yield* PrismaClient;
|
|
806
956
|
|
|
807
|
-
|
|
957
|
+
let primaryService!: IPrismaService;
|
|
958
|
+
let replicaService!: IPrismaService;
|
|
959
|
+
|
|
960
|
+
const buildService = (pin: PrismaRoutingPin): IPrismaService => ({
|
|
808
961
|
client,
|
|
962
|
+
$primary: () => primaryService,
|
|
963
|
+
$replica: () => replicaService,
|
|
809
964
|
/**
|
|
810
965
|
* Execute an effect within a database transaction.
|
|
811
966
|
* All operations within the effect will be atomic - they either all succeed or all fail.
|
|
@@ -1018,9 +1173,11 @@ const makePrismaService = Effect.gen(function* () {
|
|
|
1018
1173
|
${rawSqlOperations}
|
|
1019
1174
|
|
|
1020
1175
|
${modelOperations}
|
|
1021
|
-
};
|
|
1176
|
+
});
|
|
1022
1177
|
|
|
1023
|
-
|
|
1178
|
+
primaryService = buildService("primary");
|
|
1179
|
+
replicaService = buildService("replica");
|
|
1180
|
+
return buildService(undefined);
|
|
1024
1181
|
});
|
|
1025
1182
|
|
|
1026
1183
|
export class Prisma extends Context.Service<Prisma, IPrismaService>()("Prisma") {
|
|
@@ -1221,6 +1378,8 @@ export class PrismaClient extends Context.Service<PrismaClient, BasePrismaClient
|
|
|
1221
1378
|
*/
|
|
1222
1379
|
export class PrismaTransactionClientService extends Context.Service<PrismaTransactionClientService, PrismaNamespace.TransactionClient>()("PrismaTransactionClientService") {}
|
|
1223
1380
|
|
|
1381
|
+
${generatePrismaReplicasClass()}
|
|
1382
|
+
|
|
1224
1383
|
export class PrismaUniqueConstraintError extends Data.TaggedError("PrismaUniqueConstraintError")<{
|
|
1225
1384
|
cause: PrismaNamespace.PrismaClientKnownRequestError
|
|
1226
1385
|
operation: string
|
|
@@ -1572,14 +1731,7 @@ const mapUpdateManyError = (error: unknown, operation: string, model: string): P
|
|
|
1572
1731
|
throw error;
|
|
1573
1732
|
}
|
|
1574
1733
|
|
|
1575
|
-
|
|
1576
|
-
* Helper to get the current client - either the transaction client if in a transaction,
|
|
1577
|
-
* or the root client if not. Uses Effect.serviceOption to detect transaction context.
|
|
1578
|
-
*/
|
|
1579
|
-
const clientOrTx = (client: BasePrismaClient) => Effect.map(
|
|
1580
|
-
Effect.serviceOption(PrismaTransactionClientService),
|
|
1581
|
-
Option.getOrElse(() => client),
|
|
1582
|
-
);
|
|
1734
|
+
${generateRoutingHelpers()}
|
|
1583
1735
|
|
|
1584
1736
|
/**
|
|
1585
1737
|
* Like Effect.acquireUseRelease, but allows the release function to fail.
|
|
@@ -1689,8 +1841,13 @@ const $begin = (
|
|
|
1689
1841
|
const makePrismaService = Effect.gen(function* () {
|
|
1690
1842
|
const client = yield* PrismaClient;
|
|
1691
1843
|
|
|
1692
|
-
|
|
1844
|
+
let primaryService!: IPrismaService;
|
|
1845
|
+
let replicaService!: IPrismaService;
|
|
1846
|
+
|
|
1847
|
+
const buildService = (pin: PrismaRoutingPin): IPrismaService => ({
|
|
1693
1848
|
client,
|
|
1849
|
+
$primary: () => primaryService,
|
|
1850
|
+
$replica: () => replicaService,
|
|
1694
1851
|
/**
|
|
1695
1852
|
* Execute an effect within a database transaction.
|
|
1696
1853
|
* All operations within the effect will be atomic - they either all succeed or all fail.
|
|
@@ -1902,9 +2059,11 @@ const makePrismaService = Effect.gen(function* () {
|
|
|
1902
2059
|
${rawSqlOperations}
|
|
1903
2060
|
|
|
1904
2061
|
${modelOperations}
|
|
1905
|
-
};
|
|
2062
|
+
});
|
|
1906
2063
|
|
|
1907
|
-
|
|
2064
|
+
primaryService = buildService("primary");
|
|
2065
|
+
replicaService = buildService("replica");
|
|
2066
|
+
return buildService(undefined);
|
|
1908
2067
|
});
|
|
1909
2068
|
|
|
1910
2069
|
export class Prisma extends Context.Service<Prisma, IPrismaService>()("Prisma") {
|