@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
package/src/database.ts
CHANGED
|
@@ -1,26 +1,18 @@
|
|
|
1
|
-
import postgres, {
|
|
2
|
-
type PostgresClient,
|
|
3
|
-
type PostgresOptionsBase,
|
|
4
|
-
} from "@prisma-next/postgres/runtime";
|
|
1
|
+
import postgres, { type PostgresClient, type PostgresOptionsBase } from "@prisma-next/postgres/runtime";
|
|
5
2
|
import { type Layer, Redacted } from "effect";
|
|
6
|
-
import type { PrismaError } from "./error.
|
|
7
|
-
import { acquireConnectedClient } from "./internal/client-lifecycle.
|
|
8
|
-
import {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
makeSqlDatabase,
|
|
14
|
-
namespaceModels,
|
|
15
|
-
} from "./internal/database-factory.js";
|
|
16
|
-
import type { AnySqlContract } from "./internal/executor.js";
|
|
17
|
-
import { fromPrismaPromise } from "./internal/promise.js";
|
|
3
|
+
import type { PrismaError } from "./error.ts";
|
|
4
|
+
import { acquireConnectedClient } from "./internal/client-lifecycle.ts";
|
|
5
|
+
import { namespaceModels } from "./internal/database-facade.ts";
|
|
6
|
+
import { makeSqlDatabase } from "./internal/database-factory.ts";
|
|
7
|
+
import type { DatabaseIdentifier, DatabaseIdentifierLiteral, DatabaseServiceHolder, DefaultModels } from "./internal/database-types.ts";
|
|
8
|
+
import type { AnySqlContract } from "./internal/executor.ts";
|
|
9
|
+
import { fromPrismaPromise } from "./internal/promise.ts";
|
|
18
10
|
|
|
19
11
|
export type {
|
|
20
12
|
AnyDatabase,
|
|
21
13
|
DatabaseService,
|
|
22
14
|
DatabaseServiceOf,
|
|
23
|
-
} from "./internal/database-
|
|
15
|
+
} from "./internal/database-types.ts";
|
|
24
16
|
|
|
25
17
|
export interface DatabaseLayerOptions extends PostgresOptionsBase {
|
|
26
18
|
readonly url: string | Redacted.Redacted<string>;
|
|
@@ -36,18 +28,20 @@ type DatabaseFactoryOptions<Contract extends AnySqlContract> =
|
|
|
36
28
|
readonly contract?: never;
|
|
37
29
|
};
|
|
38
30
|
|
|
39
|
-
export interface DatabaseDefinition<
|
|
40
|
-
Contract
|
|
41
|
-
Identifier extends string,
|
|
42
|
-
> extends DatabaseServiceHolder<Contract, Identifier> {
|
|
43
|
-
readonly layer: (
|
|
44
|
-
options: DatabaseLayerOptions,
|
|
45
|
-
) => Layer.Layer<DatabaseIdentifier<Contract, Identifier>, PrismaError>;
|
|
31
|
+
export interface DatabaseDefinition<Contract extends AnySqlContract, Identifier extends string> extends DatabaseServiceHolder<Contract, Identifier> {
|
|
32
|
+
readonly layer: (options: DatabaseLayerOptions) => Layer.Layer<DatabaseIdentifier<Contract, Identifier>, PrismaError>;
|
|
46
33
|
}
|
|
47
34
|
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
|
|
35
|
+
const clientOptions = ({ url, extensions, middleware, poolOptions, verifyMarker }: DatabaseLayerOptions) => ({
|
|
36
|
+
url: typeof url === "string" ? url : Redacted.value(url),
|
|
37
|
+
...(extensions === undefined ? {} : { extensions }),
|
|
38
|
+
...(middleware === undefined ? {} : { middleware }),
|
|
39
|
+
...(poolOptions === undefined ? {} : { poolOptions }),
|
|
40
|
+
...(verifyMarker === undefined ? {} : { verifyMarker }),
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
const defaultModels = <Contract extends AnySqlContract, Models extends object>(client: Pick<PostgresClient<Contract>, "contract" | "orm">): Models =>
|
|
44
|
+
namespaceModels<Contract, Models>(client.contract, client.orm);
|
|
51
45
|
|
|
52
46
|
export const makeDatabase =
|
|
53
47
|
<const Contract extends AnySqlContract>() =>
|
|
@@ -57,45 +51,33 @@ export const makeDatabase =
|
|
|
57
51
|
): DatabaseDefinition<Contract, Identifier> => {
|
|
58
52
|
type Models = DefaultModels<Contract>;
|
|
59
53
|
|
|
60
|
-
return makeSqlDatabase<Contract, Identifier, DatabaseLayerOptions>(
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
};
|
|
73
|
-
const client =
|
|
74
|
-
options.contract === undefined
|
|
75
|
-
? postgres<Contract>({
|
|
76
|
-
...clientOptions,
|
|
77
|
-
contractJson: options.contractJson,
|
|
78
|
-
})
|
|
79
|
-
: postgres<Contract>({
|
|
80
|
-
...clientOptions,
|
|
81
|
-
contract: options.contract,
|
|
82
|
-
});
|
|
54
|
+
return makeSqlDatabase<Contract, Identifier, DatabaseLayerOptions>(identifier, (layerOptions) => {
|
|
55
|
+
const forwarded = clientOptions(layerOptions);
|
|
56
|
+
const client =
|
|
57
|
+
options.contract === undefined
|
|
58
|
+
? postgres<Contract>({
|
|
59
|
+
...forwarded,
|
|
60
|
+
contractJson: options.contractJson,
|
|
61
|
+
})
|
|
62
|
+
: postgres<Contract>({
|
|
63
|
+
...forwarded,
|
|
64
|
+
contract: options.contract,
|
|
65
|
+
});
|
|
83
66
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
) as DatabaseDefinition<Contract, Identifier>;
|
|
67
|
+
return fromPrismaPromise(() =>
|
|
68
|
+
acquireConnectedClient(client, () => ({
|
|
69
|
+
client,
|
|
70
|
+
identity: {},
|
|
71
|
+
liveness: {
|
|
72
|
+
closedCode: "RUNTIME.DATABASE_CLOSED",
|
|
73
|
+
open: true,
|
|
74
|
+
},
|
|
75
|
+
mode: "root",
|
|
76
|
+
models: defaultModels<Contract, Models>(client),
|
|
77
|
+
querySemaphore: undefined,
|
|
78
|
+
transactionIdentity: undefined,
|
|
79
|
+
transactionSemaphore: undefined,
|
|
80
|
+
})),
|
|
81
|
+
);
|
|
82
|
+
});
|
|
101
83
|
};
|
package/src/error.ts
CHANGED
|
@@ -1,31 +1,24 @@
|
|
|
1
1
|
import { Data, Redacted } from "effect";
|
|
2
2
|
|
|
3
|
-
export class PrismaRuntimeFailure extends Data.TaggedClass(
|
|
4
|
-
"PrismaRuntimeFailure",
|
|
5
|
-
)<{
|
|
3
|
+
export class PrismaRuntimeFailure extends Data.TaggedClass("PrismaRuntimeFailure")<{
|
|
6
4
|
readonly code: string;
|
|
7
5
|
readonly original: Redacted.Redacted<unknown>;
|
|
8
6
|
}> {}
|
|
9
7
|
|
|
10
8
|
export class PrismaQueryFailure extends Data.TaggedClass("PrismaQueryFailure")<{
|
|
11
|
-
readonly sqlState?: string;
|
|
12
|
-
readonly constraint?: string;
|
|
13
|
-
readonly table?: string;
|
|
14
|
-
readonly column?: string;
|
|
9
|
+
readonly sqlState?: string | undefined;
|
|
10
|
+
readonly constraint?: string | undefined;
|
|
11
|
+
readonly table?: string | undefined;
|
|
12
|
+
readonly column?: string | undefined;
|
|
15
13
|
readonly original: Redacted.Redacted<unknown>;
|
|
16
14
|
}> {}
|
|
17
15
|
|
|
18
|
-
export class PrismaConnectionFailure extends Data.TaggedClass(
|
|
19
|
-
|
|
20
|
-
)<{
|
|
21
|
-
readonly transient?: boolean;
|
|
16
|
+
export class PrismaConnectionFailure extends Data.TaggedClass("PrismaConnectionFailure")<{
|
|
17
|
+
readonly transient?: boolean | undefined;
|
|
22
18
|
readonly original: Redacted.Redacted<unknown>;
|
|
23
19
|
}> {}
|
|
24
20
|
|
|
25
|
-
export type PrismaErrorReason =
|
|
26
|
-
| PrismaRuntimeFailure
|
|
27
|
-
| PrismaQueryFailure
|
|
28
|
-
| PrismaConnectionFailure;
|
|
21
|
+
export type PrismaErrorReason = PrismaRuntimeFailure | PrismaQueryFailure | PrismaConnectionFailure;
|
|
29
22
|
|
|
30
23
|
export class PrismaError extends Data.TaggedError("PrismaError")<{
|
|
31
24
|
readonly reason: PrismaErrorReason;
|
|
@@ -52,10 +45,7 @@ export const isPrismaFailure = (error: unknown): error is PrismaFailure => {
|
|
|
52
45
|
if ("code" in error && typeof error.code === "string") {
|
|
53
46
|
return true;
|
|
54
47
|
}
|
|
55
|
-
return (
|
|
56
|
-
"kind" in error &&
|
|
57
|
-
(error.kind === "sql_query" || error.kind === "sql_connection")
|
|
58
|
-
);
|
|
48
|
+
return "kind" in error && (error.kind === "sql_query" || error.kind === "sql_connection");
|
|
59
49
|
};
|
|
60
50
|
|
|
61
51
|
export const toPrismaError = (error: PrismaFailure): PrismaError => {
|
package/src/index.ts
CHANGED
|
@@ -6,12 +6,12 @@ export {
|
|
|
6
6
|
type DatabaseService,
|
|
7
7
|
type DatabaseServiceOf,
|
|
8
8
|
makeDatabase,
|
|
9
|
-
} from "./database.
|
|
9
|
+
} from "./database.ts";
|
|
10
10
|
export {
|
|
11
11
|
PrismaConnectionFailure,
|
|
12
12
|
PrismaError,
|
|
13
13
|
type PrismaErrorReason,
|
|
14
14
|
PrismaQueryFailure,
|
|
15
15
|
PrismaRuntimeFailure,
|
|
16
|
-
} from "./error.
|
|
17
|
-
export type { Relation } from "./relation.
|
|
16
|
+
} from "./error.ts";
|
|
17
|
+
export type { Relation } from "./relation.ts";
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { RuntimeConnection, RuntimeTransaction } from "@prisma-next/sql-runtime";
|
|
2
|
+
|
|
3
|
+
export type SettledConnection = Pick<RuntimeConnection, "destroy" | "release">;
|
|
4
|
+
export type SettledTransaction = Pick<RuntimeTransaction, "commit" | "rollback">;
|
|
5
|
+
|
|
6
|
+
interface Settled {
|
|
7
|
+
readonly connection: SettledConnection;
|
|
8
|
+
readonly transaction: SettledTransaction;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const runtimeFailure = (code: string, cause: unknown, details?: Readonly<Record<string, unknown>>): Error & { readonly code: string } =>
|
|
12
|
+
Object.assign(new Error(code, { cause }), { code, ...details });
|
|
13
|
+
|
|
14
|
+
class ConnectionDisposal {
|
|
15
|
+
disposed = false;
|
|
16
|
+
|
|
17
|
+
constructor(private readonly connection: SettledConnection) {}
|
|
18
|
+
|
|
19
|
+
async destroy(reason: unknown): Promise<void> {
|
|
20
|
+
if (this.disposed) {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
this.disposed = true;
|
|
24
|
+
await this.connection.destroy(reason).catch(() => undefined);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const commit = async ({ transaction }: Settled, disposal: ConnectionDisposal): Promise<unknown> => {
|
|
29
|
+
try {
|
|
30
|
+
await transaction.commit();
|
|
31
|
+
return undefined;
|
|
32
|
+
} catch (commitError) {
|
|
33
|
+
await transaction.rollback().catch(() => disposal.destroy(commitError));
|
|
34
|
+
return runtimeFailure("RUNTIME.TRANSACTION_COMMIT_FAILED", commitError);
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const rollback = async ({ transaction }: Settled, disposal: ConnectionDisposal): Promise<unknown> => {
|
|
39
|
+
try {
|
|
40
|
+
await transaction.rollback();
|
|
41
|
+
return undefined;
|
|
42
|
+
} catch (rollbackError) {
|
|
43
|
+
await disposal.destroy(rollbackError);
|
|
44
|
+
return runtimeFailure("RUNTIME.TRANSACTION_ROLLBACK_FAILED", rollbackError);
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const release = async ({ connection }: Settled, disposal: ConnectionDisposal, failure: unknown): Promise<void> => {
|
|
49
|
+
if (disposal.disposed) {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
try {
|
|
53
|
+
await connection.release();
|
|
54
|
+
} catch (releaseError) {
|
|
55
|
+
await disposal.destroy(releaseError);
|
|
56
|
+
throw failure === undefined ? releaseError : runtimeFailure("RUNTIME.TRANSACTION_RELEASE_FAILED", failure, { releaseError });
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export const settleConnection = async (settled: Settled, commitTransaction: boolean): Promise<void> => {
|
|
61
|
+
const disposal = new ConnectionDisposal(settled.connection);
|
|
62
|
+
const failure = commitTransaction ? await commit(settled, disposal) : await rollback(settled, disposal);
|
|
63
|
+
await release(settled, disposal, failure);
|
|
64
|
+
if (failure !== undefined) {
|
|
65
|
+
throw failure;
|
|
66
|
+
}
|
|
67
|
+
};
|
|
@@ -3,10 +3,7 @@ export interface ClientLifecycle {
|
|
|
3
3
|
readonly close: () => PromiseLike<void>;
|
|
4
4
|
}
|
|
5
5
|
|
|
6
|
-
export const acquireConnectedClient = async <A>(
|
|
7
|
-
client: ClientLifecycle,
|
|
8
|
-
initialize: () => A,
|
|
9
|
-
): Promise<A> => {
|
|
6
|
+
export const acquireConnectedClient = async <A>(client: ClientLifecycle, initialize: () => A): Promise<A> => {
|
|
10
7
|
try {
|
|
11
8
|
await client.connect();
|
|
12
9
|
return initialize();
|
|
@@ -18,14 +15,10 @@ export const acquireConnectedClient = async <A>(
|
|
|
18
15
|
|
|
19
16
|
const reservedModelNames = new Set(["transaction"]);
|
|
20
17
|
|
|
21
|
-
export const assertAvailableModelNames = (
|
|
22
|
-
modelNames: ReadonlyArray<string>,
|
|
23
|
-
): void => {
|
|
18
|
+
export const assertAvailableModelNames = (modelNames: ReadonlyArray<string>): void => {
|
|
24
19
|
for (const modelName of modelNames) {
|
|
25
20
|
if (reservedModelNames.has(modelName)) {
|
|
26
|
-
throw new TypeError(
|
|
27
|
-
`Prisma model name conflicts with the database facade: ${modelName}`,
|
|
28
|
-
);
|
|
21
|
+
throw new TypeError(`Prisma model name conflicts with the database facade: ${modelName}`);
|
|
29
22
|
}
|
|
30
23
|
}
|
|
31
24
|
};
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
const timestampReference = /\b(?:Timestamp|Timestamptz)<(?:\d+|undefined)>/g;
|
|
2
|
-
const timestampCodecReference =
|
|
3
|
-
/\bCodecTypes\['pg\/(?:timestamp|timestamptz)@1'\]\['(?:input|output)'\]/g;
|
|
2
|
+
const timestampCodecReference = /\bCodecTypes\['pg\/(?:timestamp|timestamptz)@1'\]\['(?:input|output)'\]/g;
|
|
4
3
|
const unsupportedTimestampReference = /\b(?:Timestamp|Timestamptz)\s*</;
|
|
5
4
|
|
|
6
5
|
/**
|
|
@@ -8,14 +7,10 @@ const unsupportedTimestampReference = /\b(?:Timestamp|Timestamptz)\s*</;
|
|
|
8
7
|
* JavaScript Dates accepted and returned by its runtime codecs.
|
|
9
8
|
*/
|
|
10
9
|
export const normalizePrismaNextContractTypes = (source: string): string => {
|
|
11
|
-
const normalized = source
|
|
12
|
-
.replaceAll(timestampReference, "Date")
|
|
13
|
-
.replaceAll(timestampCodecReference, "Date");
|
|
10
|
+
const normalized = source.replaceAll(timestampReference, "Date").replaceAll(timestampCodecReference, "Date");
|
|
14
11
|
|
|
15
12
|
if (unsupportedTimestampReference.test(normalized)) {
|
|
16
|
-
throw new Error(
|
|
17
|
-
"Unsupported Prisma Next timestamp declaration; update effect-prisma before using this generated contract",
|
|
18
|
-
);
|
|
13
|
+
throw new Error("Unsupported Prisma Next timestamp declaration; update effect-prisma before using this generated contract");
|
|
19
14
|
}
|
|
20
15
|
|
|
21
16
|
return normalized;
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { type Context, Effect } from "effect";
|
|
2
|
+
import { type PrismaError, toPrismaError } from "../error.ts";
|
|
3
|
+
import { assertAvailableModelNames } from "./client-lifecycle.ts";
|
|
4
|
+
import type { DatabaseIdentifier, DatabaseService, DefaultModels } from "./database-types.ts";
|
|
5
|
+
import type { AnySqlContract, DatabaseExecutor } from "./executor.ts";
|
|
6
|
+
import { makeModelRelation } from "./relation-runtime.ts";
|
|
7
|
+
import { acquireTransaction, releaseTransaction, type TransactionOrm, withTransactionSemaphore } from "./transaction.ts";
|
|
8
|
+
|
|
9
|
+
export interface ActiveTransaction<Contract extends AnySqlContract, Identifier extends string> {
|
|
10
|
+
readonly executor: DatabaseExecutor<DefaultModels<Contract>, Contract>;
|
|
11
|
+
readonly facade: DatabaseService<Contract, Identifier>;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface FacadeScope<Contract extends AnySqlContract, Identifier extends string> {
|
|
15
|
+
readonly activeTransaction: Context.Reference<ActiveTransaction<Contract, Identifier> | undefined>;
|
|
16
|
+
readonly executors: WeakMap<DatabaseService<Contract, Identifier>, DatabaseExecutor<DefaultModels<Contract>, Contract>>;
|
|
17
|
+
readonly service: Context.Service<DatabaseIdentifier<Contract, Identifier>, DatabaseService<Contract, Identifier>>;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Prisma Next builds each namespace's model clients from the contract, so they are the contract's DefaultModels.
|
|
21
|
+
export function contractModels<Models extends object>(namespace: unknown): Models;
|
|
22
|
+
export function contractModels(namespace: unknown): unknown {
|
|
23
|
+
return namespace;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// The facade serves `transaction` itself and a Relation for every other model name, matching DatabaseService.
|
|
27
|
+
function databaseFacade<Contract extends AnySqlContract, Identifier extends string>(facade: object): DatabaseService<Contract, Identifier>;
|
|
28
|
+
function databaseFacade(facade: object): unknown {
|
|
29
|
+
return facade;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Resolve the models of the single domain namespace from a namespaced ORM
|
|
34
|
+
* client. Prisma Next namespaces PostgreSQL models under their schema and
|
|
35
|
+
* SQLite models under the unbound namespace.
|
|
36
|
+
*/
|
|
37
|
+
export const namespaceModels = <Contract extends AnySqlContract, Models extends object>(
|
|
38
|
+
contract: Contract,
|
|
39
|
+
namespacedOrm: TransactionOrm<Contract>,
|
|
40
|
+
): Models => {
|
|
41
|
+
const namespaces = Object.entries(contract.domain.namespaces);
|
|
42
|
+
const [only] = namespaces;
|
|
43
|
+
if (namespaces.length !== 1 || only === undefined) {
|
|
44
|
+
throw new TypeError("Effect Prisma currently requires exactly one domain namespace");
|
|
45
|
+
}
|
|
46
|
+
const [name, namespace] = only;
|
|
47
|
+
assertAvailableModelNames(Object.keys(namespace.models));
|
|
48
|
+
return contractModels<Models>(Reflect.get(namespacedOrm, name));
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export const runTransaction = <Contract extends AnySqlContract, Identifier extends string, A, E, R>(
|
|
52
|
+
scope: FacadeScope<Contract, Identifier>,
|
|
53
|
+
current: DatabaseExecutor<DefaultModels<Contract>, Contract>,
|
|
54
|
+
currentFacade: DatabaseService<Contract, Identifier>,
|
|
55
|
+
program: Effect.Effect<A, E, R>,
|
|
56
|
+
release: typeof releaseTransaction,
|
|
57
|
+
mode: "test" | "transaction",
|
|
58
|
+
span: string,
|
|
59
|
+
): Effect.Effect<A, E | PrismaError, Exclude<R, DatabaseIdentifier<Contract, Identifier>>> => {
|
|
60
|
+
if (!current.liveness.open) {
|
|
61
|
+
return Effect.fail(toPrismaError({ code: current.liveness.closedCode }));
|
|
62
|
+
}
|
|
63
|
+
if (current.mode === "test" || (current.mode === "transaction" && mode === "transaction")) {
|
|
64
|
+
return Effect.provideService(program, scope.service, currentFacade);
|
|
65
|
+
}
|
|
66
|
+
if (current.mode === "transaction") {
|
|
67
|
+
return Effect.fail(
|
|
68
|
+
toPrismaError({
|
|
69
|
+
code: "RUNTIME.TEST_TRANSACTION_INSIDE_TRANSACTION_UNSUPPORTED",
|
|
70
|
+
}),
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const transaction = Effect.acquireUseRelease(
|
|
75
|
+
acquireTransaction(
|
|
76
|
+
current,
|
|
77
|
+
(transactionOrm) => namespaceModels<Contract, DefaultModels<Contract>>(current.client.contract, transactionOrm),
|
|
78
|
+
mode,
|
|
79
|
+
),
|
|
80
|
+
(resource) => {
|
|
81
|
+
const transactionFacade = makeFacade(scope, resource.executor);
|
|
82
|
+
return program.pipe(
|
|
83
|
+
Effect.provideService(scope.service, transactionFacade),
|
|
84
|
+
Effect.provideService(scope.activeTransaction, {
|
|
85
|
+
executor: resource.executor,
|
|
86
|
+
facade: transactionFacade,
|
|
87
|
+
}),
|
|
88
|
+
);
|
|
89
|
+
},
|
|
90
|
+
release,
|
|
91
|
+
).pipe(Effect.withSpan(span, { kind: "client" }));
|
|
92
|
+
|
|
93
|
+
return withTransactionSemaphore(current.transactionSemaphore, transaction);
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
export function makeFacade<Contract extends AnySqlContract, Identifier extends string>(
|
|
97
|
+
scope: FacadeScope<Contract, Identifier>,
|
|
98
|
+
current: DatabaseExecutor<DefaultModels<Contract>, Contract>,
|
|
99
|
+
): DatabaseService<Contract, Identifier> {
|
|
100
|
+
let facade: DatabaseService<Contract, Identifier>;
|
|
101
|
+
const resolveActiveTransaction = Effect.flatMap(scope.activeTransaction, (active) => {
|
|
102
|
+
if (current.mode === "root") {
|
|
103
|
+
return Effect.succeed(active ?? { executor: current, facade });
|
|
104
|
+
}
|
|
105
|
+
return active?.executor === current
|
|
106
|
+
? Effect.succeed(active)
|
|
107
|
+
: Effect.fail(
|
|
108
|
+
toPrismaError({
|
|
109
|
+
code: "RUNTIME.TRANSACTION_CONTEXT_MISMATCH",
|
|
110
|
+
}),
|
|
111
|
+
);
|
|
112
|
+
});
|
|
113
|
+
const resolveExecutor = Effect.suspend(() =>
|
|
114
|
+
current.liveness.open ? Effect.map(resolveActiveTransaction, ({ executor }) => executor) : Effect.succeed(current),
|
|
115
|
+
);
|
|
116
|
+
const target: object = Object.create(null);
|
|
117
|
+
Object.assign(target, {
|
|
118
|
+
transaction: <A, E, R>(program: Effect.Effect<A, E, R> & (DatabaseIdentifier<Contract, Identifier> extends R ? unknown : never)) =>
|
|
119
|
+
Effect.suspend(() => {
|
|
120
|
+
if (!current.liveness.open) {
|
|
121
|
+
return Effect.fail(toPrismaError({ code: current.liveness.closedCode }));
|
|
122
|
+
}
|
|
123
|
+
return Effect.flatMap(resolveActiveTransaction, (selected) =>
|
|
124
|
+
runTransaction(scope, selected.executor, selected.facade, program, releaseTransaction, "transaction", "prisma.transaction"),
|
|
125
|
+
);
|
|
126
|
+
}),
|
|
127
|
+
});
|
|
128
|
+
facade = databaseFacade<Contract, Identifier>(
|
|
129
|
+
new Proxy(target, {
|
|
130
|
+
get(target, property, receiver) {
|
|
131
|
+
if (Reflect.has(target, property)) {
|
|
132
|
+
return Reflect.get(target, property, receiver);
|
|
133
|
+
}
|
|
134
|
+
if (typeof property !== "string") {
|
|
135
|
+
return undefined;
|
|
136
|
+
}
|
|
137
|
+
return makeModelRelation(current, property, resolveExecutor);
|
|
138
|
+
},
|
|
139
|
+
}),
|
|
140
|
+
);
|
|
141
|
+
scope.executors.set(facade, current);
|
|
142
|
+
return facade;
|
|
143
|
+
}
|