@shivaedev/effect-prisma 0.6.2 → 0.6.4

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 (129) hide show
  1. package/CHANGELOG.md +13 -1
  2. package/dist/database.d.ts +4 -4
  3. package/dist/database.d.ts.map +1 -1
  4. package/dist/database.js +12 -12
  5. package/dist/database.js.map +1 -1
  6. package/dist/error.d.ts +5 -5
  7. package/dist/error.d.ts.map +1 -1
  8. package/dist/error.js +1 -2
  9. package/dist/error.js.map +1 -1
  10. package/dist/index.d.ts +3 -3
  11. package/dist/internal/adapters/transaction-settlement.d.ts +10 -0
  12. package/dist/internal/adapters/transaction-settlement.d.ts.map +1 -0
  13. package/dist/internal/adapters/transaction-settlement.js +56 -0
  14. package/dist/internal/adapters/transaction-settlement.js.map +1 -0
  15. package/dist/internal/client-lifecycle.d.ts.map +1 -1
  16. package/dist/internal/client-lifecycle.js.map +1 -1
  17. package/dist/internal/contract-normalization.d.ts.map +1 -1
  18. package/dist/internal/contract-normalization.js +1 -3
  19. package/dist/internal/contract-normalization.js.map +1 -1
  20. package/dist/internal/database-facade.d.ts +24 -0
  21. package/dist/internal/database-facade.d.ts.map +1 -0
  22. package/dist/internal/database-facade.js +84 -0
  23. package/dist/internal/database-facade.js.map +1 -0
  24. package/dist/internal/database-factory.d.ts +4 -44
  25. package/dist/internal/database-factory.d.ts.map +1 -1
  26. package/dist/internal/database-factory.js +11 -84
  27. package/dist/internal/database-factory.js.map +1 -1
  28. package/dist/internal/database-types.d.ts +37 -0
  29. package/dist/internal/database-types.d.ts.map +1 -0
  30. package/dist/internal/database-types.js +2 -0
  31. package/dist/internal/database-types.js.map +1 -0
  32. package/dist/internal/dynamic.d.ts +5 -0
  33. package/dist/internal/dynamic.d.ts.map +1 -0
  34. package/dist/internal/dynamic.js +12 -0
  35. package/dist/internal/dynamic.js.map +1 -0
  36. package/dist/internal/executor.d.ts.map +1 -1
  37. package/dist/internal/promise.d.ts +1 -1
  38. package/dist/internal/promise.d.ts.map +1 -1
  39. package/dist/internal/promise.js +1 -3
  40. package/dist/internal/promise.js.map +1 -1
  41. package/dist/internal/query-execution.d.ts +2 -2
  42. package/dist/internal/query-execution.d.ts.map +1 -1
  43. package/dist/internal/query-execution.js +2 -6
  44. package/dist/internal/query-execution.js.map +1 -1
  45. package/dist/internal/recipe.d.ts.map +1 -1
  46. package/dist/internal/recipe.js +7 -18
  47. package/dist/internal/recipe.js.map +1 -1
  48. package/dist/internal/relation-plan.d.ts +1 -1
  49. package/dist/internal/relation-result.d.ts +4 -0
  50. package/dist/internal/relation-result.d.ts.map +1 -0
  51. package/dist/internal/relation-result.js +19 -0
  52. package/dist/internal/relation-result.js.map +1 -0
  53. package/dist/internal/relation-runtime.d.ts +3 -3
  54. package/dist/internal/relation-runtime.d.ts.map +1 -1
  55. package/dist/internal/relation-runtime.js +9 -34
  56. package/dist/internal/relation-runtime.js.map +1 -1
  57. package/dist/internal/relation-stream.d.ts +3 -3
  58. package/dist/internal/relation-stream.d.ts.map +1 -1
  59. package/dist/internal/relation-stream.js +12 -14
  60. package/dist/internal/relation-stream.js.map +1 -1
  61. package/dist/internal/sqlite-datetime.d.ts +1 -1
  62. package/dist/internal/sqlite-datetime.d.ts.map +1 -1
  63. package/dist/internal/sqlite-datetime.js +3 -1
  64. package/dist/internal/sqlite-datetime.js.map +1 -1
  65. package/dist/internal/sqlite-pragmas.d.ts.map +1 -1
  66. package/dist/internal/sqlite-pragmas.js.map +1 -1
  67. package/dist/internal/testing.d.ts +1 -1
  68. package/dist/internal/testing.d.ts.map +1 -1
  69. package/dist/internal/testing.js +5 -2
  70. package/dist/internal/testing.js.map +1 -1
  71. package/dist/internal/transaction.d.ts +5 -5
  72. package/dist/internal/transaction.d.ts.map +1 -1
  73. package/dist/internal/transaction.js +3 -51
  74. package/dist/internal/transaction.js.map +1 -1
  75. package/dist/relation/include-metadata.d.ts.map +1 -1
  76. package/dist/relation/include.d.ts +2 -2
  77. package/dist/relation/include.d.ts.map +1 -1
  78. package/dist/relation/prisma-methods.d.ts +3 -3
  79. package/dist/relation/prisma-methods.d.ts.map +1 -1
  80. package/dist/relation.d.ts +2 -2
  81. package/dist/relation.d.ts.map +1 -1
  82. package/dist/sqlite.d.ts +3 -3
  83. package/dist/sqlite.d.ts.map +1 -1
  84. package/dist/sqlite.js +14 -12
  85. package/dist/sqlite.js.map +1 -1
  86. package/dist/testing/transaction.d.ts +6 -7
  87. package/dist/testing/transaction.d.ts.map +1 -1
  88. package/dist/testing/transaction.js +2 -2
  89. package/dist/testing/transaction.js.map +1 -1
  90. package/dist/testing/types.d.ts +2 -2
  91. package/dist/testing/types.d.ts.map +1 -1
  92. package/dist/testing/vitest.d.ts +3 -3
  93. package/dist/testing/vitest.d.ts.map +1 -1
  94. package/dist/testing/vitest.js +2 -2
  95. package/dist/testing/vitest.js.map +1 -1
  96. package/dist/testing.d.ts +3 -3
  97. package/package.json +6 -6
  98. package/src/bin/normalize-contract.ts +1 -1
  99. package/src/database.ts +49 -67
  100. package/src/error.ts +9 -19
  101. package/src/index.ts +3 -3
  102. package/src/internal/adapters/transaction-settlement.ts +67 -0
  103. package/src/internal/client-lifecycle.ts +3 -10
  104. package/src/internal/contract-normalization.ts +3 -8
  105. package/src/internal/database-facade.ts +143 -0
  106. package/src/internal/database-factory.ts +30 -298
  107. package/src/internal/database-types.ts +69 -0
  108. package/src/internal/dynamic.ts +16 -0
  109. package/src/internal/executor.ts +1 -4
  110. package/src/internal/promise.ts +3 -11
  111. package/src/internal/query-execution.ts +4 -8
  112. package/src/internal/recipe.ts +19 -84
  113. package/src/internal/relation-plan.ts +1 -1
  114. package/src/internal/relation-result.ts +27 -0
  115. package/src/internal/relation-runtime.ts +43 -133
  116. package/src/internal/relation-stream.ts +22 -48
  117. package/src/internal/sqlite-datetime.ts +10 -23
  118. package/src/internal/sqlite-pragmas.ts +1 -4
  119. package/src/internal/testing.ts +13 -15
  120. package/src/internal/transaction.ts +14 -107
  121. package/src/relation/include-metadata.ts +10 -42
  122. package/src/relation/include.ts +19 -115
  123. package/src/relation/prisma-methods.ts +23 -111
  124. package/src/relation.ts +18 -46
  125. package/src/sqlite.ts +57 -78
  126. package/src/testing/transaction.ts +20 -33
  127. package/src/testing/types.ts +9 -28
  128. package/src/testing/vitest.ts +14 -28
  129. package/src/testing.ts +3 -3
