@shivaedev/effect-prisma 0.0.0 → 0.1.0

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 (89) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/LICENSE +21 -0
  3. package/README.md +120 -2
  4. package/dist/database.d.ts +35 -0
  5. package/dist/database.d.ts.map +1 -0
  6. package/dist/database.js +83 -0
  7. package/dist/database.js.map +1 -0
  8. package/dist/error.d.ts +52 -0
  9. package/dist/error.d.ts.map +1 -0
  10. package/dist/error.js +48 -0
  11. package/dist/error.js.map +1 -0
  12. package/dist/index.d.ts +4 -0
  13. package/dist/index.d.ts.map +1 -0
  14. package/dist/index.js +3 -0
  15. package/dist/index.js.map +1 -0
  16. package/dist/internal/client-lifecycle.d.ts +7 -0
  17. package/dist/internal/client-lifecycle.d.ts.map +1 -0
  18. package/dist/internal/client-lifecycle.js +19 -0
  19. package/dist/internal/client-lifecycle.js.map +1 -0
  20. package/dist/internal/executor.d.ts +16 -0
  21. package/dist/internal/executor.d.ts.map +1 -0
  22. package/dist/internal/executor.js +2 -0
  23. package/dist/internal/executor.js.map +1 -0
  24. package/dist/internal/promise.d.ts +4 -0
  25. package/dist/internal/promise.d.ts.map +1 -0
  26. package/dist/internal/promise.js +9 -0
  27. package/dist/internal/promise.js.map +1 -0
  28. package/dist/internal/recipe.d.ts +13 -0
  29. package/dist/internal/recipe.d.ts.map +1 -0
  30. package/dist/internal/recipe.js +37 -0
  31. package/dist/internal/recipe.js.map +1 -0
  32. package/dist/internal/relation-runtime.d.ts +5 -0
  33. package/dist/internal/relation-runtime.d.ts.map +1 -0
  34. package/dist/internal/relation-runtime.js +88 -0
  35. package/dist/internal/relation-runtime.js.map +1 -0
  36. package/dist/internal/testing.d.ts +11 -0
  37. package/dist/internal/testing.d.ts.map +1 -0
  38. package/dist/internal/testing.js +9 -0
  39. package/dist/internal/testing.js.map +1 -0
  40. package/dist/internal/transaction.d.ts +14 -0
  41. package/dist/internal/transaction.d.ts.map +1 -0
  42. package/dist/internal/transaction.js +79 -0
  43. package/dist/internal/transaction.js.map +1 -0
  44. package/dist/internal/vitest-database.d.ts +18 -0
  45. package/dist/internal/vitest-database.d.ts.map +1 -0
  46. package/dist/internal/vitest-database.js +73 -0
  47. package/dist/internal/vitest-database.js.map +1 -0
  48. package/dist/relation/prisma-methods.d.ts +53 -0
  49. package/dist/relation/prisma-methods.d.ts.map +1 -0
  50. package/dist/relation/prisma-methods.js +2 -0
  51. package/dist/relation/prisma-methods.js.map +1 -0
  52. package/dist/relation.d.ts +27 -0
  53. package/dist/relation.d.ts.map +1 -0
  54. package/dist/relation.js +2 -0
  55. package/dist/relation.js.map +1 -0
  56. package/dist/testing/transaction.d.ts +10 -0
  57. package/dist/testing/transaction.d.ts.map +1 -0
  58. package/dist/testing/transaction.js +6 -0
  59. package/dist/testing/transaction.js.map +1 -0
  60. package/dist/testing/types.d.ts +29 -0
  61. package/dist/testing/types.d.ts.map +1 -0
  62. package/dist/testing/types.js +2 -0
  63. package/dist/testing/types.js.map +1 -0
  64. package/dist/testing/vitest.d.ts +6 -0
  65. package/dist/testing/vitest.d.ts.map +1 -0
  66. package/dist/testing/vitest.js +28 -0
  67. package/dist/testing/vitest.js.map +1 -0
  68. package/dist/testing.d.ts +4 -0
  69. package/dist/testing.d.ts.map +1 -0
  70. package/dist/testing.js +3 -0
  71. package/dist/testing.js.map +1 -0
  72. package/package.json +69 -4
  73. package/src/database.ts +232 -0
  74. package/src/error.ts +89 -0
  75. package/src/index.ts +14 -0
  76. package/src/internal/client-lifecycle.ts +31 -0
  77. package/src/internal/executor.ts +28 -0
  78. package/src/internal/promise.ts +16 -0
  79. package/src/internal/recipe.ts +71 -0
  80. package/src/internal/relation-runtime.ts +217 -0
  81. package/src/internal/testing.ts +32 -0
  82. package/src/internal/transaction.ts +149 -0
  83. package/src/internal/vitest-database.ts +210 -0
  84. package/src/relation/prisma-methods.ts +192 -0
  85. package/src/relation.ts +104 -0
  86. package/src/testing/transaction.ts +28 -0
  87. package/src/testing/types.ts +67 -0
  88. package/src/testing/vitest.ts +67 -0
  89. package/src/testing.ts +8 -0
