@shivaedev/effect-prisma 0.6.4 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +27 -0
- package/dist/bin/normalize-contract.js +2 -1
- package/dist/bin/normalize-contract.js.map +1 -1
- package/dist/database.d.ts +3 -3
- package/dist/database.d.ts.map +1 -1
- package/dist/database.js +4 -4
- package/dist/database.js.map +1 -1
- package/dist/error.js +4 -4
- package/dist/error.js.map +1 -1
- package/dist/internal/adapters/transaction-settlement.d.ts.map +1 -1
- package/dist/internal/adapters/transaction-settlement.js +1 -1
- package/dist/internal/adapters/transaction-settlement.js.map +1 -1
- package/dist/internal/client-lifecycle.d.ts +2 -2
- 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 +0 -4
- package/dist/internal/contract-normalization.d.ts.map +1 -1
- package/dist/internal/contract-normalization.js +4 -7
- package/dist/internal/contract-normalization.js.map +1 -1
- package/dist/internal/database-facade.d.ts +1 -6
- package/dist/internal/database-facade.d.ts.map +1 -1
- package/dist/internal/database-facade.js +3 -8
- package/dist/internal/database-facade.js.map +1 -1
- package/dist/internal/database-factory.d.ts +1 -5
- package/dist/internal/database-factory.d.ts.map +1 -1
- package/dist/internal/database-factory.js +1 -7
- package/dist/internal/database-factory.js.map +1 -1
- package/dist/internal/database-types.d.ts +4 -8
- package/dist/internal/database-types.d.ts.map +1 -1
- package/dist/internal/dynamic.d.ts +1 -1
- package/dist/internal/dynamic.d.ts.map +1 -1
- package/dist/internal/dynamic.js.map +1 -1
- package/dist/internal/executor.d.ts +3 -7
- 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 +2 -2
- package/dist/internal/promise.js.map +1 -1
- package/dist/internal/query-execution.d.ts +1 -1
- package/dist/internal/query-execution.d.ts.map +1 -1
- package/dist/internal/query-execution.js +1 -1
- package/dist/internal/query-execution.js.map +1 -1
- package/dist/internal/recipe.d.ts +3 -3
- package/dist/internal/recipe.d.ts.map +1 -1
- package/dist/internal/recipe.js +2 -2
- package/dist/internal/recipe.js.map +1 -1
- package/dist/internal/relation-result.d.ts +1 -1
- package/dist/internal/relation-result.d.ts.map +1 -1
- package/dist/internal/relation-result.js.map +1 -1
- package/dist/internal/relation-runtime.d.ts +2 -2
- package/dist/internal/relation-runtime.d.ts.map +1 -1
- package/dist/internal/relation-runtime.js +3 -3
- package/dist/internal/relation-runtime.js.map +1 -1
- package/dist/internal/relation-stream.d.ts +1 -1
- package/dist/internal/relation-stream.d.ts.map +1 -1
- package/dist/internal/relation-stream.js +13 -15
- package/dist/internal/relation-stream.js.map +1 -1
- package/dist/internal/sqlite-datetime.d.ts +0 -26
- package/dist/internal/sqlite-datetime.d.ts.map +1 -1
- package/dist/internal/sqlite-datetime.js +13 -34
- package/dist/internal/sqlite-datetime.js.map +1 -1
- package/dist/internal/sqlite-pragmas.d.ts +1 -13
- package/dist/internal/sqlite-pragmas.d.ts.map +1 -1
- package/dist/internal/sqlite-pragmas.js +2 -12
- 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/transaction.d.ts +3 -3
- package/dist/internal/transaction.d.ts.map +1 -1
- package/dist/internal/transaction.js +3 -3
- package/dist/internal/transaction.js.map +1 -1
- package/dist/relation/include-metadata.d.ts +1 -1
- package/dist/relation/include-metadata.d.ts.map +1 -1
- package/dist/relation/include.d.ts +7 -4
- package/dist/relation/include.d.ts.map +1 -1
- package/dist/relation/prisma-methods.d.ts +16 -16
- package/dist/relation/prisma-methods.d.ts.map +1 -1
- package/dist/relation.d.ts +3 -3
- package/dist/relation.d.ts.map +1 -1
- package/dist/sqlite.d.ts +4 -9
- package/dist/sqlite.d.ts.map +1 -1
- package/dist/sqlite.js +6 -6
- package/dist/sqlite.js.map +1 -1
- package/dist/testing/transaction.d.ts +1 -1
- package/dist/testing/transaction.d.ts.map +1 -1
- package/dist/testing/transaction.js +1 -1
- package/dist/testing/transaction.js.map +1 -1
- package/dist/testing/types.d.ts +7 -7
- package/dist/testing/types.d.ts.map +1 -1
- package/dist/testing/vitest.d.ts.map +1 -1
- package/dist/testing/vitest.js.map +1 -1
- package/dist/testing.d.ts +3 -3
- package/dist/testing.d.ts.map +1 -1
- package/dist/testing.js +2 -2
- package/dist/testing.js.map +1 -1
- package/package.json +42 -36
- package/src/bin/normalize-contract.ts +2 -1
- package/src/database.ts +7 -7
- package/src/error.ts +4 -4
- package/src/internal/adapters/transaction-settlement.ts +4 -1
- package/src/internal/client-lifecycle.ts +2 -2
- package/src/internal/contract-normalization.ts +4 -7
- package/src/internal/database-facade.ts +3 -8
- package/src/internal/database-factory.ts +5 -9
- package/src/internal/database-types.ts +5 -9
- package/src/internal/dynamic.ts +1 -1
- package/src/internal/executor.ts +3 -7
- package/src/internal/promise.ts +2 -2
- package/src/internal/query-execution.ts +1 -1
- package/src/internal/recipe.ts +8 -8
- package/src/internal/relation-result.ts +2 -2
- package/src/internal/relation-runtime.ts +6 -6
- package/src/internal/relation-stream.ts +5 -6
- package/src/internal/sqlite-datetime.ts +13 -34
- package/src/internal/sqlite-pragmas.ts +3 -13
- package/src/internal/testing.ts +1 -1
- package/src/internal/transaction.ts +5 -5
- package/src/relation/include-metadata.ts +1 -1
- package/src/relation/include.ts +36 -34
- package/src/relation/prisma-methods.ts +21 -19
- package/src/relation.ts +3 -3
- package/src/sqlite.ts +10 -15
- package/src/testing/transaction.ts +2 -2
- package/src/testing/types.ts +8 -8
- package/src/testing/vitest.ts +1 -1
- package/src/testing.ts +3 -3
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Context, Effect, Layer } from "effect";
|
|
2
|
-
import type { PrismaError } from "
|
|
2
|
+
import type { PrismaError } from "#error.ts";
|
|
3
3
|
import { type ActiveTransaction, type FacadeScope, makeFacade, runTransaction } from "./database-facade.ts";
|
|
4
4
|
import {
|
|
5
5
|
type DatabaseIdentifier,
|
|
@@ -15,10 +15,6 @@ import { releaseTestTransaction } from "./transaction.ts";
|
|
|
15
15
|
|
|
16
16
|
let activeTransactionReferenceSequence = 0;
|
|
17
17
|
|
|
18
|
-
/**
|
|
19
|
-
* Assemble the database service and its Layer around a driver-specific
|
|
20
|
-
* executor acquisition.
|
|
21
|
-
*/
|
|
22
18
|
export const makeSqlDatabase = <const Contract extends AnySqlContract, const Identifier extends string, Options>(
|
|
23
19
|
identifier: DatabaseIdentifierLiteral<Identifier>,
|
|
24
20
|
acquireExecutor: (options: Options) => Effect.Effect<DatabaseExecutor<DefaultModels<Contract>, Contract>, PrismaError>,
|
|
@@ -48,11 +44,11 @@ export const makeSqlDatabase = <const Contract extends AnySqlContract, const Ide
|
|
|
48
44
|
const withTestTransaction = <A, E, R>(
|
|
49
45
|
program: Effect.Effect<A, E, R> & (DatabaseId extends R ? unknown : never),
|
|
50
46
|
): Effect.Effect<A, E | PrismaError, DatabaseId | Exclude<R, DatabaseId>> =>
|
|
51
|
-
Effect.flatMap(Service, (facade) =>
|
|
52
|
-
|
|
47
|
+
Effect.flatMap(Service, (facade) =>
|
|
48
|
+
Effect.flatMap(executorOf(facade), (current) =>
|
|
53
49
|
runTransaction(scope, current, facade, program, releaseTestTransaction, "test", "prisma.testTransaction"),
|
|
54
|
-
)
|
|
55
|
-
|
|
50
|
+
),
|
|
51
|
+
);
|
|
56
52
|
|
|
57
53
|
const layer = (layerOptions: Options): Layer.Layer<DatabaseId, PrismaError> => {
|
|
58
54
|
const acquire = Effect.acquireRelease(
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { orm } from "@prisma-next/sql-orm-client";
|
|
2
2
|
import type { Context, Effect, Layer } from "effect";
|
|
3
|
-
import type { PrismaError } from "
|
|
4
|
-
import type { Relation } from "
|
|
3
|
+
import type { PrismaError } from "#error.ts";
|
|
4
|
+
import type { Relation } from "#relation.ts";
|
|
5
5
|
import type { AnySqlContract } from "./executor.ts";
|
|
6
6
|
|
|
7
7
|
type OrmFor<Contract extends AnySqlContract> = ReturnType<typeof orm<Contract>>;
|
|
@@ -45,20 +45,16 @@ type DatabaseModels<Contract extends AnySqlContract, Identifier extends string>
|
|
|
45
45
|
};
|
|
46
46
|
|
|
47
47
|
export type DatabaseService<Contract extends AnySqlContract, Identifier extends string> = DatabaseModels<Contract, Identifier> & {
|
|
48
|
-
transaction<A, E, R>(
|
|
48
|
+
transaction: <A, E, R>(
|
|
49
49
|
program: Effect.Effect<A, E, R> & (DatabaseIdentifier<Contract, Identifier> extends R ? unknown : never),
|
|
50
|
-
)
|
|
50
|
+
) => Effect.Effect<A, E | PrismaError, Exclude<R, DatabaseIdentifier<Contract, Identifier>>>;
|
|
51
51
|
};
|
|
52
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
53
|
export interface DatabaseServiceHolder<Contract extends AnySqlContract, Identifier extends string>
|
|
58
54
|
extends Context.Service<DatabaseIdentifier<Contract, Identifier>, DatabaseService<Contract, Identifier>> {}
|
|
59
55
|
|
|
60
56
|
export type AnyDatabase = Effect.Effect<unknown, never, unknown> & {
|
|
61
|
-
readonly layer: (...arguments_:
|
|
57
|
+
readonly layer: (...arguments_: readonly never[]) => Layer.Any;
|
|
62
58
|
};
|
|
63
59
|
|
|
64
60
|
export type DatabaseServiceOf<Database extends AnyDatabase> = Effect.Success<Database>;
|
package/src/internal/dynamic.ts
CHANGED
|
@@ -3,7 +3,7 @@ const isObjectLike = (value: unknown): value is object => (typeof value === "obj
|
|
|
3
3
|
export const hasMethod = (value: unknown, name: PropertyKey): value is object =>
|
|
4
4
|
isObjectLike(value) && typeof Reflect.get(value, name) === "function";
|
|
5
5
|
|
|
6
|
-
export const invokeMethod = (target: object, name: PropertyKey, arguments_:
|
|
6
|
+
export const invokeMethod = (target: object, name: PropertyKey, arguments_: readonly unknown[]): unknown => {
|
|
7
7
|
const method: unknown = Reflect.get(target, name);
|
|
8
8
|
if (typeof method !== "function") {
|
|
9
9
|
throw new TypeError(`${String(name)} is not a method`);
|
package/src/internal/executor.ts
CHANGED
|
@@ -6,15 +6,11 @@ import type { Semaphore } from "effect";
|
|
|
6
6
|
export type AnySqlContract = PrismaContract<SqlStorage>;
|
|
7
7
|
export type AnyPostgresContract = AnySqlContract;
|
|
8
8
|
|
|
9
|
-
/**
|
|
10
|
-
* The subset of a Prisma Next client the package depends on. Both the
|
|
11
|
-
* PostgreSQL and the SQLite client satisfy it.
|
|
12
|
-
*/
|
|
13
9
|
export interface SqlDatabaseClient<Contract extends AnySqlContract> {
|
|
14
|
-
|
|
10
|
+
close: () => Promise<void>;
|
|
15
11
|
readonly context: ExecutionContext<Contract>;
|
|
16
|
-
|
|
17
|
-
|
|
12
|
+
readonly contract: Contract;
|
|
13
|
+
runtime: () => Runtime;
|
|
18
14
|
}
|
|
19
15
|
|
|
20
16
|
export interface DatabaseExecutor<Models extends object, Contract extends AnySqlContract = AnySqlContract> {
|
package/src/internal/promise.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { Effect } from "effect";
|
|
2
|
-
import { isPrismaFailure, type PrismaError, toPrismaError } from "
|
|
2
|
+
import { isPrismaFailure, type PrismaError, toPrismaError } from "#error.ts";
|
|
3
3
|
|
|
4
4
|
export const fromPrismaPromise = <A>(evaluate: (signal: AbortSignal) => PromiseLike<A>): Effect.Effect<A, PrismaError> =>
|
|
5
5
|
Effect.tryPromise({
|
|
6
|
-
try: (signal) => Promise.resolve(evaluate(signal)),
|
|
7
6
|
catch: (error) => error,
|
|
7
|
+
try: (signal) => Promise.resolve(evaluate(signal)),
|
|
8
8
|
}).pipe(Effect.catch((error) => (isPrismaFailure(error) ? Effect.fail(toPrismaError(error)) : Effect.die(error))));
|
package/src/internal/recipe.ts
CHANGED
|
@@ -2,29 +2,29 @@ import { hasMethod, invokeMethod } from "./dynamic.ts";
|
|
|
2
2
|
import { getRelationPlan, type RelationPlan } from "./relation-plan.ts";
|
|
3
3
|
|
|
4
4
|
export interface RelationOperation {
|
|
5
|
+
readonly arguments: readonly unknown[];
|
|
5
6
|
readonly name: PropertyKey;
|
|
6
|
-
readonly arguments: ReadonlyArray<unknown>;
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
export interface RelationRecipe {
|
|
10
10
|
readonly model: string;
|
|
11
|
-
readonly parent?: RelationRecipe;
|
|
12
11
|
readonly operation?: RelationOperation;
|
|
12
|
+
readonly parent?: RelationRecipe;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
export const rootRecipe = (model: string): RelationRecipe => ({ model });
|
|
16
16
|
|
|
17
|
-
export const appendOperation = (parent: RelationRecipe, name: PropertyKey, arguments_:
|
|
17
|
+
export const appendOperation = (parent: RelationRecipe, name: PropertyKey, arguments_: readonly unknown[]): RelationRecipe => ({
|
|
18
18
|
model: parent.model,
|
|
19
|
-
parent,
|
|
20
19
|
operation: {
|
|
21
|
-
name,
|
|
22
20
|
arguments: arguments_,
|
|
21
|
+
name,
|
|
23
22
|
},
|
|
23
|
+
parent,
|
|
24
24
|
});
|
|
25
25
|
|
|
26
|
-
const operations = (recipe: RelationRecipe):
|
|
27
|
-
const reversed:
|
|
26
|
+
const operations = (recipe: RelationRecipe): readonly RelationOperation[] => {
|
|
27
|
+
const reversed: RelationOperation[] = [];
|
|
28
28
|
let current: RelationRecipe | undefined = recipe;
|
|
29
29
|
|
|
30
30
|
while (current !== undefined) {
|
|
@@ -37,7 +37,7 @@ const operations = (recipe: RelationRecipe): ReadonlyArray<RelationOperation> =>
|
|
|
37
37
|
return reversed.reverse();
|
|
38
38
|
};
|
|
39
39
|
|
|
40
|
-
const applyMethod = (current: unknown, name: PropertyKey, arguments_:
|
|
40
|
+
const applyMethod = (current: unknown, name: PropertyKey, arguments_: readonly unknown[], model: string): unknown => {
|
|
41
41
|
if (!hasMethod(current, name)) {
|
|
42
42
|
throw new TypeError(`Cannot call ${String(name)} while replaying ${model}`);
|
|
43
43
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Effect, Option } from "effect";
|
|
2
|
-
import type { PrismaError } from "
|
|
2
|
+
import type { PrismaError } from "#error.ts";
|
|
3
3
|
import { hasMethod, invokeMethod, isPromiseLike } from "./dynamic.ts";
|
|
4
4
|
import { fromPrismaPromise } from "./promise.ts";
|
|
5
5
|
|
|
@@ -10,7 +10,7 @@ const countOf = (result: unknown): unknown => (typeof result === "object" && res
|
|
|
10
10
|
|
|
11
11
|
export const evaluateResult = (value: unknown, terminal: PropertyKey | undefined): Effect.Effect<unknown, PrismaError> => {
|
|
12
12
|
if (terminal === "count" && hasMethod(value, "aggregate")) {
|
|
13
|
-
return settle(invokeMethod(value, "aggregate", [(summary: { count()
|
|
13
|
+
return settle(invokeMethod(value, "aggregate", [(summary: { count: () => unknown }) => ({ count: summary.count() })]), countOf);
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
if (terminal === "exists" && hasMethod(value, "first")) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Effect, Effectable, type Stream } from "effect";
|
|
2
|
-
import type { PrismaError } from "
|
|
3
|
-
import type { Relation } from "
|
|
2
|
+
import type { PrismaError } from "#error.ts";
|
|
3
|
+
import type { Relation } from "#relation.ts";
|
|
4
4
|
import type { AnyPostgresContract, DatabaseExecutor } from "./executor.ts";
|
|
5
5
|
import { executeQuery } from "./query-execution.ts";
|
|
6
6
|
import { appendOperation, type RelationRecipe, replayRecipe, rootRecipe } from "./recipe.ts";
|
|
@@ -42,22 +42,22 @@ const relationEffect = (self: object): Effect.Effect<unknown, PrismaError> => {
|
|
|
42
42
|
),
|
|
43
43
|
).pipe(
|
|
44
44
|
Effect.withSpan(`prisma.${runtime.recipe.model}.${String(runtime.terminal ?? "all")}`, {
|
|
45
|
-
kind: "client",
|
|
46
45
|
attributes: {
|
|
47
|
-
"db.system": "postgresql",
|
|
48
46
|
"db.model": runtime.recipe.model,
|
|
49
47
|
"db.operation": String(runtime.terminal ?? "all"),
|
|
48
|
+
"db.system": "postgresql",
|
|
50
49
|
},
|
|
50
|
+
kind: "client",
|
|
51
51
|
}),
|
|
52
52
|
);
|
|
53
53
|
};
|
|
54
54
|
|
|
55
55
|
const RelationPrototype = {
|
|
56
56
|
...Effectable.Prototype<RelationValue>({
|
|
57
|
-
label: "EffectPrismaRelation",
|
|
58
57
|
evaluate() {
|
|
59
58
|
return relationEffect(this);
|
|
60
59
|
},
|
|
60
|
+
label: "EffectPrismaRelation",
|
|
61
61
|
}),
|
|
62
62
|
get stream(): Stream.Stream<unknown, PrismaError> {
|
|
63
63
|
const runtime = runtimeOf(this);
|
|
@@ -89,7 +89,7 @@ const makeRelationProxy = (runtime: AnyRelationRuntime): object => {
|
|
|
89
89
|
terminal: "count",
|
|
90
90
|
});
|
|
91
91
|
}
|
|
92
|
-
return (...arguments_:
|
|
92
|
+
return (...arguments_: readonly unknown[]) =>
|
|
93
93
|
makeRelationProxy({
|
|
94
94
|
...runtime,
|
|
95
95
|
recipe: appendOperation(runtime.recipe, property, arguments_),
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { Effect, Stream } from "effect";
|
|
2
|
-
import { isPrismaFailure, type PrismaError, toPrismaError } from "
|
|
2
|
+
import { isPrismaFailure, type PrismaError, toPrismaError } from "#error.ts";
|
|
3
3
|
import { hasMethod, invokeMethod, isAsyncIterable } from "./dynamic.ts";
|
|
4
4
|
import type { AnyPostgresContract, DatabaseExecutor } from "./executor.ts";
|
|
5
5
|
import { fromPrismaPromise } from "./promise.ts";
|
|
6
6
|
import { executeQuery } from "./query-execution.ts";
|
|
7
7
|
import { type RelationRecipe, replayRecipe } from "./recipe.ts";
|
|
8
8
|
|
|
9
|
-
const collectRows = (iterable: AsyncIterable<unknown>): Effect.Effect<
|
|
9
|
+
const collectRows = (iterable: AsyncIterable<unknown>): Effect.Effect<unknown[], PrismaError> =>
|
|
10
10
|
fromPrismaPromise(async () => {
|
|
11
|
-
const rows:
|
|
11
|
+
const rows: unknown[] = [];
|
|
12
12
|
for await (const row of iterable) {
|
|
13
13
|
rows.push(row);
|
|
14
14
|
}
|
|
@@ -18,8 +18,8 @@ const collectRows = (iterable: AsyncIterable<unknown>): Effect.Effect<Array<unkn
|
|
|
18
18
|
export const makeRelationStream = <Models extends object, Contract extends AnyPostgresContract>(
|
|
19
19
|
resolveExecutor: Effect.Effect<DatabaseExecutor<Models, Contract>, PrismaError>,
|
|
20
20
|
recipe: RelationRecipe,
|
|
21
|
-
): Stream.Stream<unknown, PrismaError> =>
|
|
22
|
-
|
|
21
|
+
): Stream.Stream<unknown, PrismaError> =>
|
|
22
|
+
Stream.unwrap(
|
|
23
23
|
Effect.flatMap(resolveExecutor, (executor) =>
|
|
24
24
|
executeQuery(
|
|
25
25
|
executor,
|
|
@@ -43,4 +43,3 @@ export const makeRelationStream = <Models extends object, Contract extends AnyPo
|
|
|
43
43
|
),
|
|
44
44
|
),
|
|
45
45
|
);
|
|
46
|
-
};
|
|
@@ -8,44 +8,23 @@ type CodecJson = Parameters<ContractCodec["decodeJson"]>[0];
|
|
|
8
8
|
|
|
9
9
|
const sqliteDatetimeCodecId = "sqlite/datetime@1";
|
|
10
10
|
|
|
11
|
-
const zonelessDatetime = /^(\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+)?)$/u;
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
*
|
|
16
|
-
* SQLite stores `DateTime` as text and computes `datetime('now')` — the default
|
|
17
|
-
* `prisma-next db init` generates — in UTC, but writes it as
|
|
18
|
-
* `YYYY-MM-DD HH:MM:SS` with no zone. `new Date` reads that form as local time,
|
|
19
|
-
* so decoding it unchanged shifts the instant by the process' UTC offset.
|
|
20
|
-
* Values that already carry `Z` or a numeric offset, date-only values (which
|
|
21
|
-
* `new Date` already reads as UTC), and anything that is not a datetime string
|
|
22
|
-
* are returned unchanged.
|
|
23
|
-
*/
|
|
13
|
+
// SQLite computes `datetime('now')`, the default `prisma-next db init` generates, in UTC but writes it as `YYYY-MM-DD HH:MM:SS`
|
|
14
|
+
// with no zone, and `new Date` reads that form as local time. Values with a zone and date-only values already read as UTC.
|
|
24
15
|
export const normalizeSqliteDatetime = (value: string): string => value.replace(zonelessDatetime, "$1T$2Z");
|
|
25
16
|
|
|
26
17
|
const decodeAsUtc = (codec: ContractCodec): ContractCodec => ({
|
|
27
|
-
id: sqliteDatetimeCodecId,
|
|
28
|
-
encode: (value, context) => codec.encode(value, context),
|
|
29
18
|
decode: (wire: CodecWire, context) => codec.decode(typeof wire === "string" ? normalizeSqliteDatetime(wire) : wire, context),
|
|
30
|
-
encodeJson: (value) => codec.encodeJson(value),
|
|
31
19
|
decodeJson: (json: CodecJson) => codec.decodeJson(typeof json === "string" ? normalizeSqliteDatetime(json) : json),
|
|
20
|
+
encode: (value, context) => codec.encode(value, context),
|
|
21
|
+
encodeJson: (value) => codec.encodeJson(value),
|
|
22
|
+
id: sqliteDatetimeCodecId,
|
|
32
23
|
});
|
|
33
24
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
*
|
|
38
|
-
* The registry is the single resolution point for both row decoding and the
|
|
39
|
-
* JSON decoding of included relations, so wrapping it covers every read path
|
|
40
|
-
* without touching encoding. Prisma Next rejects a second descriptor for an
|
|
41
|
-
* already-registered codec id, so an extension pack cannot replace the codec
|
|
42
|
-
* itself. Must run before the first query, because the runtime reads the
|
|
43
|
-
* registry off the context when it is created.
|
|
44
|
-
*
|
|
45
|
-
* Codecs are identified by the resolved reference rather than by `codec.id`:
|
|
46
|
-
* Prisma Next materializes a codec through an unbound descriptor factory, so
|
|
47
|
-
* the instance's `id` accessor throws.
|
|
48
|
-
*/
|
|
25
|
+
// The registry resolves codecs for rows and included relations alike, and Prisma Next rejects a second descriptor for a registered
|
|
26
|
+
// codec id, so wrapping the registry is the one way to cover every read. It must run before the first query, which reads the
|
|
27
|
+
// registry off the context. Codecs are matched by reference because a materialized codec's `id` accessor throws.
|
|
49
28
|
export const decodeSqliteDatetimesAsUtc = (context: ExecutionContext<AnySqlContract>): void => {
|
|
50
29
|
const registry = context.contractCodecs;
|
|
51
30
|
const descriptors = context.codecDescriptors;
|
|
@@ -62,6 +41,10 @@ export const decodeSqliteDatetimesAsUtc = (context: ExecutionContext<AnySqlContr
|
|
|
62
41
|
};
|
|
63
42
|
|
|
64
43
|
const utcRegistry: CodecRegistry = {
|
|
44
|
+
forCodecRef: (reference) => {
|
|
45
|
+
const codec = registry.forCodecRef(reference);
|
|
46
|
+
return reference.codecId === sqliteDatetimeCodecId ? wrap(codec) : codec;
|
|
47
|
+
},
|
|
65
48
|
forColumn: (namespaceId, table, column) => {
|
|
66
49
|
const codec = registry.forColumn(namespaceId, table, column);
|
|
67
50
|
if (codec === undefined) {
|
|
@@ -70,10 +53,6 @@ export const decodeSqliteDatetimesAsUtc = (context: ExecutionContext<AnySqlContr
|
|
|
70
53
|
const reference = descriptors.codecRefForColumn(namespaceId, table, column);
|
|
71
54
|
return reference?.codecId === sqliteDatetimeCodecId ? wrap(codec) : codec;
|
|
72
55
|
},
|
|
73
|
-
forCodecRef: (reference) => {
|
|
74
|
-
const codec = registry.forCodecRef(reference);
|
|
75
|
-
return reference.codecId === sqliteDatetimeCodecId ? wrap(codec) : codec;
|
|
76
|
-
},
|
|
77
56
|
};
|
|
78
57
|
|
|
79
58
|
if (!Reflect.set(context, "contractCodecs", utcRegistry)) {
|
|
@@ -2,11 +2,7 @@ import { DatabaseSync } from "node:sqlite";
|
|
|
2
2
|
|
|
3
3
|
export const MEMORY_PATH = ":memory:";
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
* Prisma Next's SQLite driver opens a fresh `node:sqlite` connection for every
|
|
7
|
-
* `RuntimeConnection`, so an in-memory database would give each transaction its
|
|
8
|
-
* own empty database.
|
|
9
|
-
*/
|
|
5
|
+
// Prisma Next's SQLite driver opens a fresh `node:sqlite` connection per `RuntimeConnection`, so each transaction would get its own empty in-memory database.
|
|
10
6
|
export const assertFileBackedPath = (path: string): void => {
|
|
11
7
|
if (path === MEMORY_PATH || path.trim().length === 0) {
|
|
12
8
|
throw new TypeError(
|
|
@@ -15,14 +11,8 @@ export const assertFileBackedPath = (path: string): void => {
|
|
|
15
11
|
}
|
|
16
12
|
};
|
|
17
13
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
*
|
|
21
|
-
* The driver already sets `foreign_keys` and `busy_timeout` on every connection
|
|
22
|
-
* it opens, but it has no hook for anything else, so durable pragmas such as
|
|
23
|
-
* `journal_mode` are applied once against the database file itself.
|
|
24
|
-
*/
|
|
25
|
-
export const applySqlitePragmas = (path: string, pragmas: ReadonlyArray<string>): void => {
|
|
14
|
+
// The driver has no hook for pragmas beyond `foreign_keys` and `busy_timeout`, so durable ones such as `journal_mode` go to the file once.
|
|
15
|
+
export const applySqlitePragmas = (path: string, pragmas: readonly string[]): void => {
|
|
26
16
|
if (pragmas.length === 0) {
|
|
27
17
|
return;
|
|
28
18
|
}
|
package/src/internal/testing.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { orm } from "@prisma-next/sql-orm-client";
|
|
2
2
|
import { Effect, Exit, Semaphore } from "effect";
|
|
3
|
-
import type { PrismaError } from "
|
|
4
|
-
import { type SettledConnection, type SettledTransaction, settleConnection } from "
|
|
3
|
+
import type { PrismaError } from "#error.ts";
|
|
4
|
+
import { type SettledConnection, type SettledTransaction, settleConnection } from "#internal/adapters/transaction-settlement.ts";
|
|
5
5
|
import type { AnySqlContract, DatabaseExecutor } from "./executor.ts";
|
|
6
6
|
import { fromPrismaPromise } from "./promise.ts";
|
|
7
7
|
|
|
@@ -14,10 +14,10 @@ export const withTransactionSemaphore = <A, E, R>(
|
|
|
14
14
|
|
|
15
15
|
export interface TransactionResource<Models extends object, Contract extends AnySqlContract> {
|
|
16
16
|
readonly connection: SettledConnection;
|
|
17
|
-
readonly transaction: SettledTransaction;
|
|
18
17
|
readonly executor: DatabaseExecutor<Models, Contract> & {
|
|
19
18
|
readonly querySemaphore: Semaphore.Semaphore;
|
|
20
19
|
};
|
|
20
|
+
readonly transaction: SettledTransaction;
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
export const acquireTransaction = <Models extends object, Contract extends AnySqlContract>(
|
|
@@ -31,13 +31,12 @@ export const acquireTransaction = <Models extends object, Contract extends AnySq
|
|
|
31
31
|
try {
|
|
32
32
|
const transaction = await connection.transaction();
|
|
33
33
|
const transactionOrm = orm({
|
|
34
|
-
runtime: transaction,
|
|
35
34
|
context: current.client.context,
|
|
35
|
+
runtime: transaction,
|
|
36
36
|
});
|
|
37
37
|
|
|
38
38
|
return {
|
|
39
39
|
connection,
|
|
40
|
-
transaction,
|
|
41
40
|
executor: {
|
|
42
41
|
client: current.client,
|
|
43
42
|
identity: current.identity,
|
|
@@ -51,6 +50,7 @@ export const acquireTransaction = <Models extends object, Contract extends AnySq
|
|
|
51
50
|
transactionIdentity: {},
|
|
52
51
|
transactionSemaphore: current.transactionSemaphore,
|
|
53
52
|
},
|
|
53
|
+
transaction,
|
|
54
54
|
};
|
|
55
55
|
} catch (error) {
|
|
56
56
|
await connection.destroy(error).catch(() => undefined);
|
|
@@ -62,7 +62,7 @@ type AnyNullableField<Fields, Names extends readonly string[]> = Names extends r
|
|
|
62
62
|
|
|
63
63
|
export type IncludedRelationValue<Contract extends AnyPostgresContract, Model extends string, RelationName extends string, Value> =
|
|
64
64
|
RelationCardinalityOf<Contract, Model, RelationName> extends "1:N" | "N:M"
|
|
65
|
-
?
|
|
65
|
+
? Value[]
|
|
66
66
|
: RelationCardinalityOf<Contract, Model, RelationName> extends "N:1"
|
|
67
67
|
? AnyNullableField<ModelFields<Contract, Model>, RelationLocalFields<Contract, Model, RelationName>> extends true
|
|
68
68
|
? Value | null
|
package/src/relation/include.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { DefaultModelRow, Collection as PrismaCollection, RelationNames } from "@prisma-next/sql-orm-client";
|
|
2
|
-
import type { Relation, RelationQuery } from "
|
|
2
|
+
import type { Relation, RelationQuery } from "#relation.ts";
|
|
3
3
|
import type { AnyPostgresContract, IncludedRelationValue, IsToManyRelation, RelatedModelNameOf } from "./include-metadata.ts";
|
|
4
4
|
|
|
5
5
|
type Simplify<Value> = { [Key in keyof Value]: Value[Key] };
|
|
@@ -43,40 +43,42 @@ type WithIncludedRelation<Collection, Contract extends AnyPostgresContract, Mode
|
|
|
43
43
|
? PrismaCollection<Contract, Model, Simplify<Row & { readonly [Key in RelationName]: Value }>, State>
|
|
44
44
|
: never;
|
|
45
45
|
|
|
46
|
+
interface IncludeOverloads<Collection, Contract extends AnyPostgresContract, Model extends string, DatabaseId> {
|
|
47
|
+
<RelationName extends RelationNames<Contract, Model>>(
|
|
48
|
+
relationName: RelationName,
|
|
49
|
+
): Relation<
|
|
50
|
+
WithIncludedRelation<
|
|
51
|
+
Collection,
|
|
52
|
+
Contract,
|
|
53
|
+
Model,
|
|
54
|
+
RelationName,
|
|
55
|
+
IncludedRelationValue<Contract, Model, RelationName, DefaultModelRow<Contract, RelatedModelNameOf<Contract, Model, RelationName>>>
|
|
56
|
+
>,
|
|
57
|
+
Contract,
|
|
58
|
+
Model,
|
|
59
|
+
DatabaseId
|
|
60
|
+
>;
|
|
61
|
+
<RelationName extends RelationNames<Contract, Model>, Query>(
|
|
62
|
+
relationName: RelationName,
|
|
63
|
+
query: Query & AcceptRelatedQuery<Query, Contract, RelatedModelNameOf<Contract, Model, RelationName>, DatabaseId>,
|
|
64
|
+
): Relation<
|
|
65
|
+
WithIncludedRelation<Collection, Contract, Model, RelationName, IncludedQueryValue<Query, Contract, Model, RelationName>>,
|
|
66
|
+
Contract,
|
|
67
|
+
Model,
|
|
68
|
+
DatabaseId
|
|
69
|
+
>;
|
|
70
|
+
<RelationName extends RelationNames<Contract, Model>, Shape extends Readonly<Record<string, unknown>>>(
|
|
71
|
+
relationName: RelationName,
|
|
72
|
+
shape: IsToManyRelation<Contract, Model, RelationName> extends true
|
|
73
|
+
? keyof Shape extends never
|
|
74
|
+
? never
|
|
75
|
+
: Shape & AcceptRelatedQueryShape<Shape, Contract, RelatedModelNameOf<Contract, Model, RelationName>, DatabaseId>
|
|
76
|
+
: never,
|
|
77
|
+
): Relation<WithIncludedRelation<Collection, Contract, Model, RelationName, QueryShapeValue<Shape>>, Contract, Model, DatabaseId>;
|
|
78
|
+
}
|
|
79
|
+
|
|
46
80
|
export type IncludeMethod<Collection, Contract, Model extends string, DatabaseId> = Contract extends AnyPostgresContract
|
|
47
81
|
? Collection extends PrismaCollection<Contract, Model, infer _Row, infer _State>
|
|
48
|
-
? {
|
|
49
|
-
include<RelationName extends RelationNames<Contract, Model>>(
|
|
50
|
-
relationName: RelationName,
|
|
51
|
-
): Relation<
|
|
52
|
-
WithIncludedRelation<
|
|
53
|
-
Collection,
|
|
54
|
-
Contract,
|
|
55
|
-
Model,
|
|
56
|
-
RelationName,
|
|
57
|
-
IncludedRelationValue<Contract, Model, RelationName, DefaultModelRow<Contract, RelatedModelNameOf<Contract, Model, RelationName>>>
|
|
58
|
-
>,
|
|
59
|
-
Contract,
|
|
60
|
-
Model,
|
|
61
|
-
DatabaseId
|
|
62
|
-
>;
|
|
63
|
-
include<RelationName extends RelationNames<Contract, Model>, Query>(
|
|
64
|
-
relationName: RelationName,
|
|
65
|
-
query: Query & AcceptRelatedQuery<Query, Contract, RelatedModelNameOf<Contract, Model, RelationName>, DatabaseId>,
|
|
66
|
-
): Relation<
|
|
67
|
-
WithIncludedRelation<Collection, Contract, Model, RelationName, IncludedQueryValue<Query, Contract, Model, RelationName>>,
|
|
68
|
-
Contract,
|
|
69
|
-
Model,
|
|
70
|
-
DatabaseId
|
|
71
|
-
>;
|
|
72
|
-
include<RelationName extends RelationNames<Contract, Model>, Shape extends Readonly<Record<string, unknown>>>(
|
|
73
|
-
relationName: RelationName,
|
|
74
|
-
shape: IsToManyRelation<Contract, Model, RelationName> extends true
|
|
75
|
-
? keyof Shape extends never
|
|
76
|
-
? never
|
|
77
|
-
: Shape & AcceptRelatedQueryShape<Shape, Contract, RelatedModelNameOf<Contract, Model, RelationName>, DatabaseId>
|
|
78
|
-
: never,
|
|
79
|
-
): Relation<WithIncludedRelation<Collection, Contract, Model, RelationName, QueryShapeValue<Shape>>, Contract, Model, DatabaseId>;
|
|
80
|
-
}
|
|
82
|
+
? { include: IncludeOverloads<Collection, Contract, Model, DatabaseId> }
|
|
81
83
|
: Record<never, never>
|
|
82
84
|
: Record<never, never>;
|
|
@@ -9,11 +9,11 @@ import type {
|
|
|
9
9
|
Collection as PrismaCollection,
|
|
10
10
|
} from "@prisma-next/sql-orm-client";
|
|
11
11
|
import type { Effect, Stream } from "effect";
|
|
12
|
-
import type { PrismaError } from "
|
|
13
|
-
import type { CollectionResult, Relation, RelationQuery } from "
|
|
12
|
+
import type { PrismaError } from "#error.ts";
|
|
13
|
+
import type { CollectionResult, Relation, RelationQuery } from "#relation.ts";
|
|
14
14
|
import type { IncludeMethod } from "./include.ts";
|
|
15
15
|
|
|
16
|
-
type AnyFunction = (...arguments_:
|
|
16
|
+
type AnyFunction = (...arguments_: readonly never[]) => unknown;
|
|
17
17
|
type AnyPostgresContract = PrismaContract<SqlStorage>;
|
|
18
18
|
type FieldTuple<Row> = readonly [keyof Row & string, ...(keyof Row & string)[]];
|
|
19
19
|
type Simplify<Value> = { [Key in keyof Value]: Value[Key] };
|
|
@@ -25,9 +25,9 @@ type TerminalMethod<Method> = Method extends (...arguments_: infer Arguments) =>
|
|
|
25
25
|
type SelectMethod<Collection, Contract, Model extends string, DatabaseId> = Contract extends AnyPostgresContract
|
|
26
26
|
? Collection extends PrismaCollection<Contract, Model, infer _Row, infer State>
|
|
27
27
|
? {
|
|
28
|
-
select<Fields extends FieldTuple<DefaultModelRow<Contract, Model>>>(
|
|
28
|
+
select: <Fields extends FieldTuple<DefaultModelRow<Contract, Model>>>(
|
|
29
29
|
...fields: Fields
|
|
30
|
-
)
|
|
30
|
+
) => Relation<PrismaCollection<Contract, Model, Pick<DefaultModelRow<Contract, Model>, Fields[number]>, State>, Contract, Model, DatabaseId>;
|
|
31
31
|
}
|
|
32
32
|
: Record<never, never>
|
|
33
33
|
: Collection extends { select: infer Select extends AnyFunction }
|
|
@@ -46,16 +46,16 @@ type AggregateConfigure<Collection> = Collection extends {
|
|
|
46
46
|
|
|
47
47
|
type AggregateSuccess<Collection, Contract extends AnyPostgresContract, Model extends string, Spec extends AggregateSpec> =
|
|
48
48
|
Collection extends GroupedCollection<Contract, Model, infer Fields extends FieldTuple<DefaultModelRow<Contract, Model>>>
|
|
49
|
-
?
|
|
49
|
+
? Simplify<Pick<DefaultModelRow<Contract, Model>, Fields[number]> & AggregateResult<Spec>>[]
|
|
50
50
|
: AggregateResult<Spec>;
|
|
51
51
|
|
|
52
52
|
type AggregateMethod<Collection, Contract, Model extends string> = Contract extends AnyPostgresContract
|
|
53
53
|
? Collection extends { aggregate: AnyFunction }
|
|
54
54
|
? {
|
|
55
|
-
aggregate<Spec extends AggregateSpec>(
|
|
55
|
+
aggregate: <Spec extends AggregateSpec>(
|
|
56
56
|
make: (aggregate: AggregateBuilder<Contract, Model>) => Spec,
|
|
57
57
|
configure?: AggregateConfigure<Collection>,
|
|
58
|
-
)
|
|
58
|
+
) => Effect.Effect<AggregateSuccess<Collection, Contract, Model, Spec>, PrismaError>;
|
|
59
59
|
}
|
|
60
60
|
: Record<never, never>
|
|
61
61
|
: Record<never, never>;
|
|
@@ -63,16 +63,18 @@ type AggregateMethod<Collection, Contract, Model extends string> = Contract exte
|
|
|
63
63
|
type CollectionMethods<Collection, Contract, Model extends string, DatabaseId> = Contract extends AnyPostgresContract
|
|
64
64
|
? Collection extends PrismaCollection<Contract, Model, infer _Row, infer State>
|
|
65
65
|
? {
|
|
66
|
-
groupBy<Fields extends FieldTuple<DefaultModelRow<Contract, Model>>>(
|
|
66
|
+
groupBy: <Fields extends FieldTuple<DefaultModelRow<Contract, Model>>>(
|
|
67
67
|
...fields: Fields
|
|
68
|
-
)
|
|
69
|
-
cursor(
|
|
68
|
+
) => Relation<GroupedCollection<Contract, Model, Fields>, Contract, Model, DatabaseId>;
|
|
69
|
+
cursor: (
|
|
70
70
|
values: State extends { readonly hasOrderBy: true } ? Partial<Record<keyof DefaultModelRow<Contract, Model> & string, unknown>> : never,
|
|
71
|
-
)
|
|
72
|
-
distinct<Fields extends FieldTuple<DefaultModelRow<Contract, Model>>>(
|
|
73
|
-
|
|
71
|
+
) => Relation<Collection, Contract, Model, DatabaseId>;
|
|
72
|
+
distinct: <Fields extends FieldTuple<DefaultModelRow<Contract, Model>>>(
|
|
73
|
+
...fields: Fields
|
|
74
|
+
) => Relation<Collection, Contract, Model, DatabaseId>;
|
|
75
|
+
distinctOn: <Fields extends FieldTuple<DefaultModelRow<Contract, Model>>>(
|
|
74
76
|
...fields: State extends { readonly hasOrderBy: true } ? Fields : never
|
|
75
|
-
)
|
|
77
|
+
) => Relation<Collection, Contract, Model, DatabaseId>;
|
|
76
78
|
} & (State extends { readonly hasWhere: true }
|
|
77
79
|
? {
|
|
78
80
|
readonly [Key in "delete" | "deleteAll" | "deleteCount"]: TerminalMethod<Collection[Key]>;
|
|
@@ -85,15 +87,15 @@ type CollectionConveniences<Collection, Contract, Model extends string, Database
|
|
|
85
87
|
? Collection extends PrismaCollection<Contract, Model, infer Row, infer _State>
|
|
86
88
|
? {
|
|
87
89
|
readonly stream: Stream.Stream<Row, PrismaError>;
|
|
88
|
-
count()
|
|
89
|
-
exists()
|
|
90
|
+
count: () => RelationQuery<number, Contract, Model, DatabaseId>;
|
|
91
|
+
exists: () => Effect.Effect<boolean, PrismaError>;
|
|
90
92
|
}
|
|
91
93
|
: Record<never, never>
|
|
92
94
|
: CollectionResult<Collection> extends ReadonlyArray<infer Row>
|
|
93
95
|
? {
|
|
94
96
|
readonly stream: Stream.Stream<Row, PrismaError>;
|
|
95
|
-
count()
|
|
96
|
-
exists()
|
|
97
|
+
count: () => Effect.Effect<number, PrismaError>;
|
|
98
|
+
exists: () => Effect.Effect<boolean, PrismaError>;
|
|
97
99
|
}
|
|
98
100
|
: Record<never, never>;
|
|
99
101
|
|
package/src/relation.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import type { Effect } from "effect";
|
|
2
|
+
import type { PrismaRelationMethods } from "#relation/prisma-methods.ts";
|
|
2
3
|
import type { PrismaError } from "./error.ts";
|
|
3
|
-
import type { PrismaRelationMethods } from "./relation/prisma-methods.ts";
|
|
4
4
|
|
|
5
|
-
type AnyFunction = (...arguments_:
|
|
5
|
+
type AnyFunction = (...arguments_: readonly never[]) => unknown;
|
|
6
6
|
|
|
7
7
|
export type CollectionResult<Collection> = Collection extends {
|
|
8
|
-
all()
|
|
8
|
+
all: () => infer Result;
|
|
9
9
|
}
|
|
10
10
|
? Awaited<Result>
|
|
11
11
|
: never;
|