@@ -1,328 +1,60 @@
1
- import type { orm } from "@prisma-next/sql-orm-client";
2
1
  import { Context, Effect, Layer } from "effect";
3
- import { type PrismaError, toPrismaError } from "../error.js";
4
- import type { Relation } from "../relation.js";
5
- import { assertAvailableModelNames } from "./client-lifecycle.js";
6
- import type { AnySqlContract, DatabaseExecutor } from "./executor.js";
7
- import { makeModelRelation } from "./relation-runtime.js";
8
- import { DatabaseTestingTypeId } from "./testing.js";
2
+ import type { PrismaError } from "../error.ts";
3
+ import { type ActiveTransaction, type FacadeScope, makeFacade, runTransaction } from "./database-facade.ts";
9
4
  import {
10
- acquireTransaction,
11
- releaseTestTransaction,
12
- releaseTransaction,
13
- type TransactionOrm,
14
- withTransactionSemaphore,
15
- } from "./transaction.js";
5
+ type DatabaseIdentifier,
6
+ type DatabaseIdentifierLiteral,
7
+ type DatabaseService,
8
+ type DefaultModels,
9
+ internalContextIdentifierPrefix,
10
+ type SqlDatabase,
11
+ } from "./database-types.ts";
12
+ import type { AnySqlContract, DatabaseExecutor } from "./executor.ts";
13
+ import { DatabaseTestingTypeId } from "./testing.ts";
14
+ import { releaseTestTransaction } from "./transaction.ts";
16
15
 