@@ -0,0 +1,217 @@
1
+ import type { Context } from "effect";
2
+ import { Effect, Effectable, Option, Stream } from "effect";
3
+ import { isPrismaFailure, type PrismaError, toPrismaError } from "../error.js";
4
+ import type { Relation } from "../relation.js";
5
+ import type {
6
+ AnyPostgresContract,
7
+ DatabaseExecutor,
8
+ ExecutorIdentifier,
9
+ } from "./executor.js";
10
+ import { fromPrismaPromise } from "./promise.js";
11
+ import {
12
+ appendOperation,
13
+ type RelationRecipe,
14
+ replayRecipe,
15
+ rootRecipe,
16
+ } from "./recipe.js";
17
+
18
+ type AnyFunction = (...arguments_: ReadonlyArray<never>) => unknown;
19
+
20
+ interface RelationRuntime<
21
+ Models extends object,
22
+ Contract extends AnyPostgresContract,
23
+ > {
24
+ readonly executor: Context.Service<
25
+ ExecutorIdentifier<Models>,
26
+ DatabaseExecutor<Models, Contract>
27
+ >;
28
+ readonly recipe: RelationRecipe;
29
+ readonly terminal?: PropertyKey;
30
+ }
31
+
32
+ const evaluateResult = (
33
+ value: unknown,
34
+ terminal: PropertyKey | undefined,
35
+ ): Effect.Effect<unknown, PrismaError> => {
36
+ if (
37
+ terminal === "exists" &&
38
+ typeof value === "object" &&
39
+ value !== null &&
40
+ typeof Reflect.get(value, "first") === "function"
41
+ ) {
42
+ const first = Reflect.apply(
43
+ Reflect.get(value, "first") as AnyFunction,
44
+ value,
45
+ [],
46
+ );
47
+ return fromPrismaPromise(() => first as PromiseLike<unknown>).pipe(
48
+ Effect.map((result) => result !== null),
49
+ );
50
+ }
51
+
52
+ const executable =
53
+ typeof value === "object" &&
54
+ value !== null &&
55
+ typeof Reflect.get(value, "all") === "function"
56
+ ? Reflect.apply(Reflect.get(value, "all") as AnyFunction, value, [])
57
+ : value;
58
+
59
+ if (
60
+ typeof executable === "object" &&
61
+ executable !== null &&
62
+ "then" in executable
63
+ ) {
64
+ return fromPrismaPromise(() => executable as PromiseLike<unknown>).pipe(
65
+ Effect.map((result) =>
66
+ terminal === "first" ? Option.fromNullishOr(result) : result,
67
+ ),
68
+ );
69
+ }
70
+
71
+ return Effect.succeed(executable);
72
+ };
73
+
74
+ interface RelationValue<
75
+ Models extends object,
76
+ Contract extends AnyPostgresContract,
77
+ > extends Effect.Effect<unknown, PrismaError, ExecutorIdentifier<Models>> {
78
+ readonly runtime: RelationRuntime<Models, Contract>;
79
+ readonly stream: Stream.Stream<
80
+ unknown,
81
+ PrismaError,
82
+ ExecutorIdentifier<Models>
83
+ >;
84
+ }
85
+
86
+ const relationEffect = <
87
+ Models extends object,
88
+ Contract extends AnyPostgresContract,
89
+ >(
90
+ self: RelationValue<Models, Contract>,
91
+ ): Effect.Effect<unknown, PrismaError, ExecutorIdentifier<Models>> =>
92
+ Effect.flatMap(self.runtime.executor, (executor) =>
93
+ Effect.suspend(() =>
94
+ evaluateResult(
95
+ replayRecipe(executor.models, self.runtime.recipe),
96
+ self.runtime.terminal,
97
+ ),
98
+ ),
99
+ ).pipe(
100
+ Effect.withSpan(
101
+ `prisma.${self.runtime.recipe.model}.${String(self.runtime.terminal ?? "all")}`,
102
+ {
103
+ kind: "client",
104
+ attributes: {
105
+ "db.system": "postgresql",
106
+ "db.model": self.runtime.recipe.model,
107
+ "db.operation": String(self.runtime.terminal ?? "all"),
108
+ },
109
+ },
110
+ ),
111
+ );
112
+
113
+ const relationStream = <
114
+ Models extends object,
115
+ Contract extends AnyPostgresContract,
116
+ >(
117
+ self: RelationValue<Models, Contract>,
118
+ ): Stream.Stream<unknown, PrismaError, ExecutorIdentifier<Models>> => {
119
+ const result = Effect.map(self.runtime.executor, (executor) => {
120
+ const collection = replayRecipe(executor.models, self.runtime.recipe);
121
+ if (
122
+ typeof collection !== "object" ||
123
+ collection === null ||
124
+ typeof Reflect.get(collection, "all") !== "function"
125
+ ) {
126
+ throw new TypeError("Only collection Relations can be streamed");
127
+ }
128
+ return Reflect.apply(
129
+ Reflect.get(collection, "all") as AnyFunction,
130
+ collection,
131
+ [],
132
+ ) as AsyncIterable<unknown>;
133
+ });
134
+
135
+ return Stream.unwrap(
136
+ Effect.map(result, (iterable) =>
137
+ Stream.fromAsyncIterable(iterable, (error) => error).pipe(
138
+ Stream.catch((error) =>
139
+ isPrismaFailure(error)
140
+ ? Stream.fail(toPrismaError(error))
141
+ : Stream.die(error),
142
+ ),
143
+ ),
144
+ ),
145
+ );
146
+ };
147
+
148
+ const RelationPrototype = {
149
+ ...Effectable.Prototype<
150
+ RelationValue<Record<string, unknown>, AnyPostgresContract>
151
+ >({
152
+ label: "EffectPrismaRelation",
153
+ evaluate() {
154
+ return relationEffect(this);
155
+ },
156
+ }),
157
+ get stream(): Stream.Stream<
158
+ unknown,
159
+ PrismaError,
160
+ ExecutorIdentifier<Record<string, unknown>>
161
+ > {
162
+ return relationStream(
163
+ this as RelationValue<Record<string, unknown>, AnyPostgresContract>,
164
+ );
165
+ },
166
+ };
167
+
168
+ const makeRelationProxy = <
169
+ Models extends object,
170
+ Contract extends AnyPostgresContract,
171
+ >(
172
+ runtime: RelationRuntime<Models, Contract>,
173
+ ): unknown => {
174
+ const target = Object.assign(Object.create(RelationPrototype), {
175
+ runtime,
176
+ }) as RelationValue<Models, Contract>;
177
+
178
+ return new Proxy(target, {
179
+ get(self, property, receiver) {
180
+ if (property === "then") {
181
+ return undefined;
182
+ }
183
+ if (Reflect.has(self, property)) {
184
+ return Reflect.get(self, property, receiver);
185
+ }
186
+ if (property === "exists") {
187
+ return () =>
188
+ makeRelationProxy({
189
+ ...runtime,
190
+ terminal: "exists",
191
+ });
192
+ }
193
+ return (...arguments_: ReadonlyArray<unknown>) =>
194
+ makeRelationProxy({
195
+ ...runtime,
196
+ recipe: appendOperation(runtime.recipe, property, arguments_),
197
+ terminal: property,
198
+ });
199
+ },
200
+ });
201
+ };
202
+
203
+ export const makeModelRelation = <
204
+ Collection,
205
+ Models extends object,
206
+ Contract extends AnyPostgresContract = AnyPostgresContract,
207
+ >(
208
+ executor: Context.Service<
209
+ ExecutorIdentifier<Models>,
210
+ DatabaseExecutor<Models, Contract>
211
+ >,
212
+ model: string,
213
+ ): Relation<Collection, ExecutorIdentifier<Models>> =>
214
+ makeRelationProxy({
215
+ executor,
216
+ recipe: rootRecipe(model),
217
+ }) as Relation<Collection, ExecutorIdentifier<Models>>;
@@ -0,0 +1,32 @@
1
+ import type { Effect } from "effect";
2
+ import type { PrismaError } from "../error.js";
3
+
4
+ export const DatabaseTestingTypeId: unique symbol = Symbol.for(
5
+ "@shivaedev/effect-prisma/DatabaseTesting",
6
+ );
7
+
8
+ export interface DatabaseTesting<Requirement> {
9
+ readonly withTestTransaction: <A, E, R>(
10
+ program: Effect.Effect<A, E, R>,
11
+ ) => Effect.Effect<A, E | PrismaError, R | Requirement>;
12
+ }
13
+
14
+ export interface DatabaseWithTesting<Requirement> {
15
+ readonly [DatabaseTestingTypeId]: DatabaseTesting<Requirement>;
16
+ }
17
+
18
+ export const getDatabaseTesting = <Requirement>(
19
+ database: object,
20
+ ): DatabaseTesting<Requirement> => {
21
+ const testing = Reflect.get(database, DatabaseTestingTypeId) as
22
+ | DatabaseTesting<Requirement>
23
+ | undefined;
24
+
25
+ if (testing === undefined) {
26
+ throw new TypeError(
27
+ "The database was not created by this copy of @shivaedev/effect-prisma",
28
+ );
29
+ }
30
+
31
+ return testing;
32
+ };
@@ -0,0 +1,149 @@
1
+ import type { PostgresClient } from "@prisma-next/postgres/runtime";
2
+ import { orm } from "@prisma-next/sql-orm-client";
3
+ import type {
4
+ RuntimeConnection,
5
+ RuntimeTransaction,
6
+ } from "@prisma-next/sql-runtime";
7
+ import { Effect, Exit } from "effect";
8
+ import type { PrismaError } from "../error.js";
9
+ import type { AnyPostgresContract, DatabaseExecutor } from "./executor.js";
10
+ import { fromPrismaPromise } from "./promise.js";
11
+
12
+ export interface TransactionResource<
13
+ Models extends object,
14
+ Contract extends AnyPostgresContract,
15
+ > {
16
+ readonly connection: RuntimeConnection;
17
+ readonly transaction: RuntimeTransaction;
18
+ readonly executor: DatabaseExecutor<Models, Contract>;
19
+ }
20
+
21
+ const runtimeFailure = (
22
+ code: string,
23
+ cause: unknown,
24
+ details?: Readonly<Record<string, unknown>>,
25
+ ): Error & { readonly code: string } =>
26
+ Object.assign(new Error(code, { cause }), { code, ...details });
27
+
28
+ export const acquireTransaction = <
29
+ Models extends object,
30
+ Contract extends AnyPostgresContract,
31
+ >(
32
+ current: DatabaseExecutor<Models, Contract>,
33
+ models: (orm: PostgresClient<Contract>["orm"]) => Models,
34
+ ): Effect.Effect<TransactionResource<Models, Contract>, PrismaError> =>
35
+ fromPrismaPromise(async () => {
36
+ const connection = await current.client.runtime().connection();
37
+
38
+ try {
39
+ const transaction = await connection.transaction();
40
+ const transactionOrm = orm({
41
+ runtime: transaction,
42
+ context: current.client.context,
43
+ });
44
+
45
+ return {
46
+ connection,
47
+ transaction,
48
+ executor: {
49
+ client: current.client,
50
+ models: models(transactionOrm),
51
+ transactional: true,
52
+ },
53
+ };
54
+ } catch (error) {
55
+ await connection.destroy(error).catch(() => undefined);
56
+ throw error;
57
+ }
58
+ });
59
+
60
+ export const releaseTransaction = <
61
+ Models extends object,
62
+ Contract extends AnyPostgresContract,
63
+ A,
64
+ E,
65
+ >(
66
+ resource: TransactionResource<Models, Contract>,
67
+ exit: Exit.Exit<A, E>,
68
+ ): Effect.Effect<void, PrismaError> => settleTransaction(resource, exit, true);
69
+
70
+ export const releaseTestTransaction = <
71
+ Models extends object,
72
+ Contract extends AnyPostgresContract,
73
+ A,
74
+ E,
75
+ >(
76
+ resource: TransactionResource<Models, Contract>,
77
+ exit: Exit.Exit<A, E>,
78
+ ): Effect.Effect<void, PrismaError> => settleTransaction(resource, exit, false);
79
+
80
+ const settleTransaction = <
81
+ Models extends object,
82
+ Contract extends AnyPostgresContract,
83
+ A,
84
+ E,
85
+ >(
86
+ resource: TransactionResource<Models, Contract>,
87
+ exit: Exit.Exit<A, E>,
88
+ commitOnSuccess: boolean,
89
+ ): Effect.Effect<void, PrismaError> =>
90
+ Effect.uninterruptible(
91
+ fromPrismaPromise(async () => {
92
+ let disposed = false;
93
+ let failure: unknown;
94
+
95
+ const destroy = async (reason: unknown): Promise<void> => {
96
+ if (disposed) {
97
+ return;
98
+ }
99
+ disposed = true;
100
+ await resource.connection.destroy(reason).catch(() => undefined);
101
+ };
102
+
103
+ if (commitOnSuccess && Exit.isSuccess(exit)) {
104
+ try {
105
+ await resource.transaction.commit();
106
+ } catch (commitError) {
107
+ try {
108
+ await resource.transaction.rollback();
109
+ } catch {
110
+ await destroy(commitError);
111
+ }
112
+ failure = runtimeFailure(
113
+ "RUNTIME.TRANSACTION_COMMIT_FAILED",
114
+ commitError,
115
+ );
116
+ }
117
+ } else {
118
+ try {
119
+ await resource.transaction.rollback();
120
+ } catch (rollbackError) {
121
+ await destroy(rollbackError);
122
+ failure = runtimeFailure(
123
+ "RUNTIME.TRANSACTION_ROLLBACK_FAILED",
124
+ rollbackError,
125
+ );
126
+ }
127
+ }
128
+
129
+ if (!disposed) {
130
+ try {
131
+ await resource.connection.release();
132
+ } catch (releaseError) {
133
+ await destroy(releaseError);
134
+ if (failure !== undefined) {
135
+ throw runtimeFailure(
136
+ "RUNTIME.TRANSACTION_RELEASE_FAILED",
137
+ failure,
138
+ { releaseError },
139
+ );
140
+ }
141
+ throw releaseError;
142
+ }
143
+ }
144
+
145
+ if (failure !== undefined) {
146
+ throw failure;
147
+ }
148
+ }),
149
+ );
@@ -0,0 +1,210 @@
1
+ import type { TestContext, TestOptions } from "@effect/vitest";
2
+ import { Cause, type Context, Effect, Exit, Layer, type Scope } from "effect";
3
+ import * as TestClock from "effect/testing/TestClock";
4
+ import * as TestConsole from "effect/testing/TestConsole";
5
+ import { withTestTransaction } from "../testing/transaction.js";
6
+ import type {
7
+ AnyDatabase,
8
+ DatabaseRequirement,
9
+ DatabaseService,
10
+ DatabaseTest,
11
+ DatabaseTester,
12
+ } from "../testing/types.js";
13
+
14
+ export const fixtureName = "__effectPrismaContext";
15
+
16
+ export type DatabaseFixture<Provided> = TestContext & {
17
+ readonly [fixtureName]: Context.Context<Provided>;
18
+ };
19
+
20
+ export interface FixtureTestApi<Provided> {
21
+ (
22
+ name: string,
23
+ options: TestOptions,
24
+ body: (context: DatabaseFixture<Provided>) => Promise<unknown>,
25
+ ): void;
26
+ readonly skip: FixtureTestApi<Provided>;
27
+ readonly skipIf: (condition: unknown) => FixtureTestApi<Provided>;
28
+ readonly runIf: (condition: unknown) => FixtureTestApi<Provided>;
29
+ readonly only: FixtureTestApi<Provided>;
30
+ readonly fails: FixtureTestApi<Provided>;
31
+ readonly for: <Item>(
32
+ cases: ReadonlyArray<Item>,
33
+ ) => (
34
+ name: string,
35
+ options: TestOptions,
36
+ body: (item: Item, context: DatabaseFixture<Provided>) => Promise<unknown>,
37
+ ) => void;
38
+ }
39
+
40
+ const TestEnvironment = Layer.mergeAll(TestConsole.layer, TestClock.layer());
41
+
42
+ const contextFrom = <Provided>(
43
+ context: TestContext,
44
+ ): Context.Context<Provided> =>
45
+ (context as DatabaseFixture<Provided>)[fixtureName];
46
+
47
+ const runEffectTest = <A, E>(
48
+ effect: Effect.Effect<A, E, Scope.Scope>,
49
+ context: TestContext,
50
+ ): Promise<A> =>
51
+ Effect.runPromise(
52
+ Effect.gen(function* () {
53
+ const exit = yield* Effect.exit(effect);
54
+ if (Exit.isFailure(exit)) {
55
+ for (const error of Cause.prettyErrors(exit.cause)) {
56
+ yield* Effect.logError(error);
57
+ }
58
+ }
59
+ return yield* exit;
60
+ }).pipe(Effect.scoped, Effect.provide(TestEnvironment)),
61
+ { signal: context.signal },
62
+ );
63
+
64
+ const normalizeTestOptions = (
65
+ options: number | TestOptions | undefined,
66
+ ): TestOptions =>
67
+ typeof options === "number" ? { timeout: options } : (options ?? {});
68
+
69
+ const restoreContext = <Provided>(
70
+ fixture: Context.Context<Provided>,
71
+ context: Omit<DatabaseFixture<Provided>, typeof fixtureName>,
72
+ ): DatabaseFixture<Provided> =>
73
+ ({
74
+ ...context,
75
+ [fixtureName]: fixture,
76
+ }) as DatabaseFixture<Provided>;
77
+
78
+ export const makeDatabaseTester = <Database extends AnyDatabase, Provided>(
79
+ fixtureIt: FixtureTestApi<
80
+ Provided | DatabaseRequirement<Database> | Effect.Services<Database>
81
+ >,
82
+ database: Database,
83
+ ): DatabaseTester<
84
+ Database,
85
+ Provided | DatabaseRequirement<Database> | Effect.Services<Database>
86
+ > => {
87
+ type Services =
88
+ | Provided
89
+ | DatabaseRequirement<Database>
90
+ | Effect.Services<Database>;
91
+
92
+ const run = <A, Eff extends Effect.Effect<unknown, unknown, Services>>(
93
+ body: (
94
+ database: DatabaseService<Database>,
95
+ context: TestContext,
96
+ ) => Generator<Eff, A, never>,
97
+ context: TestContext,
98
+ ): Effect.Effect<A, unknown, Scope.Scope> => {
99
+ const effect = withTestTransaction(
100
+ database,
101
+ Effect.gen(function* () {
102
+ const service = yield* database as unknown as Effect.Effect<
103
+ DatabaseService<Database>,
104
+ never,
105
+ Effect.Services<Database>
106
+ >;
107
+ return yield* Effect.gen(() => body(service, context));
108
+ }),
109
+ ).pipe(Effect.provide(contextFrom<Services>(context)));
110
+
111
+ // The callback constraint proves that its services are in the built
112
+ // Layer. TypeScript cannot reduce that generic Exclude after
113
+ // Effect.provide, so the runner closes the type at this private boundary.
114
+ return effect as unknown as Effect.Effect<A, unknown, Scope.Scope>;
115
+ };
116
+
117
+ const register =
118
+ (current: FixtureTestApi<Services>): DatabaseTest<Database, Services> =>
119
+ (name, body, options) =>
120
+ current(
121
+ name,
122
+ normalizeTestOptions(options),
123
+ ({
124
+ __effectPrismaContext,
125
+ task,
126
+ signal,
127
+ onTestFailed,
128
+ onTestFinished,
129
+ skip,
130
+ annotate,
131
+ expect,
132
+ _local,
133
+ }: DatabaseFixture<Services>) => {
134
+ const testContext = restoreContext(__effectPrismaContext, {
135
+ task,
136
+ signal,
137
+ onTestFailed,
138
+ onTestFinished,
139
+ skip,
140
+ annotate,
141
+ expect,
142
+ _local,
143
+ });
144
+ return runEffectTest(run(body, testContext), testContext);
145
+ },
146
+ );
147
+
148
+ const make = (
149
+ current: FixtureTestApi<Services>,
150
+ ): DatabaseTester<Database, Services> => {
151
+ const test = register(current);
152
+
153
+ return Object.assign(test, {
154
+ skip: register(current.skip),
155
+ skipIf: (condition: unknown) => register(current.skipIf(condition)),
156
+ runIf: (condition: unknown) => register(current.runIf(condition)),
157
+ only: register(current.only),
158
+ each: <Item>(cases: ReadonlyArray<Item>) => {
159
+ return <A, Eff extends Effect.Effect<unknown, unknown, Services>>(
160
+ name: string,
161
+ body: (
162
+ item: Item,
163
+ database: DatabaseService<Database>,
164
+ context: TestContext,
165
+ ) => Generator<Eff, A, never>,
166
+ options?: number | TestOptions,
167
+ ) =>
168
+ fixtureIt.for(cases)(
169
+ name,
170
+ normalizeTestOptions(options),
171
+ (
172
+ item,
173
+ {
174
+ __effectPrismaContext,
175
+ task,
176
+ signal,
177
+ onTestFailed,
178
+ onTestFinished,
179
+ skip,
180
+ annotate,
181
+ expect,
182
+ _local,
183
+ }: DatabaseFixture<Services>,
184
+ ) => {
185
+ const testContext = restoreContext(__effectPrismaContext, {
186
+ task,
187
+ signal,
188
+ onTestFailed,
189
+ onTestFinished,
190
+ skip,
191
+ annotate,
192
+ expect,
193
+ _local,
194
+ });
195
+ return runEffectTest(
196
+ run(
197
+ (service, testContext) => body(item, service, testContext),
198
+ testContext,
199
+ ),
200
+ testContext,
201
+ );
202
+ },
203
+ );
204
+ },
205
+ fails: register(current.fails),
206
+ });
207
+ };
208
+
209
+ return make(fixtureIt);
210
+ };