@shivaedev/effect-prisma 0.6.3 → 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.
- package/CHANGELOG.md +5 -1
- package/dist/database.d.ts +4 -4
- package/dist/database.d.ts.map +1 -1
- package/dist/database.js +12 -12
- package/dist/database.js.map +1 -1
- package/dist/error.d.ts +5 -5
- package/dist/error.d.ts.map +1 -1
- package/dist/error.js +1 -2
- package/dist/error.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/internal/adapters/transaction-settlement.d.ts +10 -0
- package/dist/internal/adapters/transaction-settlement.d.ts.map +1 -0
- package/dist/internal/adapters/transaction-settlement.js +56 -0
- package/dist/internal/adapters/transaction-settlement.js.map +1 -0
- package/dist/internal/client-lifecycle.d.ts.map +1 -1
- package/dist/internal/client-lifecycle.js.map +1 -1
- package/dist/internal/contract-normalization.d.ts.map +1 -1
- package/dist/internal/contract-normalization.js +1 -3
- package/dist/internal/contract-normalization.js.map +1 -1
- package/dist/internal/database-facade.d.ts +24 -0
- package/dist/internal/database-facade.d.ts.map +1 -0
- package/dist/internal/database-facade.js +84 -0
- package/dist/internal/database-facade.js.map +1 -0
- package/dist/internal/database-factory.d.ts +4 -44
- package/dist/internal/database-factory.d.ts.map +1 -1
- package/dist/internal/database-factory.js +11 -84
- package/dist/internal/database-factory.js.map +1 -1
- package/dist/internal/database-types.d.ts +37 -0
- package/dist/internal/database-types.d.ts.map +1 -0
- package/dist/internal/database-types.js +2 -0
- package/dist/internal/database-types.js.map +1 -0
- package/dist/internal/dynamic.d.ts +5 -0
- package/dist/internal/dynamic.d.ts.map +1 -0
- package/dist/internal/dynamic.js +12 -0
- package/dist/internal/dynamic.js.map +1 -0
- package/dist/internal/executor.d.ts.map +1 -1
- package/dist/internal/promise.d.ts +1 -1
- package/dist/internal/promise.d.ts.map +1 -1
- package/dist/internal/promise.js +1 -3
- package/dist/internal/promise.js.map +1 -1
- package/dist/internal/query-execution.d.ts +2 -2
- package/dist/internal/query-execution.d.ts.map +1 -1
- package/dist/internal/query-execution.js +2 -6
- package/dist/internal/query-execution.js.map +1 -1
- package/dist/internal/recipe.d.ts.map +1 -1
- package/dist/internal/recipe.js +7 -18
- package/dist/internal/recipe.js.map +1 -1
- package/dist/internal/relation-plan.d.ts +1 -1
- package/dist/internal/relation-result.d.ts +4 -0
- package/dist/internal/relation-result.d.ts.map +1 -0
- package/dist/internal/relation-result.js +19 -0
- package/dist/internal/relation-result.js.map +1 -0
- package/dist/internal/relation-runtime.d.ts +3 -3
- package/dist/internal/relation-runtime.d.ts.map +1 -1
- package/dist/internal/relation-runtime.js +9 -34
- package/dist/internal/relation-runtime.js.map +1 -1
- package/dist/internal/relation-stream.d.ts +3 -3
- package/dist/internal/relation-stream.d.ts.map +1 -1
- package/dist/internal/relation-stream.js +12 -14
- package/dist/internal/relation-stream.js.map +1 -1
- package/dist/internal/sqlite-datetime.d.ts +1 -1
- package/dist/internal/sqlite-datetime.d.ts.map +1 -1
- package/dist/internal/sqlite-datetime.js +3 -1
- package/dist/internal/sqlite-datetime.js.map +1 -1
- package/dist/internal/sqlite-pragmas.d.ts.map +1 -1
- package/dist/internal/sqlite-pragmas.js.map +1 -1
- package/dist/internal/testing.d.ts +1 -1
- package/dist/internal/testing.d.ts.map +1 -1
- package/dist/internal/testing.js +5 -2
- package/dist/internal/testing.js.map +1 -1
- package/dist/internal/transaction.d.ts +5 -5
- package/dist/internal/transaction.d.ts.map +1 -1
- package/dist/internal/transaction.js +3 -51
- package/dist/internal/transaction.js.map +1 -1
- package/dist/relation/include-metadata.d.ts.map +1 -1
- package/dist/relation/include.d.ts +2 -2
- package/dist/relation/include.d.ts.map +1 -1
- package/dist/relation/prisma-methods.d.ts +3 -3
- package/dist/relation/prisma-methods.d.ts.map +1 -1
- package/dist/relation.d.ts +2 -2
- package/dist/relation.d.ts.map +1 -1
- package/dist/sqlite.d.ts +3 -3
- package/dist/sqlite.d.ts.map +1 -1
- package/dist/sqlite.js +14 -12
- package/dist/sqlite.js.map +1 -1
- package/dist/testing/transaction.d.ts +6 -7
- package/dist/testing/transaction.d.ts.map +1 -1
- package/dist/testing/transaction.js +2 -2
- package/dist/testing/transaction.js.map +1 -1
- package/dist/testing/types.d.ts +2 -2
- package/dist/testing/types.d.ts.map +1 -1
- package/dist/testing/vitest.d.ts +3 -3
- package/dist/testing/vitest.d.ts.map +1 -1
- package/dist/testing/vitest.js +2 -2
- package/dist/testing/vitest.js.map +1 -1
- package/dist/testing.d.ts +3 -3
- package/package.json +2 -2
- package/src/bin/normalize-contract.ts +1 -1
- package/src/database.ts +49 -67
- package/src/error.ts +9 -19
- package/src/index.ts +3 -3
- package/src/internal/adapters/transaction-settlement.ts +67 -0
- package/src/internal/client-lifecycle.ts +3 -10
- package/src/internal/contract-normalization.ts +3 -8
- package/src/internal/database-facade.ts +143 -0
- package/src/internal/database-factory.ts +30 -298
- package/src/internal/database-types.ts +69 -0
- package/src/internal/dynamic.ts +16 -0
- package/src/internal/executor.ts +1 -4
- package/src/internal/promise.ts +3 -11
- package/src/internal/query-execution.ts +4 -8
- package/src/internal/recipe.ts +19 -84
- package/src/internal/relation-plan.ts +1 -1
- package/src/internal/relation-result.ts +27 -0
- package/src/internal/relation-runtime.ts +43 -133
- package/src/internal/relation-stream.ts +22 -48
- package/src/internal/sqlite-datetime.ts +10 -23
- package/src/internal/sqlite-pragmas.ts +1 -4
- package/src/internal/testing.ts +13 -15
- package/src/internal/transaction.ts +14 -107
- package/src/relation/include-metadata.ts +10 -42
- package/src/relation/include.ts +19 -115
- package/src/relation/prisma-methods.ts +23 -111
- package/src/relation.ts +18 -46
- package/src/sqlite.ts +57 -78
- package/src/testing/transaction.ts +20 -33
- package/src/testing/types.ts +9 -28
- package/src/testing/vitest.ts +14 -28
- package/src/testing.ts +3 -3
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Effect, Option } from "effect";
|
|
2
|
+
import type { PrismaError } from "../error.ts";
|
|
3
|
+
import { hasMethod, invokeMethod, isPromiseLike } from "./dynamic.ts";
|
|
4
|
+
import { fromPrismaPromise } from "./promise.ts";
|
|
5
|
+
|
|
6
|
+
const settle = (value: unknown, map: (result: unknown) => unknown): Effect.Effect<unknown, PrismaError> =>
|
|
7
|
+
isPromiseLike(value) ? fromPrismaPromise(() => value).pipe(Effect.map(map)) : Effect.succeed(map(value));
|
|
8
|
+
|
|
9
|
+
const countOf = (result: unknown): unknown => (typeof result === "object" && result !== null ? Reflect.get(result, "count") : undefined);
|
|
10
|
+
|
|
11
|
+
export const evaluateResult = (value: unknown, terminal: PropertyKey | undefined): Effect.Effect<unknown, PrismaError> => {
|
|
12
|
+
if (terminal === "count" && hasMethod(value, "aggregate")) {
|
|
13
|
+
return settle(invokeMethod(value, "aggregate", [(summary: { count(): unknown }) => ({ count: summary.count() })]), countOf);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if (terminal === "exists" && hasMethod(value, "first")) {
|
|
17
|
+
return settle(invokeMethod(value, "first", []), (result) => result !== null);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const executable = hasMethod(value, "all") ? invokeMethod(value, "all", []) : value;
|
|
21
|
+
|
|
22
|
+
if (isPromiseLike(executable)) {
|
|
23
|
+
return settle(executable, (result) => (terminal === "first" ? Option.fromNullishOr(result) : result));
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return Effect.succeed(executable);
|
|
27
|
+
};
|
|
@@ -1,141 +1,54 @@
|
|
|
1
|
-
import { Effect, Effectable,
|
|
2
|
-
import type { PrismaError } from "../error.
|
|
3
|
-
import type { Relation } from "../relation.
|
|
4
|
-
import type { AnyPostgresContract, DatabaseExecutor } from "./executor.
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
replayRecipe,
|
|
11
|
-
rootRecipe,
|
|
12
|
-
} from "./recipe.js";
|
|
13
|
-
import { setRelationPlan } from "./relation-plan.js";
|
|
14
|
-
import { makeRelationStream } from "./relation-stream.js";
|
|
1
|
+
import { Effect, Effectable, type Stream } from "effect";
|
|
2
|
+
import type { PrismaError } from "../error.ts";
|
|
3
|
+
import type { Relation } from "../relation.ts";
|
|
4
|
+
import type { AnyPostgresContract, DatabaseExecutor } from "./executor.ts";
|
|
5
|
+
import { executeQuery } from "./query-execution.ts";
|
|
6
|
+
import { appendOperation, type RelationRecipe, replayRecipe, rootRecipe } from "./recipe.ts";
|
|
7
|
+
import { setRelationPlan } from "./relation-plan.ts";
|
|
8
|
+
import { evaluateResult } from "./relation-result.ts";
|
|
9
|
+
import { makeRelationStream } from "./relation-stream.ts";
|
|
15
10
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
interface RelationRuntime<
|
|
19
|
-
Models extends object,
|
|
20
|
-
Contract extends AnyPostgresContract,
|
|
21
|
-
> {
|
|
11
|
+
interface RelationRuntime<Models extends object, Contract extends AnyPostgresContract> {
|
|
22
12
|
readonly executor: DatabaseExecutor<Models, Contract>;
|
|
23
13
|
readonly recipe: RelationRecipe;
|
|
24
|
-
readonly resolveExecutor: Effect.Effect<
|
|
25
|
-
DatabaseExecutor<Models, Contract>,
|
|
26
|
-
PrismaError
|
|
27
|
-
>;
|
|
14
|
+
readonly resolveExecutor: Effect.Effect<DatabaseExecutor<Models, Contract>, PrismaError>;
|
|
28
15
|
readonly terminal?: PropertyKey;
|
|
29
16
|
}
|
|
30
17
|
|
|
31
|
-
const evaluateResult = (
|
|
32
|
-
value: unknown,
|
|
33
|
-
terminal: PropertyKey | undefined,
|
|
34
|
-
): Effect.Effect<unknown, PrismaError> => {
|
|
35
|
-
if (
|
|
36
|
-
terminal === "count" &&
|
|
37
|
-
typeof value === "object" &&
|
|
38
|
-
value !== null &&
|
|
39
|
-
typeof Reflect.get(value, "aggregate") === "function"
|
|
40
|
-
) {
|
|
41
|
-
const aggregate = Reflect.apply(
|
|
42
|
-
Reflect.get(value, "aggregate") as AnyFunction,
|
|
43
|
-
value,
|
|
44
|
-
[(summary: { count(): unknown }) => ({ count: summary.count() })],
|
|
45
|
-
);
|
|
46
|
-
return fromPrismaPromise(
|
|
47
|
-
() => aggregate as PromiseLike<{ count: number }>,
|
|
48
|
-
).pipe(Effect.map((result) => result.count));
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
if (
|
|
52
|
-
terminal === "exists" &&
|
|
53
|
-
typeof value === "object" &&
|
|
54
|
-
value !== null &&
|
|
55
|
-
typeof Reflect.get(value, "first") === "function"
|
|
56
|
-
) {
|
|
57
|
-
const first = Reflect.apply(
|
|
58
|
-
Reflect.get(value, "first") as AnyFunction,
|
|
59
|
-
value,
|
|
60
|
-
[],
|
|
61
|
-
);
|
|
62
|
-
return fromPrismaPromise(() => first as PromiseLike<unknown>).pipe(
|
|
63
|
-
Effect.map((result) => result !== null),
|
|
64
|
-
);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
const executable =
|
|
68
|
-
typeof value === "object" &&
|
|
69
|
-
value !== null &&
|
|
70
|
-
typeof Reflect.get(value, "all") === "function"
|
|
71
|
-
? Reflect.apply(Reflect.get(value, "all") as AnyFunction, value, [])
|
|
72
|
-
: value;
|
|
73
|
-
|
|
74
|
-
if (
|
|
75
|
-
typeof executable === "object" &&
|
|
76
|
-
executable !== null &&
|
|
77
|
-
"then" in executable
|
|
78
|
-
) {
|
|
79
|
-
return fromPrismaPromise(() => executable as PromiseLike<unknown>).pipe(
|
|
80
|
-
Effect.map((result) =>
|
|
81
|
-
terminal === "first" ? Option.fromNullishOr(result) : result,
|
|
82
|
-
),
|
|
83
|
-
);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
return Effect.succeed(executable);
|
|
87
|
-
};
|
|
88
|
-
|
|
89
18
|
interface RelationValue extends Effect.Effect<unknown, PrismaError> {
|
|
90
19
|
readonly stream: Stream.Stream<unknown, PrismaError>;
|
|
91
20
|
}
|
|
92
21
|
|
|
93
|
-
|
|
22
|
+
type AnyRelationRuntime = RelationRuntime<object, AnyPostgresContract>;
|
|
94
23
|
|
|
95
|
-
const
|
|
96
|
-
|
|
97
|
-
):
|
|
24
|
+
const runtimes = new WeakMap<object, AnyRelationRuntime>();
|
|
25
|
+
|
|
26
|
+
const runtimeOf = (self: object): AnyRelationRuntime => {
|
|
98
27
|
const runtime = runtimes.get(self);
|
|
99
28
|
if (runtime === undefined) {
|
|
100
29
|
throw new TypeError("Relation runtime is unavailable");
|
|
101
30
|
}
|
|
102
|
-
return runtime
|
|
31
|
+
return runtime;
|
|
103
32
|
};
|
|
104
33
|
|
|
105
|
-
const relationEffect = <
|
|
106
|
-
|
|
107
|
-
Contract extends AnyPostgresContract,
|
|
108
|
-
>(
|
|
109
|
-
self: RelationValue,
|
|
110
|
-
): Effect.Effect<unknown, PrismaError> => {
|
|
111
|
-
const runtime = runtimeOf<Models, Contract>(self);
|
|
34
|
+
const relationEffect = (self: object): Effect.Effect<unknown, PrismaError> => {
|
|
35
|
+
const runtime = runtimeOf(self);
|
|
112
36
|
return Effect.flatMap(runtime.resolveExecutor, (executor) =>
|
|
113
37
|
executeQuery(
|
|
114
38
|
executor,
|
|
115
39
|
Effect.suspend(() =>
|
|
116
|
-
evaluateResult(
|
|
117
|
-
replayRecipe(
|
|
118
|
-
executor.models,
|
|
119
|
-
runtime.recipe,
|
|
120
|
-
executor.identity,
|
|
121
|
-
executor.transactionIdentity,
|
|
122
|
-
),
|
|
123
|
-
runtime.terminal,
|
|
124
|
-
),
|
|
40
|
+
evaluateResult(replayRecipe(executor.models, runtime.recipe, executor.identity, executor.transactionIdentity), runtime.terminal),
|
|
125
41
|
),
|
|
126
42
|
),
|
|
127
43
|
).pipe(
|
|
128
|
-
Effect.withSpan(
|
|
129
|
-
|
|
130
|
-
{
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
"db.model": runtime.recipe.model,
|
|
135
|
-
"db.operation": String(runtime.terminal ?? "all"),
|
|
136
|
-
},
|
|
44
|
+
Effect.withSpan(`prisma.${runtime.recipe.model}.${String(runtime.terminal ?? "all")}`, {
|
|
45
|
+
kind: "client",
|
|
46
|
+
attributes: {
|
|
47
|
+
"db.system": "postgresql",
|
|
48
|
+
"db.model": runtime.recipe.model,
|
|
49
|
+
"db.operation": String(runtime.terminal ?? "all"),
|
|
137
50
|
},
|
|
138
|
-
),
|
|
51
|
+
}),
|
|
139
52
|
);
|
|
140
53
|
};
|
|
141
54
|
|
|
@@ -147,21 +60,13 @@ const RelationPrototype = {
|
|
|
147
60
|
},
|
|
148
61
|
}),
|
|
149
62
|
get stream(): Stream.Stream<unknown, PrismaError> {
|
|
150
|
-
const
|
|
151
|
-
const runtime = runtimeOf<Record<string, unknown>, AnyPostgresContract>(
|
|
152
|
-
relation,
|
|
153
|
-
);
|
|
63
|
+
const runtime = runtimeOf(this);
|
|
154
64
|
return makeRelationStream(runtime.resolveExecutor, runtime.recipe);
|
|
155
65
|
},
|
|
156
66
|
};
|
|
157
67
|
|
|
158
|
-
const makeRelationProxy =
|
|
159
|
-
|
|
160
|
-
Contract extends AnyPostgresContract,
|
|
161
|
-
>(
|
|
162
|
-
runtime: RelationRuntime<Models, Contract>,
|
|
163
|
-
): unknown => {
|
|
164
|
-
const target = Object.create(RelationPrototype) as RelationValue;
|
|
68
|
+
const makeRelationProxy = (runtime: AnyRelationRuntime): object => {
|
|
69
|
+
const target: object = Object.create(RelationPrototype);
|
|
165
70
|
const proxy = new Proxy(target, {
|
|
166
71
|
get(self, property, receiver) {
|
|
167
72
|
if (property === "then") {
|
|
@@ -206,6 +111,12 @@ const makeRelationProxy = <
|
|
|
206
111
|
return proxy;
|
|
207
112
|
};
|
|
208
113
|
|
|
114
|
+
// The proxy replays every Relation call onto the Prisma collection for `model`, so it has that collection's Relation surface.
|
|
115
|
+
function asRelation<Collection, Model extends string>(proxy: object): Relation<Collection, undefined, Model>;
|
|
116
|
+
function asRelation(proxy: object): unknown {
|
|
117
|
+
return proxy;
|
|
118
|
+
}
|
|
119
|
+
|
|
209
120
|
export const makeModelRelation = <
|
|
210
121
|
Collection,
|
|
211
122
|
Models extends object,
|
|
@@ -214,13 +125,12 @@ export const makeModelRelation = <
|
|
|
214
125
|
>(
|
|
215
126
|
executor: DatabaseExecutor<Models, ExecutorContract>,
|
|
216
127
|
model: Model,
|
|
217
|
-
resolveExecutor: Effect.Effect<
|
|
218
|
-
DatabaseExecutor<Models, ExecutorContract>,
|
|
219
|
-
PrismaError
|
|
220
|
-
> = Effect.succeed(executor),
|
|
128
|
+
resolveExecutor: Effect.Effect<DatabaseExecutor<Models, ExecutorContract>, PrismaError> = Effect.succeed(executor),
|
|
221
129
|
): Relation<Collection, undefined, Model> =>
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
130
|
+
asRelation<Collection, Model>(
|
|
131
|
+
makeRelationProxy({
|
|
132
|
+
executor,
|
|
133
|
+
recipe: rootRecipe(model),
|
|
134
|
+
resolveExecutor,
|
|
135
|
+
}),
|
|
136
|
+
);
|
|
@@ -1,20 +1,22 @@
|
|
|
1
1
|
import { Effect, Stream } from "effect";
|
|
2
|
-
import { isPrismaFailure, type PrismaError, toPrismaError } from "../error.
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
2
|
+
import { isPrismaFailure, type PrismaError, toPrismaError } from "../error.ts";
|
|
3
|
+
import { hasMethod, invokeMethod, isAsyncIterable } from "./dynamic.ts";
|
|
4
|
+
import type { AnyPostgresContract, DatabaseExecutor } from "./executor.ts";
|
|
5
|
+
import { fromPrismaPromise } from "./promise.ts";
|
|
6
|
+
import { executeQuery } from "./query-execution.ts";
|
|
7
|
+
import { type RelationRecipe, replayRecipe } from "./recipe.ts";
|
|
7
8
|
|
|
8
|
-
|
|
9
|
+
const collectRows = (iterable: AsyncIterable<unknown>): Effect.Effect<Array<unknown>, PrismaError> =>
|
|
10
|
+
fromPrismaPromise(async () => {
|
|
11
|
+
const rows: Array<unknown> = [];
|
|
12
|
+
for await (const row of iterable) {
|
|
13
|
+
rows.push(row);
|
|
14
|
+
}
|
|
15
|
+
return rows;
|
|
16
|
+
});
|
|
9
17
|
|
|
10
|
-
export const makeRelationStream = <
|
|
11
|
-
Models
|
|
12
|
-
Contract extends AnyPostgresContract,
|
|
13
|
-
>(
|
|
14
|
-
resolveExecutor: Effect.Effect<
|
|
15
|
-
DatabaseExecutor<Models, Contract>,
|
|
16
|
-
PrismaError
|
|
17
|
-
>,
|
|
18
|
+
export const makeRelationStream = <Models extends object, Contract extends AnyPostgresContract>(
|
|
19
|
+
resolveExecutor: Effect.Effect<DatabaseExecutor<Models, Contract>, PrismaError>,
|
|
18
20
|
recipe: RelationRecipe,
|
|
19
21
|
): Stream.Stream<unknown, PrismaError> => {
|
|
20
22
|
return Stream.unwrap(
|
|
@@ -22,49 +24,21 @@ export const makeRelationStream = <
|
|
|
22
24
|
executeQuery(
|
|
23
25
|
executor,
|
|
24
26
|
Effect.sync(() => {
|
|
25
|
-
const collection = replayRecipe(
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
executor.identity,
|
|
29
|
-
executor.transactionIdentity,
|
|
30
|
-
);
|
|
31
|
-
if (
|
|
32
|
-
typeof collection !== "object" ||
|
|
33
|
-
collection === null ||
|
|
34
|
-
typeof Reflect.get(collection, "all") !== "function"
|
|
35
|
-
) {
|
|
27
|
+
const collection = replayRecipe(executor.models, recipe, executor.identity, executor.transactionIdentity);
|
|
28
|
+
const iterable = hasMethod(collection, "all") ? invokeMethod(collection, "all", []) : undefined;
|
|
29
|
+
if (!isAsyncIterable(iterable)) {
|
|
36
30
|
throw new TypeError("Only collection Relations can be streamed");
|
|
37
31
|
}
|
|
38
|
-
const iterable = Reflect.apply(
|
|
39
|
-
Reflect.get(collection, "all") as AnyFunction,
|
|
40
|
-
collection,
|
|
41
|
-
[],
|
|
42
|
-
) as AsyncIterable<unknown>;
|
|
43
32
|
|
|
44
33
|
if (executor.mode === "root") {
|
|
45
34
|
return Stream.fromAsyncIterable(iterable, (error) => error).pipe(
|
|
46
|
-
Stream.catch((error) =>
|
|
47
|
-
isPrismaFailure(error)
|
|
48
|
-
? Stream.fail(toPrismaError(error))
|
|
49
|
-
: Stream.die(error),
|
|
50
|
-
),
|
|
35
|
+
Stream.catch((error) => (isPrismaFailure(error) ? Stream.fail(toPrismaError(error)) : Stream.die(error))),
|
|
51
36
|
);
|
|
52
37
|
}
|
|
53
38
|
|
|
54
|
-
const buffered = executeQuery(
|
|
55
|
-
executor,
|
|
56
|
-
fromPrismaPromise(async () => {
|
|
57
|
-
const rows: Array<unknown> = [];
|
|
58
|
-
for await (const row of iterable) {
|
|
59
|
-
rows.push(row);
|
|
60
|
-
}
|
|
61
|
-
return rows;
|
|
62
|
-
}),
|
|
63
|
-
);
|
|
39
|
+
const buffered = executeQuery(executor, collectRows(iterable));
|
|
64
40
|
|
|
65
|
-
return Stream.unwrap(
|
|
66
|
-
Effect.map(buffered, (rows) => Stream.fromIterable(rows)),
|
|
67
|
-
);
|
|
41
|
+
return Stream.unwrap(Effect.map(buffered, (rows) => Stream.fromIterable(rows)));
|
|
68
42
|
}),
|
|
69
43
|
),
|
|
70
44
|
),
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ExecutionContext } from "@prisma-next/sql-runtime";
|
|
2
|
-
import type { AnySqlContract } from "./executor.
|
|
2
|
+
import type { AnySqlContract } from "./executor.ts";
|
|
3
3
|
|
|
4
4
|
type CodecRegistry = ExecutionContext<AnySqlContract>["contractCodecs"];
|
|
5
5
|
type ContractCodec = ReturnType<CodecRegistry["forCodecRef"]>;
|
|
@@ -8,8 +8,7 @@ type CodecJson = Parameters<ContractCodec["decodeJson"]>[0];
|
|
|
8
8
|
|
|
9
9
|
const sqliteDatetimeCodecId = "sqlite/datetime@1";
|
|
10
10
|
|
|
11
|
-
const zonelessDatetime =
|
|
12
|
-
/^(\d{4}-\d{2}-\d{2})[T ](\d{2}:\d{2}(?::\d{2})?(?:\.\d+)?)$/;
|
|
11
|
+
const zonelessDatetime = /^(\d{4}-\d{2}-\d{2})[T ](\d{2}:\d{2}(?::\d{2})?(?:\.\d+)?)$/;
|
|
13
12
|
|
|
14
13
|
/**
|
|
15
14
|
* Stamp `Z` onto a SQLite datetime string that carries no zone designator.
|
|
@@ -22,22 +21,14 @@ const zonelessDatetime =
|
|
|
22
21
|
* `new Date` already reads as UTC), and anything that is not a datetime string
|
|
23
22
|
* are returned unchanged.
|
|
24
23
|
*/
|
|
25
|
-
export const normalizeSqliteDatetime = (value: string): string =>
|
|
26
|
-
value.replace(zonelessDatetime, "$1T$2Z");
|
|
24
|
+
export const normalizeSqliteDatetime = (value: string): string => value.replace(zonelessDatetime, "$1T$2Z");
|
|
27
25
|
|
|
28
26
|
const decodeAsUtc = (codec: ContractCodec): ContractCodec => ({
|
|
29
27
|
id: sqliteDatetimeCodecId,
|
|
30
28
|
encode: (value, context) => codec.encode(value, context),
|
|
31
|
-
decode: (wire: CodecWire, context) =>
|
|
32
|
-
codec.decode(
|
|
33
|
-
typeof wire === "string" ? normalizeSqliteDatetime(wire) : wire,
|
|
34
|
-
context,
|
|
35
|
-
),
|
|
29
|
+
decode: (wire: CodecWire, context) => codec.decode(typeof wire === "string" ? normalizeSqliteDatetime(wire) : wire, context),
|
|
36
30
|
encodeJson: (value) => codec.encodeJson(value),
|
|
37
|
-
decodeJson: (json: CodecJson) =>
|
|
38
|
-
codec.decodeJson(
|
|
39
|
-
typeof json === "string" ? normalizeSqliteDatetime(json) : json,
|
|
40
|
-
),
|
|
31
|
+
decodeJson: (json: CodecJson) => codec.decodeJson(typeof json === "string" ? normalizeSqliteDatetime(json) : json),
|
|
41
32
|
});
|
|
42
33
|
|
|
43
34
|
/**
|
|
@@ -55,9 +46,7 @@ const decodeAsUtc = (codec: ContractCodec): ContractCodec => ({
|
|
|
55
46
|
* Prisma Next materializes a codec through an unbound descriptor factory, so
|
|
56
47
|
* the instance's `id` accessor throws.
|
|
57
48
|
*/
|
|
58
|
-
export const decodeSqliteDatetimesAsUtc = (
|
|
59
|
-
context: ExecutionContext<AnySqlContract>,
|
|
60
|
-
): void => {
|
|
49
|
+
export const decodeSqliteDatetimesAsUtc = (context: ExecutionContext<AnySqlContract>): void => {
|
|
61
50
|
const registry = context.contractCodecs;
|
|
62
51
|
const descriptors = context.codecDescriptors;
|
|
63
52
|
const utcCodecs = new WeakMap<ContractCodec, ContractCodec>();
|
|
@@ -78,11 +67,7 @@ export const decodeSqliteDatetimesAsUtc = (
|
|
|
78
67
|
if (codec === undefined) {
|
|
79
68
|
return undefined;
|
|
80
69
|
}
|
|
81
|
-
const reference = descriptors.codecRefForColumn(
|
|
82
|
-
namespaceId,
|
|
83
|
-
table,
|
|
84
|
-
column,
|
|
85
|
-
);
|
|
70
|
+
const reference = descriptors.codecRefForColumn(namespaceId, table, column);
|
|
86
71
|
return reference?.codecId === sqliteDatetimeCodecId ? wrap(codec) : codec;
|
|
87
72
|
},
|
|
88
73
|
forCodecRef: (reference) => {
|
|
@@ -91,5 +76,7 @@ export const decodeSqliteDatetimesAsUtc = (
|
|
|
91
76
|
},
|
|
92
77
|
};
|
|
93
78
|
|
|
94
|
-
(context
|
|
79
|
+
if (!Reflect.set(context, "contractCodecs", utcRegistry)) {
|
|
80
|
+
throw new TypeError("The SQLite execution context does not accept a codec registry");
|
|
81
|
+
}
|
|
95
82
|
};
|
|
@@ -22,10 +22,7 @@ export const assertFileBackedPath = (path: string): void => {
|
|
|
22
22
|
* it opens, but it has no hook for anything else, so durable pragmas such as
|
|
23
23
|
* `journal_mode` are applied once against the database file itself.
|
|
24
24
|
*/
|
|
25
|
-
export const applySqlitePragmas = (
|
|
26
|
-
path: string,
|
|
27
|
-
pragmas: ReadonlyArray<string>,
|
|
28
|
-
): void => {
|
|
25
|
+
export const applySqlitePragmas = (path: string, pragmas: ReadonlyArray<string>): void => {
|
|
29
26
|
if (pragmas.length === 0) {
|
|
30
27
|
return;
|
|
31
28
|
}
|
package/src/internal/testing.ts
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
import type { Effect } from "effect";
|
|
2
|
-
import type { PrismaError } from "../error.
|
|
2
|
+
import type { PrismaError } from "../error.ts";
|
|
3
3
|
|
|
4
|
-
export const DatabaseTestingTypeId: unique symbol = Symbol.for(
|
|
5
|
-
"@shivaedev/effect-prisma/DatabaseTesting",
|
|
6
|
-
);
|
|
4
|
+
export const DatabaseTestingTypeId: unique symbol = Symbol.for("@shivaedev/effect-prisma/DatabaseTesting");
|
|
7
5
|
|
|
8
6
|
export interface DatabaseTesting<DatabaseId> {
|
|
9
7
|
readonly withTestTransaction: <A, E, R>(
|
|
@@ -15,18 +13,18 @@ export interface DatabaseWithTesting<DatabaseId> {
|
|
|
15
13
|
readonly [DatabaseTestingTypeId]: DatabaseTesting<DatabaseId>;
|
|
16
14
|
}
|
|
17
15
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
):
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
| undefined;
|
|
16
|
+
// makeSqlDatabase installs the testing hooks for the database's own service identifier.
|
|
17
|
+
function testingFor<DatabaseId>(testing: object): DatabaseTesting<DatabaseId>;
|
|
18
|
+
function testingFor(testing: object): unknown {
|
|
19
|
+
return testing;
|
|
20
|
+
}
|
|
24
21
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
22
|
+
export const getDatabaseTesting = <DatabaseId>(database: object): DatabaseTesting<DatabaseId> => {
|
|
23
|
+
const testing: unknown = Reflect.get(database, DatabaseTestingTypeId);
|
|
24
|
+
|
|
25
|
+
if (typeof testing !== "object" || testing === null || typeof Reflect.get(testing, "withTestTransaction") !== "function") {
|
|
26
|
+
throw new TypeError("The database was not created by this copy of @shivaedev/effect-prisma");
|
|
29
27
|
}
|
|
30
28
|
|
|
31
|
-
return testing;
|
|
29
|
+
return testingFor<DatabaseId>(testing);
|
|
32
30
|
};
|
|
@@ -1,45 +1,26 @@
|
|
|
1
1
|
import { orm } from "@prisma-next/sql-orm-client";
|
|
2
|
-
import type {
|
|
3
|
-
RuntimeConnection,
|
|
4
|
-
RuntimeTransaction,
|
|
5
|
-
} from "@prisma-next/sql-runtime";
|
|
6
2
|
import { Effect, Exit, Semaphore } from "effect";
|
|
7
|
-
import type { PrismaError } from "../error.
|
|
8
|
-
import type
|
|
9
|
-
import {
|
|
3
|
+
import type { PrismaError } from "../error.ts";
|
|
4
|
+
import { type SettledConnection, type SettledTransaction, settleConnection } from "./adapters/transaction-settlement.ts";
|
|
5
|
+
import type { AnySqlContract, DatabaseExecutor } from "./executor.ts";
|
|
6
|
+
import { fromPrismaPromise } from "./promise.ts";
|
|
10
7
|
|
|
11
|
-
export type TransactionOrm<Contract extends AnySqlContract> = ReturnType<
|
|
12
|
-
typeof orm<Contract>
|
|
13
|
-
>;
|
|
8
|
+
export type TransactionOrm<Contract extends AnySqlContract> = ReturnType<typeof orm<Contract>>;
|
|
14
9
|
|
|
15
10
|
export const withTransactionSemaphore = <A, E, R>(
|
|
16
11
|
semaphore: Semaphore.Semaphore | undefined,
|
|
17
12
|
transaction: Effect.Effect<A, E, R>,
|
|
18
|
-
): Effect.Effect<A, E, R> =>
|
|
19
|
-
semaphore === undefined ? transaction : semaphore.withPermit(transaction);
|
|
13
|
+
): Effect.Effect<A, E, R> => (semaphore === undefined ? transaction : semaphore.withPermit(transaction));
|
|
20
14
|
|
|
21
|
-
export interface TransactionResource<
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
> {
|
|
25
|
-
readonly connection: RuntimeConnection;
|
|
26
|
-
readonly transaction: RuntimeTransaction;
|
|
15
|
+
export interface TransactionResource<Models extends object, Contract extends AnySqlContract> {
|
|
16
|
+
readonly connection: SettledConnection;
|
|
17
|
+
readonly transaction: SettledTransaction;
|
|
27
18
|
readonly executor: DatabaseExecutor<Models, Contract> & {
|
|
28
19
|
readonly querySemaphore: Semaphore.Semaphore;
|
|
29
20
|
};
|
|
30
21
|
}
|
|
31
22
|
|
|
32
|
-
const
|
|
33
|
-
code: string,
|
|
34
|
-
cause: unknown,
|
|
35
|
-
details?: Readonly<Record<string, unknown>>,
|
|
36
|
-
): Error & { readonly code: string } =>
|
|
37
|
-
Object.assign(new Error(code, { cause }), { code, ...details });
|
|
38
|
-
|
|
39
|
-
export const acquireTransaction = <
|
|
40
|
-
Models extends object,
|
|
41
|
-
Contract extends AnySqlContract,
|
|
42
|
-
>(
|
|
23
|
+
export const acquireTransaction = <Models extends object, Contract extends AnySqlContract>(
|
|
43
24
|
current: DatabaseExecutor<Models, Contract>,
|
|
44
25
|
models: (orm: TransactionOrm<Contract>) => Models,
|
|
45
26
|
mode: "test" | "transaction",
|
|
@@ -77,32 +58,17 @@ export const acquireTransaction = <
|
|
|
77
58
|
}
|
|
78
59
|
});
|
|
79
60
|
|
|
80
|
-
export const releaseTransaction = <
|
|
81
|
-
Models extends object,
|
|
82
|
-
Contract extends AnySqlContract,
|
|
83
|
-
A,
|
|
84
|
-
E,
|
|
85
|
-
>(
|
|
61
|
+
export const releaseTransaction = <Models extends object, Contract extends AnySqlContract, A, E>(
|
|
86
62
|
resource: TransactionResource<Models, Contract>,
|
|
87
63
|
exit: Exit.Exit<A, E>,
|
|
88
64
|
): Effect.Effect<void, PrismaError> => settleTransaction(resource, exit, true);
|
|
89
65
|
|
|
90
|
-
export const releaseTestTransaction = <
|
|
91
|
-
Models extends object,
|
|
92
|
-
Contract extends AnySqlContract,
|
|
93
|
-
A,
|
|
94
|
-
E,
|
|
95
|
-
>(
|
|
66
|
+
export const releaseTestTransaction = <Models extends object, Contract extends AnySqlContract, A, E>(
|
|
96
67
|
resource: TransactionResource<Models, Contract>,
|
|
97
68
|
exit: Exit.Exit<A, E>,
|
|
98
69
|
): Effect.Effect<void, PrismaError> => settleTransaction(resource, exit, false);
|
|
99
70
|
|
|
100
|
-
const settleTransaction = <
|
|
101
|
-
Models extends object,
|
|
102
|
-
Contract extends AnySqlContract,
|
|
103
|
-
A,
|
|
104
|
-
E,
|
|
105
|
-
>(
|
|
71
|
+
const settleTransaction = <Models extends object, Contract extends AnySqlContract, A, E>(
|
|
106
72
|
resource: TransactionResource<Models, Contract>,
|
|
107
73
|
exit: Exit.Exit<A, E>,
|
|
108
74
|
commitOnSuccess: boolean,
|
|
@@ -112,66 +78,7 @@ const settleTransaction = <
|
|
|
112
78
|
resource.executor.liveness.open = false;
|
|
113
79
|
}).pipe(
|
|
114
80
|
Effect.andThen(
|
|
115
|
-
resource.executor.querySemaphore.withPermit(
|
|
116
|
-
fromPrismaPromise(async () => {
|
|
117
|
-
let disposed = false;
|
|
118
|
-
let failure: unknown;
|
|
119
|
-
|
|
120
|
-
const destroy = async (reason: unknown): Promise<void> => {
|
|
121
|
-
if (disposed) {
|
|
122
|
-
return;
|
|
123
|
-
}
|
|
124
|
-
disposed = true;
|
|
125
|
-
await resource.connection.destroy(reason).catch(() => undefined);
|
|
126
|
-
};
|
|
127
|
-
|
|
128
|
-
if (commitOnSuccess && Exit.isSuccess(exit)) {
|
|
129
|
-
try {
|
|
130
|
-
await resource.transaction.commit();
|
|
131
|
-
} catch (commitError) {
|
|
132
|
-
try {
|
|
133
|
-
await resource.transaction.rollback();
|
|
134
|
-
} catch {
|
|
135
|
-
await destroy(commitError);
|
|
136
|
-
}
|
|
137
|
-
failure = runtimeFailure(
|
|
138
|
-
"RUNTIME.TRANSACTION_COMMIT_FAILED",
|
|
139
|
-
commitError,
|
|
140
|
-
);
|
|
141
|
-
}
|
|
142
|
-
} else {
|
|
143
|
-
try {
|
|
144
|
-
await resource.transaction.rollback();
|
|
145
|
-
} catch (rollbackError) {
|
|
146
|
-
await destroy(rollbackError);
|
|
147
|
-
failure = runtimeFailure(
|
|
148
|
-
"RUNTIME.TRANSACTION_ROLLBACK_FAILED",
|
|
149
|
-
rollbackError,
|
|
150
|
-
);
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
if (!disposed) {
|
|
155
|
-
try {
|
|
156
|
-
await resource.connection.release();
|
|
157
|
-
} catch (releaseError) {
|
|
158
|
-
await destroy(releaseError);
|
|
159
|
-
if (failure !== undefined) {
|
|
160
|
-
throw runtimeFailure(
|
|
161
|
-
"RUNTIME.TRANSACTION_RELEASE_FAILED",
|
|
162
|
-
failure,
|
|
163
|
-
{ releaseError },
|
|
164
|
-
);
|
|
165
|
-
}
|
|
166
|
-
throw releaseError;
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
if (failure !== undefined) {
|
|
171
|
-
throw failure;
|
|
172
|
-
}
|
|
173
|
-
}),
|
|
174
|
-
),
|
|
81
|
+
resource.executor.querySemaphore.withPermit(fromPrismaPromise(() => settleConnection(resource, commitOnSuccess && Exit.isSuccess(exit)))),
|
|
175
82
|
),
|
|
176
83
|
),
|
|
177
84
|
);
|