17
- type OrmFor<Contract extends AnySqlContract> = ReturnType<typeof orm<Contract>>;
18
- type DefaultNamespaceId<Contract extends AnySqlContract> =
19
- "__unbound__" extends keyof OrmFor<Contract>
20
- ? "__unbound__"
21
- : "public" extends keyof OrmFor<Contract>
22
- ? "public"
23
- : keyof OrmFor<Contract>;
24
- export type DefaultModels<Contract extends AnySqlContract> =
25
- OrmFor<Contract>[DefaultNamespaceId<Contract>] extends infer Models extends
26
- object
27
- ? Models
28
- : never;
29
-
30
- type IsUnion<Value, Whole = Value> = Value extends unknown
31
- ? [Whole] extends [Value]
32
- ? false
33
- : true
34
- : never;
35
-
36
- type IsSingletonString<Value extends string> =
37
- Record<never, never> extends Record<Value, never> ? false : true;
38
-
39
- const internalContextIdentifierPrefix = "\0@shivaedev/effect-prisma/internal/";
40
16
  let activeTransactionReferenceSequence = 0;
41
17
 
42
- export type DatabaseIdentifierLiteral<Identifier extends string> = [
43
- Identifier,
44
- ] extends [`\0@shivaedev/effect-prisma/internal/${string}`]
45
- ? never
46
- : string extends Identifier
47
- ? never
48
- : true extends IsUnion<Identifier>
49
- ? never
50
- : IsSingletonString<Identifier> extends true
51
- ? Identifier
52
- : never;
53
-
54
- export interface DatabaseIdentifier<
55
- Contract extends AnySqlContract,
56
- Identifier extends string,
57
- > {
58
- readonly _contract: (contract: Contract) => Contract;
59
- readonly _databaseIdentifier: (identifier: Identifier) => Identifier;
60
- }
61
-
62
- type DatabaseModels<
63
- Contract extends AnySqlContract,
64
- Identifier extends string,
65
- > = {
66
- readonly [Model in keyof DefaultModels<Contract>]: Relation<
67
- DefaultModels<Contract>[Model],
68
- Contract,
69
- Model & string,
70
- DatabaseIdentifier<Contract, Identifier>
71
- >;
72
- };
73
-
74
- export type DatabaseService<
75
- Contract extends AnySqlContract,
76
- Identifier extends string,
77
- > = DatabaseModels<Contract, Identifier> & {
78
- transaction<A, E, R>(
79
- program: Effect.Effect<A, E, R> &
80
- (DatabaseIdentifier<Contract, Identifier> extends R ? unknown : never),
81
- ): Effect.Effect<
82
- A,
83
- E | PrismaError,
84
- Exclude<R, DatabaseIdentifier<Contract, Identifier>>
85
- >;
86
- };
87
-
88
- /**
89
- * The driver-independent part of a database definition: the Context service
90
- * carrying the typed facade. Driver entrypoints add their own `layer` options.
91
- */
92
- export interface DatabaseServiceHolder<
93
- Contract extends AnySqlContract,
94
- Identifier extends string,
95
- > extends Context.Service<
96
- DatabaseIdentifier<Contract, Identifier>,
97
- DatabaseService<Contract, Identifier>
98
- > {}
99
-
100
- export type AnyDatabase = Effect.Effect<unknown, never, unknown> & {
101
- readonly layer: (...arguments_: ReadonlyArray<never>) => Layer.Any;
102
- };
103
-
104
- export type DatabaseServiceOf<Database extends AnyDatabase> =
105
- Effect.Success<Database>;
106
-
107
- export interface SqlDatabase<
108
- Contract extends AnySqlContract,
109
- Identifier extends string,
110
- Options,
111
- > extends DatabaseServiceHolder<Contract, Identifier> {
112
- readonly layer: (
113
- options: Options,
114
- ) => Layer.Layer<DatabaseIdentifier<Contract, Identifier>, PrismaError>;
115
- }
116
-
117
- /**
118
- * Resolve the models of the single domain namespace from a namespaced ORM
119
- * client. Prisma Next namespaces PostgreSQL models under their schema and
120
- * SQLite models under the unbound namespace.
121
- */
122
- export const namespaceModels = <
123
- Contract extends AnySqlContract,
124
- Models extends object,
125
- >(
126
- contract: Contract,
127
- namespacedOrm: TransactionOrm<Contract>,
128
- ): Models => {
129
- const namespaces = Object.keys(contract.domain.namespaces);
130
- if (namespaces.length !== 1 || namespaces[0] === undefined) {
131
- throw new TypeError(
132
- "Effect Prisma currently requires exactly one domain namespace",
133
- );
134
- }
135
- assertAvailableModelNames(
136
- Object.keys(contract.domain.namespaces[namespaces[0]].models),
137
- );
138
- return Reflect.get(namespacedOrm, namespaces[0]) as Models;
139
- };
140
-
141
18
  /**
142
19
  * Assemble the database service and its Layer around a driver-specific
143
20
  * executor acquisition.
144
21
  */
145
- export const makeSqlDatabase = <
146
- const Contract extends AnySqlContract,
147
- const Identifier extends string,
148
- Options,
149
- >(
22
+ export const makeSqlDatabase = <const Contract extends AnySqlContract, const Identifier extends string, Options>(
150
23
  identifier: DatabaseIdentifierLiteral<Identifier>,
151
- acquireExecutor: (
152
- options: Options,
153
- ) => Effect.Effect<
154
- DatabaseExecutor<DefaultModels<Contract>, Contract>,
155
- PrismaError
156
- >,
24
+ acquireExecutor: (options: Options) => Effect.Effect<DatabaseExecutor<DefaultModels<Contract>, Contract>, PrismaError>,
157
25
  ): SqlDatabase<Contract, Identifier, Options> => {
158
26
  if (identifier.startsWith(internalContextIdentifierPrefix)) {
159
- throw new TypeError(
160
- "Database identifier uses a reserved internal namespace",
161
- );
27
+ throw new TypeError("Database identifier uses a reserved internal namespace");
162
28
  }
163
29
  type Models = DefaultModels<Contract>;
164
30
  type DatabaseId = DatabaseIdentifier<Contract, Identifier>;
165
31
 
166
- const Service = Context.Service<
167
- DatabaseId,
168
- DatabaseService<Contract, Identifier>
169
- >(identifier);
170
- const executors = new WeakMap<
171
- DatabaseService<Contract, Identifier>,
172
- DatabaseExecutor<Models, Contract>
173
- >();
174
- type ActiveTransaction = {
175
- readonly executor: DatabaseExecutor<Models, Contract>;
176
- readonly facade: DatabaseService<Contract, Identifier>;
32
+ const Service = Context.Service<DatabaseId, DatabaseService<Contract, Identifier>>(identifier);
33
+ const scope: FacadeScope<Contract, Identifier> = {
34
+ activeTransaction: Context.Reference<ActiveTransaction<Contract, Identifier> | undefined>(
35
+ `${internalContextIdentifierPrefix}ActiveTransaction/${activeTransactionReferenceSequence++}`,
36
+ { defaultValue: () => undefined },
37
+ ),
38
+ executors: new WeakMap(),
39
+ service: Service,
177
40
  };
178
- const ActiveTransaction = Context.Reference<ActiveTransaction | undefined>(
179
- `${internalContextIdentifierPrefix}ActiveTransaction/${activeTransactionReferenceSequence++}`,
180
- { defaultValue: () => undefined },
181
- );
182
- const executorOf = (
183
- facade: DatabaseService<Contract, Identifier>,
184
- ): Effect.Effect<DatabaseExecutor<Models, Contract>> => {
185
- const executor = executors.get(facade);
41
+ const executorOf = (facade: DatabaseService<Contract, Identifier>): Effect.Effect<DatabaseExecutor<Models, Contract>> => {
42
+ const executor = scope.executors.get(facade);
186
43
  return executor === undefined
187
- ? Effect.die(
188
- new TypeError(
189
- "The database service was not created by its database Layer",
190
- ),
191
- )
44
+ ? Effect.die(new TypeError("The database service was not created by its database Layer"))
192
45
  : Effect.succeed(executor);
193
46
  };
194
- const runTransaction = <A, E, R>(
195
- current: DatabaseExecutor<Models, Contract>,
196
- currentFacade: DatabaseService<Contract, Identifier>,
197
- program: Effect.Effect<A, E, R>,
198
- release: typeof releaseTransaction,
199
- mode: "test" | "transaction",
200
- span: string,
201
- ): Effect.Effect<A, E | PrismaError, Exclude<R, DatabaseId>> => {
202
- if (!current.liveness.open) {
203
- return Effect.fail(toPrismaError({ code: current.liveness.closedCode }));
204
- }
205
- if (
206
- current.mode === "test" ||
207
- (current.mode === "transaction" && mode === "transaction")
208
- ) {
209
- return Effect.provideService(program, Service, currentFacade);
210
- }
211
- if (current.mode === "transaction") {
212
- return Effect.fail(
213
- toPrismaError({
214
- code: "RUNTIME.TEST_TRANSACTION_INSIDE_TRANSACTION_UNSUPPORTED",
215
- }),
216
- );
217
- }
218
-
219
- const transaction = Effect.acquireUseRelease(
220
- acquireTransaction(
221
- current,
222
- (transactionOrm) =>
223
- namespaceModels<Contract, Models>(
224
- current.client.contract,
225
- transactionOrm,
226
- ),
227
- mode,
228
- ),
229
- (resource) => {
230
- const transactionFacade = makeFacade(resource.executor);
231
- return program.pipe(
232
- Effect.provideService(Service, transactionFacade),
233
- Effect.provideService(ActiveTransaction, {
234
- executor: resource.executor,
235
- facade: transactionFacade,
236
- }),
237
- );
238
- },
239
- release,
240
- ).pipe(Effect.withSpan(span, { kind: "client" }));
241
-
242
- return withTransactionSemaphore(current.transactionSemaphore, transaction);
243
- };
244
-
245
- function makeFacade(
246
- current: DatabaseExecutor<Models, Contract>,
247
- ): DatabaseService<Contract, Identifier> {
248
- let facade: DatabaseService<Contract, Identifier>;
249
- const resolveActiveTransaction = Effect.flatMap(
250
- ActiveTransaction,
251
- (active) => {
252
- if (current.mode === "root") {
253
- return Effect.succeed(active ?? { executor: current, facade });
254
- }
255
- return active?.executor === current
256
- ? Effect.succeed(active)
257
- : Effect.fail(
258
- toPrismaError({
259
- code: "RUNTIME.TRANSACTION_CONTEXT_MISMATCH",
260
- }),
261
- );
262
- },
263
- );
264
- const resolveExecutor = Effect.suspend(() =>
265
- current.liveness.open
266
- ? Effect.map(resolveActiveTransaction, ({ executor }) => executor)
267
- : Effect.succeed(current),
268
- );
269
- const target = Object.assign(Object.create(null), {
270
- transaction: <A, E, R>(
271
- program: Effect.Effect<A, E, R> &
272
- (DatabaseId extends R ? unknown : never),
273
- ) =>
274
- Effect.suspend(() => {
275
- if (!current.liveness.open) {
276
- return Effect.fail(
277
- toPrismaError({ code: current.liveness.closedCode }),
278
- );
279
- }
280
- return Effect.flatMap(resolveActiveTransaction, (selected) => {
281
- return runTransaction(
282
- selected.executor,
283
- selected.facade,
284
- program,
285
- releaseTransaction,
286
- "transaction",
287
- "prisma.transaction",
288
- );
289
- });
290
- }),
291
- }) as DatabaseService<Contract, Identifier>;
292
- facade = new Proxy(target, {
293
- get(target, property, receiver) {
294
- if (Reflect.has(target, property)) {
295
- return Reflect.get(target, property, receiver);
296
- }
297
- if (typeof property !== "string") {
298
- return undefined;
299
- }
300
- return makeModelRelation(current, property, resolveExecutor);
301
- },
302
- });
303
- executors.set(facade, current);
304
- return facade;
305
- }
306
47
 
307
48
  const withTestTransaction = <A, E, R>(
308
49
  program: Effect.Effect<A, E, R> & (DatabaseId extends R ? unknown : never),
309
50
  ): Effect.Effect<A, E | PrismaError, DatabaseId | Exclude<R, DatabaseId>> =>
310
51
  Effect.flatMap(Service, (facade) => {
311
52
  return Effect.flatMap(executorOf(facade), (current) =>
312
- runTransaction(
313
- current,
314
- facade,
315
- program,
316
- releaseTestTransaction,
317
- "test",
318
- "prisma.testTransaction",
319
- ),
53
+ runTransaction(scope, current, facade, program, releaseTestTransaction, "test", "prisma.testTransaction"),
320
54
  );
321
55
  });
322
56
 
323
- const layer = (
324
- layerOptions: Options,
325
- ): Layer.Layer<DatabaseId, PrismaError> => {
57
+ const layer = (layerOptions: Options): Layer.Layer<DatabaseId, PrismaError> => {
326
58
  const acquire = Effect.acquireRelease(
327
59
  Effect.suspend(() => acquireExecutor(layerOptions)),
328
60
  (executor) => {
@@ -331,11 +63,11 @@ export const makeSqlDatabase = <
331
63
  },
332
64
  );
333
65
 
334
- return Layer.effect(Service)(Effect.map(acquire, makeFacade));
66
+ return Layer.effect(Service)(Effect.map(acquire, (executor) => makeFacade(scope, executor)));
335
67
  };
336
68
 
337
69
  return Object.assign(Service, {
338
70
  layer,
339
71
  [DatabaseTestingTypeId]: { withTestTransaction },
340
- }) as SqlDatabase<Contract, Identifier, Options>;
72
+ });
341
73
  };
@@ -0,0 +1,69 @@
1
+ import type { orm } from "@prisma-next/sql-orm-client";
2
+ import type { Context, Effect, Layer } from "effect";
3
+ import type { PrismaError } from "../error.ts";
4
+ import type { Relation } from "../relation.ts";
5
+ import type { AnySqlContract } from "./executor.ts";
6
+
7
+ type OrmFor<Contract extends AnySqlContract> = ReturnType<typeof orm<Contract>>;
8
+ type DefaultNamespaceId<Contract extends AnySqlContract> = "__unbound__" extends keyof OrmFor<Contract>
9
+ ? "__unbound__"
10
+ : "public" extends keyof OrmFor<Contract>
11
+ ? "public"
12
+ : keyof OrmFor<Contract>;
13
+ export type DefaultModels<Contract extends AnySqlContract> = OrmFor<Contract>[DefaultNamespaceId<Contract>] extends infer Models extends object
14
+ ? Models
15
+ : never;
16
+
17
+ type IsUnion<Value, Whole = Value> = Value extends unknown ? ([Whole] extends [Value] ? false : true) : never;
18
+
19
+ type IsSingletonString<Value extends string> = Record<never, never> extends Record<Value, never> ? false : true;
20
+
21
+ export const internalContextIdentifierPrefix = "\0@shivaedev/effect-prisma/internal/";
22
+
23
+ export type DatabaseIdentifierLiteral<Identifier extends string> = [Identifier] extends [`\0@shivaedev/effect-prisma/internal/${string}`]
24
+ ? never
25
+ : string extends Identifier
26
+ ? never
27
+ : true extends IsUnion<Identifier>
28
+ ? never
29
+ : IsSingletonString<Identifier> extends true
30
+ ? Identifier
31
+ : never;
32
+
33
+ export interface DatabaseIdentifier<Contract extends AnySqlContract, Identifier extends string> {
34
+ readonly _contract: (contract: Contract) => Contract;
35
+ readonly _databaseIdentifier: (identifier: Identifier) => Identifier;
36
+ }
37
+
38
+ type DatabaseModels<Contract extends AnySqlContract, Identifier extends string> = {
39
+ readonly [Model in keyof DefaultModels<Contract>]: Relation<
40
+ DefaultModels<Contract>[Model],
41
+ Contract,
42
+ Model & string,
43
+ DatabaseIdentifier<Contract, Identifier>
44
+ >;
45
+ };
46
+
47
+ export type DatabaseService<Contract extends AnySqlContract, Identifier extends string> = DatabaseModels<Contract, Identifier> & {
48
+ transaction<A, E, R>(
49
+ program: Effect.Effect<A, E, R> & (DatabaseIdentifier<Contract, Identifier> extends R ? unknown : never),
50
+ ): Effect.Effect<A, E | PrismaError, Exclude<R, DatabaseIdentifier<Contract, Identifier>>>;
51
+ };
52
+
53
+ /**
54
+ * The driver-independent part of a database definition: the Context service
55
+ * carrying the typed facade. Driver entrypoints add their own `layer` options.
56
+ */
57
+ export interface DatabaseServiceHolder<Contract extends AnySqlContract, Identifier extends string>
58
+ extends Context.Service<DatabaseIdentifier<Contract, Identifier>, DatabaseService<Contract, Identifier>> {}
59
+
60
+ export type AnyDatabase = Effect.Effect<unknown, never, unknown> & {
61
+ readonly layer: (...arguments_: ReadonlyArray<never>) => Layer.Any;
62
+ };
63
+
64
+ export type DatabaseServiceOf<Database extends AnyDatabase> = Effect.Success<Database>;
65
+
66
+ export interface SqlDatabase<Contract extends AnySqlContract, Identifier extends string, Options>
67
+ extends DatabaseServiceHolder<Contract, Identifier> {
68
+ readonly layer: (options: Options) => Layer.Layer<DatabaseIdentifier<Contract, Identifier>, PrismaError>;
69
+ }
@@ -0,0 +1,16 @@
1
+ const isObjectLike = (value: unknown): value is object => (typeof value === "object" && value !== null) || typeof value === "function";
2
+
3
+ export const hasMethod = (value: unknown, name: PropertyKey): value is object =>
4
+ isObjectLike(value) && typeof Reflect.get(value, name) === "function";
5
+
6
+ export const invokeMethod = (target: object, name: PropertyKey, arguments_: ReadonlyArray<unknown>): unknown => {
7
+ const method: unknown = Reflect.get(target, name);
8
+ if (typeof method !== "function") {
9
+ throw new TypeError(`${String(name)} is not a method`);
10
+ }
11
+ return Reflect.apply(method, target, arguments_);
12
+ };
13
+
14
+ export const isPromiseLike = (value: unknown): value is PromiseLike<unknown> => hasMethod(value, "then");
15
+
16
+ export const isAsyncIterable = (value: unknown): value is AsyncIterable<unknown> => hasMethod(value, Symbol.asyncIterator);
@@ -17,10 +17,7 @@ export interface SqlDatabaseClient<Contract extends AnySqlContract> {
17
17
  close(): Promise<void>;
18
18
  }
19
19
 
20
- export interface DatabaseExecutor<
21
- Models extends object,
22
- Contract extends AnySqlContract = AnySqlContract,
23
- > {
20
+ export interface DatabaseExecutor<Models extends object, Contract extends AnySqlContract = AnySqlContract> {
24
21
  readonly client: SqlDatabaseClient<Contract>;
25
22
  readonly identity: object;
26
23
  readonly liveness: {
@@ -1,16 +1,8 @@
1
1
  import { Effect } from "effect";
2
- import { isPrismaFailure, type PrismaError, toPrismaError } from "../error.js";
2
+ import { isPrismaFailure, type PrismaError, toPrismaError } from "../error.ts";
3
3
 
4
- export const fromPrismaPromise = <A>(
5
- evaluate: (signal: AbortSignal) => PromiseLike<A>,
6
- ): Effect.Effect<A, PrismaError> =>
4
+ export const fromPrismaPromise = <A>(evaluate: (signal: AbortSignal) => PromiseLike<A>): Effect.Effect<A, PrismaError> =>
7
5
  Effect.tryPromise({
8
6
  try: (signal) => Promise.resolve(evaluate(signal)),
9
7
  catch: (error) => error,
10
- }).pipe(
11
- Effect.catch((error) =>
12
- isPrismaFailure(error)
13
- ? Effect.fail(toPrismaError(error))
14
- : Effect.die(error),
15
- ),
16
- );
8
+ }).pipe(Effect.catch((error) => (isPrismaFailure(error) ? Effect.fail(toPrismaError(error)) : Effect.die(error))));
@@ -1,6 +1,6 @@
1
1
  import { Effect } from "effect";
2
- import { type PrismaError, toPrismaError } from "../error.js";
3
- import type { DatabaseExecutor } from "./executor.js";
2
+ import { type PrismaError, toPrismaError } from "../error.ts";
3
+ import type { DatabaseExecutor } from "./executor.ts";
4
4
 
5
5
  export const executeQuery = <A, E, R>(
6
6
  executor: Pick<DatabaseExecutor<object>, "liveness" | "querySemaphore">,
@@ -8,11 +8,7 @@ export const executeQuery = <A, E, R>(
8
8
  ): Effect.Effect<A, E | PrismaError, R> => {
9
9
  const checked = Effect.suspend(
10
10
  (): Effect.Effect<A, E | PrismaError, R> =>
11
- executor.liveness.open
12
- ? effect
13
- : Effect.fail(toPrismaError({ code: executor.liveness.closedCode })),
11
+ executor.liveness.open ? effect : Effect.fail(toPrismaError({ code: executor.liveness.closedCode })),
14
12
  );
15
- return executor.querySemaphore === undefined
16
- ? checked
17
- : executor.querySemaphore.withPermit(Effect.uninterruptible(checked));
13
+ return executor.querySemaphore === undefined ? checked : executor.querySemaphore.withPermit(Effect.uninterruptible(checked));
18
14
  };
@@ -1,4 +1,5 @@
1
- import { getRelationPlan, type RelationPlan } from "./relation-plan.js";
1
+ import { hasMethod, invokeMethod } from "./dynamic.ts";
2
+ import { getRelationPlan, type RelationPlan } from "./relation-plan.ts";
2
3
 
3
4
  export interface RelationOperation {
4
5
  readonly name: PropertyKey;
@@ -13,11 +14,7 @@ export interface RelationRecipe {
13
14
 
14
15
  export const rootRecipe = (model: string): RelationRecipe => ({ model });
15
16
 
16
- export const appendOperation = (
17
- parent: RelationRecipe,
18
- name: PropertyKey,
19
- arguments_: ReadonlyArray<unknown>,
20
- ): RelationRecipe => ({
17
+ export const appendOperation = (parent: RelationRecipe, name: PropertyKey, arguments_: ReadonlyArray<unknown>): RelationRecipe => ({
21
18
  model: parent.model,
22
19
  parent,
23
20
  operation: {
@@ -26,9 +23,7 @@ export const appendOperation = (
26
23
  },
27
24
  });
28
25
 
29
- const operations = (
30
- recipe: RelationRecipe,
31
- ): ReadonlyArray<RelationOperation> => {
26
+ const operations = (recipe: RelationRecipe): ReadonlyArray<RelationOperation> => {
32
27
  const reversed: Array<RelationOperation> = [];
33
28
  let current: RelationRecipe | undefined = recipe;
34
29
 
@@ -42,81 +37,43 @@ const operations = (
42
37
  return reversed.reverse();
43
38
  };
44
39
 
45
- const applyMethod = (
46
- current: unknown,
47
- name: PropertyKey,
48
- arguments_: ReadonlyArray<unknown>,
49
- model: string,
50
- ): unknown => {
51
- if (
52
- typeof current !== "object" ||
53
- current === null ||
54
- typeof Reflect.get(current, name) !== "function"
55
- ) {
40
+ const applyMethod = (current: unknown, name: PropertyKey, arguments_: ReadonlyArray<unknown>, model: string): unknown => {
41
+ if (!hasMethod(current, name)) {
56
42
  throw new TypeError(`Cannot call ${String(name)} while replaying ${model}`);
57
43
  }
58
-
59
- const method = Reflect.get(current, name) as (
60
- ...arguments_: ReadonlyArray<unknown>
61
- ) => unknown;
62
- return Reflect.apply(method, current, arguments_);
44
+ return invokeMethod(current, name, arguments_);
63
45
  };
64
46
 
65
- const replayPlan = (
66
- collection: unknown,
67
- plan: RelationPlan,
68
- owner: object,
69
- transactionIdentity: object | undefined,
70
- ): unknown => {
47
+ const replayPlan = (collection: unknown, plan: RelationPlan, owner: object, transactionIdentity: object | undefined): unknown => {
71
48
  if (!plan.liveness.open) {
72
49
  throw new TypeError("Included Relation is closed");
73
50
  }
74
51
  if (plan.owner !== owner) {
75
52
  throw new TypeError("Included Relations must use the same Database");
76
53
  }
77
- if (
78
- plan.transactionIdentity !== undefined &&
79
- plan.transactionIdentity !== transactionIdentity
80
- ) {
54
+ if (plan.transactionIdentity !== undefined && plan.transactionIdentity !== transactionIdentity) {
81
55
  throw new TypeError("Included Relation belongs to another transaction");
82
56
  }
83
- const relatedModel =
84
- typeof collection === "object" && collection !== null
85
- ? Reflect.get(collection, "modelName")
86
- : undefined;
57
+ const relatedModel = typeof collection === "object" && collection !== null ? Reflect.get(collection, "modelName") : undefined;
87
58
  if (typeof relatedModel === "string" && relatedModel !== plan.recipe.model) {
88
- throw new TypeError(
89
- `Included relation expects ${relatedModel}, received ${plan.recipe.model}`,
90
- );
59
+ throw new TypeError(`Included relation expects ${relatedModel}, received ${plan.recipe.model}`);
91
60
  }
92
61
 
93
- const refined = replayRecipeFrom(
94
- collection,
95
- plan.recipe,
96
- owner,
97
- transactionIdentity,
98
- );
62
+ const refined = replayRecipeFrom(collection, plan.recipe, owner, transactionIdentity);
99
63
  if (plan.terminal !== "count") {
100
64
  return refined;
101
65
  }
102
66
  return applyMethod(refined, "count", [], plan.recipe.model);
103
67
  };
104
68
 
105
- const includeRefinement = (
106
- value: unknown,
107
- owner: object,
108
- transactionIdentity: object | undefined,
109
- ): ((collection: unknown) => unknown) => {
69
+ const includeRefinement = (value: unknown, owner: object, transactionIdentity: object | undefined): ((collection: unknown) => unknown) => {
110
70
  const plan = getRelationPlan(value);
111
71
  if (plan !== undefined) {
112
- return (collection) =>
113
- replayPlan(collection, plan, owner, transactionIdentity);
72
+ return (collection) => replayPlan(collection, plan, owner, transactionIdentity);
114
73
  }
115
74
 
116
75
  if (typeof value !== "object" || value === null || Array.isArray(value)) {
117
- throw new TypeError(
118
- "An included relation must be a Relation or query record",
119
- );
76
+ throw new TypeError("An included relation must be a Relation or query record");
120
77
  }
121
78
 
122
79
  const entries = Object.entries(value);
@@ -137,46 +94,24 @@ const includeRefinement = (
137
94
  }
138
95
 
139
96
  return (collection) => {
140
- const branches = Object.fromEntries(
141
- plans.map(([name, queryPlan]) => [
142
- name,
143
- replayPlan(collection, queryPlan, owner, transactionIdentity),
144
- ]),
145
- );
97
+ const branches = Object.fromEntries(plans.map(([name, queryPlan]) => [name, replayPlan(collection, queryPlan, owner, transactionIdentity)]));
146
98
  return applyMethod(collection, "combine", [branches], model ?? "relation");
147
99
  };
148
100
  };
149
101
 
150
- const replayRecipeFrom = (
151
- root: unknown,
152
- recipe: RelationRecipe,
153
- owner: object,
154
- transactionIdentity: object | undefined,
155
- ): unknown => {
102
+ const replayRecipeFrom = (root: unknown, recipe: RelationRecipe, owner: object, transactionIdentity: object | undefined): unknown => {
156
103
  let current = root;
157
104
  for (const operation of operations(recipe)) {
158
105
  const arguments_ =
159
106
  operation.name === "include" && operation.arguments.length === 2
160
- ? [
161
- operation.arguments[0],
162
- includeRefinement(
163
- operation.arguments[1],
164
- owner,
165
- transactionIdentity,
166
- ),
167
- ]
107
+ ? [operation.arguments[0], includeRefinement(operation.arguments[1], owner, transactionIdentity)]
168
108
  : operation.arguments;
169
109
  current = applyMethod(current, operation.name, arguments_, recipe.model);
170
110
  }
171
111
  return current;
172
112
  };
173
113
 
174
- export const replayRecipe = (
175
- models: object,
176
- recipe: RelationRecipe,
177
- owner: object,
178
- transactionIdentity: object | undefined,
179
- ): unknown => {
114
+ export const replayRecipe = (models: object, recipe: RelationRecipe, owner: object, transactionIdentity: object | undefined): unknown => {
180
115
  const current: unknown = Reflect.get(models, recipe.model);
181
116
 
182
117
  if (current === undefined) {