@shivaedev/effect-prisma 0.7.1 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +22 -0
- package/README.md +198 -155
- package/dist/database.d.ts +7 -7
- package/dist/database.d.ts.map +1 -1
- package/dist/database.js +12 -8
- package/dist/database.js.map +1 -1
- package/dist/databaseTypes.d.ts +33 -0
- package/dist/databaseTypes.d.ts.map +1 -0
- package/dist/{internal/database-types.js → databaseTypes.js} +1 -1
- package/dist/databaseTypes.js.map +1 -0
- package/dist/error.d.ts.map +1 -1
- package/dist/internal/adapters/transaction-settlement.d.ts.map +1 -1
- package/dist/internal/adapters/transaction-settlement.js +9 -7
- package/dist/internal/adapters/transaction-settlement.js.map +1 -1
- package/dist/internal/client-lifecycle.d.ts.map +1 -1
- package/dist/internal/contract-normalization.d.ts.map +1 -1
- package/dist/internal/database-facade.d.ts +12 -12
- package/dist/internal/database-facade.d.ts.map +1 -1
- package/dist/internal/database-facade.js.map +1 -1
- package/dist/internal/database-factory.d.ts +2 -2
- package/dist/internal/database-factory.d.ts.map +1 -1
- package/dist/internal/database-factory.js +3 -3
- package/dist/internal/database-factory.js.map +1 -1
- package/dist/internal/dynamic.d.ts.map +1 -1
- package/dist/internal/dynamic.js +3 -1
- package/dist/internal/dynamic.js.map +1 -1
- package/dist/internal/executor.d.ts +6 -6
- package/dist/internal/executor.d.ts.map +1 -1
- package/dist/internal/promise.d.ts.map +1 -1
- package/dist/internal/query-execution.d.ts.map +1 -1
- package/dist/internal/recipe.d.ts.map +1 -1
- package/dist/internal/recipe.js +10 -10
- package/dist/internal/recipe.js.map +1 -1
- package/dist/internal/relation-plan.d.ts.map +1 -1
- package/dist/internal/relation-result.d.ts.map +1 -1
- package/dist/internal/relation-result.js +6 -2
- package/dist/internal/relation-result.js.map +1 -1
- package/dist/internal/relation-runtime.d.ts +1 -1
- package/dist/internal/relation-runtime.d.ts.map +1 -1
- package/dist/internal/relation-runtime.js +11 -6
- 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 +9 -7
- package/dist/internal/relation-stream.js.map +1 -1
- package/dist/internal/sqlite-datetime.d.ts.map +1 -1
- package/dist/internal/sqlite-datetime.js +11 -9
- package/dist/internal/sqlite-datetime.js.map +1 -1
- package/dist/internal/sqlite-pragmas.d.ts.map +1 -1
- package/dist/internal/testing.d.ts +5 -5
- package/dist/internal/testing.d.ts.map +1 -1
- package/dist/internal/testing.js.map +1 -1
- package/dist/internal/transaction.d.ts +6 -6
- package/dist/internal/transaction.d.ts.map +1 -1
- package/dist/internal/transaction.js +5 -3
- package/dist/internal/transaction.js.map +1 -1
- package/dist/relation/include.d.ts +20 -20
- package/dist/relation/include.d.ts.map +1 -1
- package/dist/relation.d.ts +23 -23
- package/dist/relation.d.ts.map +1 -1
- package/dist/sqlite.d.ts +1 -1
- package/dist/sqlite.d.ts.map +1 -1
- package/dist/sqlite.js +8 -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.map +1 -1
- package/dist/testing/types.d.ts +2 -3
- package/dist/testing/types.d.ts.map +1 -1
- package/dist/testing/vitest.d.ts +2 -1
- package/dist/testing/vitest.d.ts.map +1 -1
- package/dist/testing/vitest.js +1 -1
- package/dist/testing/vitest.js.map +1 -1
- package/docs/north-star.md +47 -0
- package/docs/roadmap.md +32 -0
- package/package.json +33 -40
- package/src/database.ts +30 -30
- package/src/databaseTypes.ts +65 -0
- package/src/internal/adapters/transaction-settlement.ts +9 -8
- package/src/internal/database-facade.ts +28 -28
- package/src/internal/database-factory.ts +15 -15
- package/src/internal/dynamic.ts +3 -1
- package/src/internal/executor.ts +6 -6
- package/src/internal/recipe.ts +10 -10
- package/src/internal/relation-result.ts +6 -3
- package/src/internal/relation-runtime.ts +25 -20
- package/src/internal/relation-stream.ts +5 -4
- package/src/internal/sqlite-datetime.ts +11 -9
- package/src/internal/testing.ts +8 -8
- package/src/internal/transaction.ts +16 -15
- package/src/relation/include.ts +48 -48
- package/src/relation.ts +38 -38
- package/src/sqlite.ts +9 -7
- package/src/testing/transaction.ts +1 -1
- package/src/testing/types.ts +2 -3
- package/src/testing/vitest.ts +4 -2
- package/dist/index.d.ts +0 -5
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -4
- package/dist/index.js.map +0 -1
- package/dist/internal/database-types.d.ts +0 -33
- package/dist/internal/database-types.d.ts.map +0 -1
- package/dist/internal/database-types.js.map +0 -1
- package/dist/testing.d.ts +0 -4
- package/dist/testing.d.ts.map +0 -1
- package/dist/testing.js +0 -3
- package/dist/testing.js.map +0 -1
- package/src/index.ts +0 -17
- package/src/internal/database-types.ts +0 -65
- package/src/testing.ts +0 -10
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shivaedev/effect-prisma",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "Effect-native PostgreSQL queries and transactions for Prisma Next",
|
|
5
5
|
"homepage": "https://github.com/ShivaeDev/platform/tree/main/packages/effect-prisma#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"sideEffects": false,
|
|
16
16
|
"type": "module",
|
|
17
17
|
"imports": {
|
|
18
|
-
"#test/*": "./test/*",
|
|
18
|
+
"#test/*": "./src/test-support/*",
|
|
19
19
|
"#*.ts": {
|
|
20
20
|
"source": "./src/*.ts",
|
|
21
21
|
"types": "./dist/*.d.ts",
|
|
@@ -23,24 +23,13 @@
|
|
|
23
23
|
}
|
|
24
24
|
},
|
|
25
25
|
"exports": {
|
|
26
|
-
"
|
|
27
|
-
"types": "./dist
|
|
28
|
-
"source": "./src
|
|
29
|
-
"import": "./dist
|
|
30
|
-
"default": "./dist
|
|
31
|
-
},
|
|
32
|
-
"./sqlite": {
|
|
33
|
-
"types": "./dist/sqlite.d.ts",
|
|
34
|
-
"source": "./src/sqlite.ts",
|
|
35
|
-
"import": "./dist/sqlite.js",
|
|
36
|
-
"default": "./dist/sqlite.js"
|
|
37
|
-
},
|
|
38
|
-
"./testing": {
|
|
39
|
-
"types": "./dist/testing.d.ts",
|
|
40
|
-
"source": "./src/testing.ts",
|
|
41
|
-
"import": "./dist/testing.js",
|
|
42
|
-
"default": "./dist/testing.js"
|
|
26
|
+
"./*.ts": {
|
|
27
|
+
"types": "./dist/*.d.ts",
|
|
28
|
+
"source": "./src/*.ts",
|
|
29
|
+
"import": "./dist/*.js",
|
|
30
|
+
"default": "./dist/*.js"
|
|
43
31
|
},
|
|
32
|
+
"./internal/*": null,
|
|
44
33
|
"./package.json": "./package.json"
|
|
45
34
|
},
|
|
46
35
|
"bin": {
|
|
@@ -49,37 +38,41 @@
|
|
|
49
38
|
"files": [
|
|
50
39
|
"dist",
|
|
51
40
|
"src",
|
|
41
|
+
"!src/**/*.test.ts",
|
|
42
|
+
"!src/**/*.spec.ts",
|
|
43
|
+
"!src/**/test-support",
|
|
44
|
+
"docs",
|
|
52
45
|
"CHANGELOG.md",
|
|
53
46
|
"README.md"
|
|
54
47
|
],
|
|
55
48
|
"dependencies": {
|
|
56
|
-
"@prisma-next/contract": "0.
|
|
57
|
-
"@prisma-next/postgres": "0.
|
|
58
|
-
"@prisma-next/sql-contract": "0.
|
|
59
|
-
"@prisma-next/sql-orm-client": "0.
|
|
60
|
-
"@prisma-next/sql-relational-core": "0.
|
|
61
|
-
"@prisma-next/sql-runtime": "0.
|
|
62
|
-
"@prisma-next/sqlite": "0.
|
|
63
|
-
"@shivaedev/effect-test": "0.
|
|
49
|
+
"@prisma-next/contract": "0.16.0",
|
|
50
|
+
"@prisma-next/postgres": "0.16.0",
|
|
51
|
+
"@prisma-next/sql-contract": "0.16.0",
|
|
52
|
+
"@prisma-next/sql-orm-client": "0.16.0",
|
|
53
|
+
"@prisma-next/sql-relational-core": "0.16.0",
|
|
54
|
+
"@prisma-next/sql-runtime": "0.16.0",
|
|
55
|
+
"@prisma-next/sqlite": "0.16.0",
|
|
56
|
+
"@shivaedev/effect-test": "0.4.0"
|
|
64
57
|
},
|
|
65
58
|
"devDependencies": {
|
|
66
|
-
"@effect/vitest": "4.0.
|
|
67
|
-
"@prisma-next/adapter-postgres": "0.
|
|
68
|
-
"@prisma-next/adapter-sqlite": "0.
|
|
69
|
-
"@prisma-next/cli": "0.
|
|
70
|
-
"@prisma-next/target-postgres": "0.
|
|
71
|
-
"@prisma-next/target-sqlite": "0.
|
|
72
|
-
"@shivaedev/quality": "0.
|
|
73
|
-
"@types/node": "24.
|
|
59
|
+
"@effect/vitest": "4.0.4",
|
|
60
|
+
"@prisma-next/adapter-postgres": "0.16.0",
|
|
61
|
+
"@prisma-next/adapter-sqlite": "0.16.0",
|
|
62
|
+
"@prisma-next/cli": "0.16.0",
|
|
63
|
+
"@prisma-next/target-postgres": "0.16.0",
|
|
64
|
+
"@prisma-next/target-sqlite": "0.16.0",
|
|
65
|
+
"@shivaedev/quality": "0.20.0",
|
|
66
|
+
"@types/node": "24.19.1",
|
|
74
67
|
"@types/pg": "8.20.0",
|
|
75
68
|
"@typescript/native": "npm:typescript@7.0.2",
|
|
76
|
-
"effect": "4.0.
|
|
69
|
+
"effect": "4.0.4",
|
|
77
70
|
"typescript": "npm:@typescript/typescript6@6.0.2",
|
|
78
|
-
"vitest": "
|
|
71
|
+
"vitest": "5.0.3"
|
|
79
72
|
},
|
|
80
73
|
"peerDependencies": {
|
|
81
|
-
"@effect/vitest": "4.0.
|
|
82
|
-
"effect": "4.0.
|
|
74
|
+
"@effect/vitest": "4.0.4",
|
|
75
|
+
"effect": "4.0.4"
|
|
83
76
|
},
|
|
84
77
|
"peerDependenciesMeta": {
|
|
85
78
|
"@effect/vitest": {
|
|
@@ -94,7 +87,7 @@
|
|
|
94
87
|
"provenance": true
|
|
95
88
|
},
|
|
96
89
|
"scripts": {
|
|
97
|
-
"build": "node --eval \"import('node:fs').then(({ rmSync }) => rmSync('dist', { force: true, recursive: true }))\" &&
|
|
90
|
+
"build": "node --eval \"import('node:fs').then(({ rmSync }) => rmSync('dist', { force: true, recursive: true }))\" && tsc --project tsconfig.build.json",
|
|
98
91
|
"ready": "pnpm typecheck && pnpm test && pnpm build",
|
|
99
92
|
"test": "vitest run",
|
|
100
93
|
"typecheck": "tsc --noEmit"
|
package/src/database.ts
CHANGED
|
@@ -1,26 +1,20 @@
|
|
|
1
1
|
import postgres, { type PostgresClient, type PostgresOptionsBase } from "@prisma-next/postgres/runtime";
|
|
2
2
|
import { type Layer, Redacted } from "effect";
|
|
3
|
+
import type { DatabaseIdentifier, DatabaseIdentifierLiteral, DatabaseServiceHolder, DefaultModels } from "#databaseTypes.ts";
|
|
3
4
|
import { acquireConnectedClient } from "#internal/client-lifecycle.ts";
|
|
4
5
|
import { namespaceModels } from "#internal/database-facade.ts";
|
|
5
6
|
import { makeSqlDatabase } from "#internal/database-factory.ts";
|
|
6
|
-
import type { DatabaseIdentifier, DatabaseIdentifierLiteral, DatabaseServiceHolder, DefaultModels } from "#internal/database-types.ts";
|
|
7
7
|
import type { AnySqlContract } from "#internal/executor.ts";
|
|
8
8
|
import { fromPrismaPromise } from "#internal/promise.ts";
|
|
9
9
|
import type { PrismaError } from "./error.ts";
|
|
10
10
|
|
|
11
|
-
export type {
|
|
12
|
-
AnyDatabase,
|
|
13
|
-
DatabaseService,
|
|
14
|
-
DatabaseServiceOf,
|
|
15
|
-
} from "#internal/database-types.ts";
|
|
16
|
-
|
|
17
11
|
export interface DatabaseLayerOptions extends PostgresOptionsBase {
|
|
18
12
|
readonly url: string | Redacted.Redacted<string>;
|
|
19
13
|
}
|
|
20
14
|
|
|
21
|
-
type DatabaseFactoryOptions<
|
|
15
|
+
type DatabaseFactoryOptions<TContract extends AnySqlContract> =
|
|
22
16
|
| {
|
|
23
|
-
readonly contract:
|
|
17
|
+
readonly contract: TContract;
|
|
24
18
|
readonly contractJson?: never;
|
|
25
19
|
}
|
|
26
20
|
| {
|
|
@@ -28,38 +22,44 @@ type DatabaseFactoryOptions<Contract extends AnySqlContract> =
|
|
|
28
22
|
readonly contract?: never;
|
|
29
23
|
};
|
|
30
24
|
|
|
31
|
-
export interface DatabaseDefinition<
|
|
32
|
-
|
|
25
|
+
export interface DatabaseDefinition<TContract extends AnySqlContract, TIdentifier extends string>
|
|
26
|
+
extends DatabaseServiceHolder<TContract, TIdentifier> {
|
|
27
|
+
readonly layer: (options: DatabaseLayerOptions) => Layer.Layer<DatabaseIdentifier<TContract, TIdentifier>, PrismaError>;
|
|
33
28
|
}
|
|
34
29
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
})
|
|
30
|
+
function clientOptions({ url, extensions, middleware, poolOptions, verifyMarker }: DatabaseLayerOptions) {
|
|
31
|
+
return {
|
|
32
|
+
url: typeof url === "string" ? url : Redacted.value(url),
|
|
33
|
+
...(extensions === undefined ? {} : { extensions }),
|
|
34
|
+
...(middleware === undefined ? {} : { middleware }),
|
|
35
|
+
...(poolOptions === undefined ? {} : { poolOptions }),
|
|
36
|
+
...(verifyMarker === undefined ? {} : { verifyMarker }),
|
|
37
|
+
};
|
|
38
|
+
}
|
|
42
39
|
|
|
43
|
-
|
|
44
|
-
|
|
40
|
+
function defaultModels<TContract extends AnySqlContract, TModels extends object>(
|
|
41
|
+
client: Pick<PostgresClient<TContract>, "contract" | "orm">,
|
|
42
|
+
): TModels {
|
|
43
|
+
return namespaceModels<TContract, TModels>(client.contract, client.orm);
|
|
44
|
+
}
|
|
45
45
|
|
|
46
46
|
export const makeDatabase =
|
|
47
|
-
<const
|
|
48
|
-
<const
|
|
49
|
-
identifier: DatabaseIdentifierLiteral<
|
|
50
|
-
options: DatabaseFactoryOptions<
|
|
51
|
-
): DatabaseDefinition<
|
|
52
|
-
type Models = DefaultModels<
|
|
47
|
+
<const TContract extends AnySqlContract>() =>
|
|
48
|
+
<const TIdentifier extends string>(
|
|
49
|
+
identifier: DatabaseIdentifierLiteral<TIdentifier>,
|
|
50
|
+
options: DatabaseFactoryOptions<TContract>,
|
|
51
|
+
): DatabaseDefinition<TContract, TIdentifier> => {
|
|
52
|
+
type Models = DefaultModels<TContract>;
|
|
53
53
|
|
|
54
|
-
return makeSqlDatabase<
|
|
54
|
+
return makeSqlDatabase<TContract, TIdentifier, DatabaseLayerOptions>(identifier, (layerOptions) => {
|
|
55
55
|
const forwarded = clientOptions(layerOptions);
|
|
56
56
|
const client =
|
|
57
57
|
options.contract === undefined
|
|
58
|
-
? postgres<
|
|
58
|
+
? postgres<TContract>({
|
|
59
59
|
...forwarded,
|
|
60
60
|
contractJson: options.contractJson,
|
|
61
61
|
})
|
|
62
|
-
: postgres<
|
|
62
|
+
: postgres<TContract>({
|
|
63
63
|
...forwarded,
|
|
64
64
|
contract: options.contract,
|
|
65
65
|
});
|
|
@@ -73,7 +73,7 @@ export const makeDatabase =
|
|
|
73
73
|
open: true,
|
|
74
74
|
},
|
|
75
75
|
mode: "root",
|
|
76
|
-
models: defaultModels<
|
|
76
|
+
models: defaultModels<TContract, Models>(client),
|
|
77
77
|
querySemaphore: undefined,
|
|
78
78
|
transactionIdentity: undefined,
|
|
79
79
|
transactionSemaphore: undefined,
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type { orm } from "@prisma-next/sql-orm-client";
|
|
2
|
+
import type { Context, Effect, Layer } from "effect";
|
|
3
|
+
import type { PrismaError } from "#error.ts";
|
|
4
|
+
import type { AnySqlContract } from "#internal/executor.ts";
|
|
5
|
+
import type { Relation } from "#relation.ts";
|
|
6
|
+
|
|
7
|
+
type OrmFor<TContract extends AnySqlContract> = ReturnType<typeof orm<TContract>>;
|
|
8
|
+
type DefaultNamespaceId<TContract extends AnySqlContract> = "__unbound__" extends keyof OrmFor<TContract>
|
|
9
|
+
? "__unbound__"
|
|
10
|
+
: "public" extends keyof OrmFor<TContract>
|
|
11
|
+
? "public"
|
|
12
|
+
: keyof OrmFor<TContract>;
|
|
13
|
+
export type DefaultModels<TContract extends AnySqlContract> = OrmFor<TContract>[DefaultNamespaceId<TContract>] extends infer TModels extends object
|
|
14
|
+
? TModels
|
|
15
|
+
: never;
|
|
16
|
+
|
|
17
|
+
type IsUnion<TValue, TWhole = TValue> = TValue extends unknown ? ([TWhole] extends [TValue] ? false : true) : never;
|
|
18
|
+
|
|
19
|
+
type IsSingletonString<TValue extends string> = Record<never, never> extends Record<TValue, never> ? false : true;
|
|
20
|
+
|
|
21
|
+
export const internalContextIdentifierPrefix = "\0@shivaedev/effect-prisma/internal/";
|
|
22
|
+
|
|
23
|
+
export type DatabaseIdentifierLiteral<TIdentifier extends string> = [TIdentifier] extends [`\0@shivaedev/effect-prisma/internal/${string}`]
|
|
24
|
+
? never
|
|
25
|
+
: string extends TIdentifier
|
|
26
|
+
? never
|
|
27
|
+
: true extends IsUnion<TIdentifier>
|
|
28
|
+
? never
|
|
29
|
+
: IsSingletonString<TIdentifier> extends true
|
|
30
|
+
? TIdentifier
|
|
31
|
+
: never;
|
|
32
|
+
|
|
33
|
+
export interface DatabaseIdentifier<TContract extends AnySqlContract, TIdentifier extends string> {
|
|
34
|
+
readonly _contract: (contract: TContract) => TContract;
|
|
35
|
+
readonly _databaseIdentifier: (identifier: TIdentifier) => TIdentifier;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
type DatabaseModels<TContract extends AnySqlContract, TIdentifier extends string> = {
|
|
39
|
+
readonly [TModel in keyof DefaultModels<TContract>]: Relation<
|
|
40
|
+
DefaultModels<TContract>[TModel],
|
|
41
|
+
TContract,
|
|
42
|
+
TModel & string,
|
|
43
|
+
DatabaseIdentifier<TContract, TIdentifier>
|
|
44
|
+
>;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export type DatabaseService<TContract extends AnySqlContract, TIdentifier extends string> = DatabaseModels<TContract, TIdentifier> & {
|
|
48
|
+
transaction: <A, E, R>(
|
|
49
|
+
program: Effect.Effect<A, E, R> & (DatabaseIdentifier<TContract, TIdentifier> extends R ? unknown : never),
|
|
50
|
+
) => Effect.Effect<A, E | PrismaError, Exclude<R, DatabaseIdentifier<TContract, TIdentifier>>>;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
export interface DatabaseServiceHolder<TContract extends AnySqlContract, TIdentifier extends string>
|
|
54
|
+
extends Context.Service<DatabaseIdentifier<TContract, TIdentifier>, DatabaseService<TContract, TIdentifier>> {}
|
|
55
|
+
|
|
56
|
+
export type AnyDatabase = Effect.Effect<unknown, never, unknown> & {
|
|
57
|
+
readonly layer: (...arguments_: readonly never[]) => Layer.Any;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export type DatabaseServiceOf<TDatabase extends AnyDatabase> = Effect.Success<TDatabase>;
|
|
61
|
+
|
|
62
|
+
export interface SqlDatabase<TContract extends AnySqlContract, TIdentifier extends string, TOptions>
|
|
63
|
+
extends DatabaseServiceHolder<TContract, TIdentifier> {
|
|
64
|
+
readonly layer: (options: TOptions) => Layer.Layer<DatabaseIdentifier<TContract, TIdentifier>, PrismaError>;
|
|
65
|
+
}
|
|
@@ -8,8 +8,9 @@ interface Settled {
|
|
|
8
8
|
readonly transaction: SettledTransaction;
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
Object.assign(new Error(code, { cause }), { code, ...details });
|
|
11
|
+
function runtimeFailure(code: string, cause: unknown, details?: Readonly<Record<string, unknown>>): Error & { readonly code: string } {
|
|
12
|
+
return Object.assign(new Error(code, { cause }), { code, ...details });
|
|
13
|
+
}
|
|
13
14
|
|
|
14
15
|
class ConnectionDisposal {
|
|
15
16
|
disposed = false;
|
|
@@ -28,7 +29,7 @@ class ConnectionDisposal {
|
|
|
28
29
|
}
|
|
29
30
|
}
|
|
30
31
|
|
|
31
|
-
|
|
32
|
+
async function commit({ transaction }: Settled, disposal: ConnectionDisposal): Promise<unknown> {
|
|
32
33
|
try {
|
|
33
34
|
await transaction.commit();
|
|
34
35
|
return undefined;
|
|
@@ -36,9 +37,9 @@ const commit = async ({ transaction }: Settled, disposal: ConnectionDisposal): P
|
|
|
36
37
|
await transaction.rollback().catch(() => disposal.destroy(commitError));
|
|
37
38
|
return runtimeFailure("RUNTIME.TRANSACTION_COMMIT_FAILED", commitError);
|
|
38
39
|
}
|
|
39
|
-
}
|
|
40
|
+
}
|
|
40
41
|
|
|
41
|
-
|
|
42
|
+
async function rollback({ transaction }: Settled, disposal: ConnectionDisposal): Promise<unknown> {
|
|
42
43
|
try {
|
|
43
44
|
await transaction.rollback();
|
|
44
45
|
return undefined;
|
|
@@ -46,9 +47,9 @@ const rollback = async ({ transaction }: Settled, disposal: ConnectionDisposal):
|
|
|
46
47
|
await disposal.destroy(rollbackError);
|
|
47
48
|
return runtimeFailure("RUNTIME.TRANSACTION_ROLLBACK_FAILED", rollbackError);
|
|
48
49
|
}
|
|
49
|
-
}
|
|
50
|
+
}
|
|
50
51
|
|
|
51
|
-
|
|
52
|
+
async function release({ connection }: Settled, disposal: ConnectionDisposal, failure: unknown): Promise<void> {
|
|
52
53
|
if (disposal.disposed) {
|
|
53
54
|
return;
|
|
54
55
|
}
|
|
@@ -58,7 +59,7 @@ const release = async ({ connection }: Settled, disposal: ConnectionDisposal, fa
|
|
|
58
59
|
await disposal.destroy(releaseError);
|
|
59
60
|
throw failure === undefined ? releaseError : runtimeFailure("RUNTIME.TRANSACTION_RELEASE_FAILED", failure, { releaseError });
|
|
60
61
|
}
|
|
61
|
-
}
|
|
62
|
+
}
|
|
62
63
|
|
|
63
64
|
export const settleConnection = async (settled: Settled, commitTransaction: boolean): Promise<void> => {
|
|
64
65
|
const disposal = new ConnectionDisposal(settled.connection);
|
|
@@ -1,38 +1,38 @@
|
|
|
1
1
|
import { type Context, Effect } from "effect";
|
|
2
|
+
import type { DatabaseIdentifier, DatabaseService, DefaultModels } from "#databaseTypes.ts";
|
|
2
3
|
import { type PrismaError, toPrismaError } from "#error.ts";
|
|
3
4
|
import { assertAvailableModelNames } from "./client-lifecycle.ts";
|
|
4
|
-
import type { DatabaseIdentifier, DatabaseService, DefaultModels } from "./database-types.ts";
|
|
5
5
|
import type { AnySqlContract, DatabaseExecutor } from "./executor.ts";
|
|
6
6
|
import { makeModelRelation } from "./relation-runtime.ts";
|
|
7
7
|
import { acquireTransaction, releaseTransaction, type TransactionOrm, withTransactionSemaphore } from "./transaction.ts";
|
|
8
8
|
|
|
9
|
-
export interface ActiveTransaction<
|
|
10
|
-
readonly executor: DatabaseExecutor<DefaultModels<
|
|
11
|
-
readonly facade: DatabaseService<
|
|
9
|
+
export interface ActiveTransaction<TContract extends AnySqlContract, TIdentifier extends string> {
|
|
10
|
+
readonly executor: DatabaseExecutor<DefaultModels<TContract>, TContract>;
|
|
11
|
+
readonly facade: DatabaseService<TContract, TIdentifier>;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
export interface FacadeScope<
|
|
15
|
-
readonly activeTransaction: Context.Reference<ActiveTransaction<
|
|
16
|
-
readonly executors: WeakMap<DatabaseService<
|
|
17
|
-
readonly service: Context.Service<DatabaseIdentifier<
|
|
14
|
+
export interface FacadeScope<TContract extends AnySqlContract, TIdentifier extends string> {
|
|
15
|
+
readonly activeTransaction: Context.Reference<ActiveTransaction<TContract, TIdentifier> | undefined>;
|
|
16
|
+
readonly executors: WeakMap<DatabaseService<TContract, TIdentifier>, DatabaseExecutor<DefaultModels<TContract>, TContract>>;
|
|
17
|
+
readonly service: Context.Service<DatabaseIdentifier<TContract, TIdentifier>, DatabaseService<TContract, TIdentifier>>;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
// Prisma Next builds each namespace's model clients from the contract, so they are the contract's DefaultModels.
|
|
21
|
-
export function contractModels<
|
|
21
|
+
export function contractModels<TModels extends object>(namespace: unknown): TModels;
|
|
22
22
|
export function contractModels(namespace: unknown): unknown {
|
|
23
23
|
return namespace;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
// The facade serves `transaction` itself and a Relation for every other model name, matching DatabaseService.
|
|
27
|
-
function databaseFacade<
|
|
27
|
+
function databaseFacade<TContract extends AnySqlContract, TIdentifier extends string>(facade: object): DatabaseService<TContract, TIdentifier>;
|
|
28
28
|
function databaseFacade(facade: object): unknown {
|
|
29
29
|
return facade;
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
export const namespaceModels = <
|
|
33
|
-
contract:
|
|
34
|
-
namespacedOrm: TransactionOrm<
|
|
35
|
-
):
|
|
32
|
+
export const namespaceModels = <TContract extends AnySqlContract, TModels extends object>(
|
|
33
|
+
contract: TContract,
|
|
34
|
+
namespacedOrm: TransactionOrm<TContract>,
|
|
35
|
+
): TModels => {
|
|
36
36
|
const namespaces = Object.entries(contract.domain.namespaces);
|
|
37
37
|
const [only] = namespaces;
|
|
38
38
|
if (namespaces.length !== 1 || only === undefined) {
|
|
@@ -40,18 +40,18 @@ export const namespaceModels = <Contract extends AnySqlContract, Models extends
|
|
|
40
40
|
}
|
|
41
41
|
const [postgresSchemaOrSqliteUnbound, namespace] = only;
|
|
42
42
|
assertAvailableModelNames(Object.keys(namespace.models));
|
|
43
|
-
return contractModels<
|
|
43
|
+
return contractModels<TModels>(Reflect.get(namespacedOrm, postgresSchemaOrSqliteUnbound));
|
|
44
44
|
};
|
|
45
45
|
|
|
46
|
-
export const runTransaction = <
|
|
47
|
-
scope: FacadeScope<
|
|
48
|
-
current: DatabaseExecutor<DefaultModels<
|
|
49
|
-
currentFacade: DatabaseService<
|
|
46
|
+
export const runTransaction = <TContract extends AnySqlContract, TIdentifier extends string, A, E, R>(
|
|
47
|
+
scope: FacadeScope<TContract, TIdentifier>,
|
|
48
|
+
current: DatabaseExecutor<DefaultModels<TContract>, TContract>,
|
|
49
|
+
currentFacade: DatabaseService<TContract, TIdentifier>,
|
|
50
50
|
program: Effect.Effect<A, E, R>,
|
|
51
51
|
release: typeof releaseTransaction,
|
|
52
52
|
mode: "test" | "transaction",
|
|
53
53
|
span: string,
|
|
54
|
-
): Effect.Effect<A, E | PrismaError, Exclude<R, DatabaseIdentifier<
|
|
54
|
+
): Effect.Effect<A, E | PrismaError, Exclude<R, DatabaseIdentifier<TContract, TIdentifier>>> => {
|
|
55
55
|
if (!current.liveness.open) {
|
|
56
56
|
return Effect.fail(toPrismaError({ code: current.liveness.closedCode }));
|
|
57
57
|
}
|
|
@@ -69,7 +69,7 @@ export const runTransaction = <Contract extends AnySqlContract, Identifier exten
|
|
|
69
69
|
const transaction = Effect.acquireUseRelease(
|
|
70
70
|
acquireTransaction(
|
|
71
71
|
current,
|
|
72
|
-
(transactionOrm) => namespaceModels<
|
|
72
|
+
(transactionOrm) => namespaceModels<TContract, DefaultModels<TContract>>(current.client.contract, transactionOrm),
|
|
73
73
|
mode,
|
|
74
74
|
),
|
|
75
75
|
(resource) => {
|
|
@@ -88,11 +88,11 @@ export const runTransaction = <Contract extends AnySqlContract, Identifier exten
|
|
|
88
88
|
return withTransactionSemaphore(current.transactionSemaphore, transaction);
|
|
89
89
|
};
|
|
90
90
|
|
|
91
|
-
export function makeFacade<
|
|
92
|
-
scope: FacadeScope<
|
|
93
|
-
current: DatabaseExecutor<DefaultModels<
|
|
94
|
-
): DatabaseService<
|
|
95
|
-
let facade: DatabaseService<
|
|
91
|
+
export function makeFacade<TContract extends AnySqlContract, TIdentifier extends string>(
|
|
92
|
+
scope: FacadeScope<TContract, TIdentifier>,
|
|
93
|
+
current: DatabaseExecutor<DefaultModels<TContract>, TContract>,
|
|
94
|
+
): DatabaseService<TContract, TIdentifier> {
|
|
95
|
+
let facade: DatabaseService<TContract, TIdentifier>;
|
|
96
96
|
const resolveActiveTransaction = Effect.flatMap(scope.activeTransaction, (active) => {
|
|
97
97
|
if (current.mode === "root") {
|
|
98
98
|
return Effect.succeed(active ?? { executor: current, facade });
|
|
@@ -110,7 +110,7 @@ export function makeFacade<Contract extends AnySqlContract, Identifier extends s
|
|
|
110
110
|
);
|
|
111
111
|
const target: object = Object.create(null);
|
|
112
112
|
Object.assign(target, {
|
|
113
|
-
transaction: <A, E, R>(program: Effect.Effect<A, E, R> & (DatabaseIdentifier<
|
|
113
|
+
transaction: <A, E, R>(program: Effect.Effect<A, E, R> & (DatabaseIdentifier<TContract, TIdentifier> extends R ? unknown : never)) =>
|
|
114
114
|
Effect.suspend(() => {
|
|
115
115
|
if (!current.liveness.open) {
|
|
116
116
|
return Effect.fail(toPrismaError({ code: current.liveness.closedCode }));
|
|
@@ -120,7 +120,7 @@ export function makeFacade<Contract extends AnySqlContract, Identifier extends s
|
|
|
120
120
|
);
|
|
121
121
|
}),
|
|
122
122
|
});
|
|
123
|
-
facade = databaseFacade<
|
|
123
|
+
facade = databaseFacade<TContract, TIdentifier>(
|
|
124
124
|
new Proxy(target, {
|
|
125
125
|
get(target, property, receiver) {
|
|
126
126
|
if (Reflect.has(target, property)) {
|
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
import { Context, Effect, Layer } from "effect";
|
|
2
|
-
import type { PrismaError } from "#error.ts";
|
|
3
|
-
import { type ActiveTransaction, type FacadeScope, makeFacade, runTransaction } from "./database-facade.ts";
|
|
4
2
|
import {
|
|
5
3
|
type DatabaseIdentifier,
|
|
6
4
|
type DatabaseIdentifierLiteral,
|
|
@@ -8,38 +6,40 @@ import {
|
|
|
8
6
|
type DefaultModels,
|
|
9
7
|
internalContextIdentifierPrefix,
|
|
10
8
|
type SqlDatabase,
|
|
11
|
-
} from "
|
|
9
|
+
} from "#databaseTypes.ts";
|
|
10
|
+
import type { PrismaError } from "#error.ts";
|
|
11
|
+
import { type ActiveTransaction, type FacadeScope, makeFacade, runTransaction } from "./database-facade.ts";
|
|
12
12
|
import type { AnySqlContract, DatabaseExecutor } from "./executor.ts";
|
|
13
13
|
import { DatabaseTestingTypeId } from "./testing.ts";
|
|
14
14
|
import { releaseTestTransaction } from "./transaction.ts";
|
|
15
15
|
|
|
16
16
|
let activeTransactionReferenceSequence = 0;
|
|
17
17
|
|
|
18
|
-
export const makeSqlDatabase = <const
|
|
19
|
-
identifier: DatabaseIdentifierLiteral<
|
|
20
|
-
acquireExecutor: (options:
|
|
21
|
-
): SqlDatabase<
|
|
18
|
+
export const makeSqlDatabase = <const TContract extends AnySqlContract, const TIdentifier extends string, TOptions>(
|
|
19
|
+
identifier: DatabaseIdentifierLiteral<TIdentifier>,
|
|
20
|
+
acquireExecutor: (options: TOptions) => Effect.Effect<DatabaseExecutor<DefaultModels<TContract>, TContract>, PrismaError>,
|
|
21
|
+
): SqlDatabase<TContract, TIdentifier, TOptions> => {
|
|
22
22
|
if (identifier.startsWith(internalContextIdentifierPrefix)) {
|
|
23
23
|
throw new TypeError("Database identifier uses a reserved internal namespace");
|
|
24
24
|
}
|
|
25
|
-
type Models = DefaultModels<
|
|
26
|
-
type DatabaseId = DatabaseIdentifier<
|
|
25
|
+
type Models = DefaultModels<TContract>;
|
|
26
|
+
type DatabaseId = DatabaseIdentifier<TContract, TIdentifier>;
|
|
27
27
|
|
|
28
|
-
const Service = Context.Service<DatabaseId, DatabaseService<
|
|
29
|
-
const scope: FacadeScope<
|
|
30
|
-
activeTransaction: Context.Reference<ActiveTransaction<
|
|
28
|
+
const Service = Context.Service<DatabaseId, DatabaseService<TContract, TIdentifier>>(identifier);
|
|
29
|
+
const scope: FacadeScope<TContract, TIdentifier> = {
|
|
30
|
+
activeTransaction: Context.Reference<ActiveTransaction<TContract, TIdentifier> | undefined>(
|
|
31
31
|
`${internalContextIdentifierPrefix}ActiveTransaction/${activeTransactionReferenceSequence++}`,
|
|
32
32
|
{ defaultValue: () => undefined },
|
|
33
33
|
),
|
|
34
34
|
executors: new WeakMap(),
|
|
35
35
|
service: Service,
|
|
36
36
|
};
|
|
37
|
-
|
|
37
|
+
function executorOf(facade: DatabaseService<TContract, TIdentifier>): Effect.Effect<DatabaseExecutor<Models, TContract>> {
|
|
38
38
|
const executor = scope.executors.get(facade);
|
|
39
39
|
return executor === undefined
|
|
40
40
|
? Effect.die(new TypeError("The database service was not created by its database Layer"))
|
|
41
41
|
: Effect.succeed(executor);
|
|
42
|
-
}
|
|
42
|
+
}
|
|
43
43
|
|
|
44
44
|
const withTestTransaction = <A, E, R>(
|
|
45
45
|
program: Effect.Effect<A, E, R> & (DatabaseId extends R ? unknown : never),
|
|
@@ -50,7 +50,7 @@ export const makeSqlDatabase = <const Contract extends AnySqlContract, const Ide
|
|
|
50
50
|
),
|
|
51
51
|
);
|
|
52
52
|
|
|
53
|
-
const layer = (layerOptions:
|
|
53
|
+
const layer = (layerOptions: TOptions): Layer.Layer<DatabaseId, PrismaError> => {
|
|
54
54
|
const acquire = Effect.acquireRelease(
|
|
55
55
|
Effect.suspend(() => acquireExecutor(layerOptions)),
|
|
56
56
|
(executor) => {
|
package/src/internal/dynamic.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
function isObjectLike(value: unknown): value is object {
|
|
2
|
+
return (typeof value === "object" && value !== null) || typeof value === "function";
|
|
3
|
+
}
|
|
2
4
|
|
|
3
5
|
export const hasMethod = (value: unknown, name: PropertyKey): value is object =>
|
|
4
6
|
isObjectLike(value) && typeof Reflect.get(value, name) === "function";
|
package/src/internal/executor.ts
CHANGED
|
@@ -6,22 +6,22 @@ import type { Semaphore } from "effect";
|
|
|
6
6
|
export type AnySqlContract = PrismaContract<SqlStorage>;
|
|
7
7
|
export type AnyPostgresContract = AnySqlContract;
|
|
8
8
|
|
|
9
|
-
export interface SqlDatabaseClient<
|
|
9
|
+
export interface SqlDatabaseClient<TContract extends AnySqlContract> {
|
|
10
10
|
close: () => Promise<void>;
|
|
11
|
-
readonly context: ExecutionContext<
|
|
12
|
-
readonly contract:
|
|
11
|
+
readonly context: ExecutionContext<TContract>;
|
|
12
|
+
readonly contract: TContract;
|
|
13
13
|
runtime: () => Runtime;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
export interface DatabaseExecutor<
|
|
17
|
-
readonly client: SqlDatabaseClient<
|
|
16
|
+
export interface DatabaseExecutor<TModels extends object, TContract extends AnySqlContract = AnySqlContract> {
|
|
17
|
+
readonly client: SqlDatabaseClient<TContract>;
|
|
18
18
|
readonly identity: object;
|
|
19
19
|
readonly liveness: {
|
|
20
20
|
readonly closedCode: string;
|
|
21
21
|
open: boolean;
|
|
22
22
|
};
|
|
23
23
|
readonly mode: "root" | "test" | "transaction";
|
|
24
|
-
readonly models:
|
|
24
|
+
readonly models: TModels;
|
|
25
25
|
readonly querySemaphore: Semaphore.Semaphore | undefined;
|
|
26
26
|
readonly transactionIdentity: object | undefined;
|
|
27
27
|
readonly transactionSemaphore: Semaphore.Semaphore | undefined;
|
package/src/internal/recipe.ts
CHANGED
|
@@ -23,7 +23,7 @@ export const appendOperation = (parent: RelationRecipe, name: PropertyKey, argum
|
|
|
23
23
|
parent,
|
|
24
24
|
});
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
function operations(recipe: RelationRecipe): readonly RelationOperation[] {
|
|
27
27
|
const reversed: RelationOperation[] = [];
|
|
28
28
|
let current: RelationRecipe | undefined = recipe;
|
|
29
29
|
|
|
@@ -35,16 +35,16 @@ const operations = (recipe: RelationRecipe): readonly RelationOperation[] => {
|
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
return reversed.reverse();
|
|
38
|
-
}
|
|
38
|
+
}
|
|
39
39
|
|
|
40
|
-
|
|
40
|
+
function 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
|
}
|
|
44
44
|
return invokeMethod(current, name, arguments_);
|
|
45
|
-
}
|
|
45
|
+
}
|
|
46
46
|
|
|
47
|
-
|
|
47
|
+
function replayPlan(collection: unknown, plan: RelationPlan, owner: object, transactionIdentity: object | undefined): unknown {
|
|
48
48
|
if (!plan.liveness.open) {
|
|
49
49
|
throw new TypeError("Included Relation is closed");
|
|
50
50
|
}
|
|
@@ -64,9 +64,9 @@ const replayPlan = (collection: unknown, plan: RelationPlan, owner: object, tran
|
|
|
64
64
|
return refined;
|
|
65
65
|
}
|
|
66
66
|
return applyMethod(refined, "count", [], plan.recipe.model);
|
|
67
|
-
}
|
|
67
|
+
}
|
|
68
68
|
|
|
69
|
-
|
|
69
|
+
function includeRefinement(value: unknown, owner: object, transactionIdentity: object | undefined): (collection: unknown) => unknown {
|
|
70
70
|
const plan = getRelationPlan(value);
|
|
71
71
|
if (plan !== undefined) {
|
|
72
72
|
return (collection) => replayPlan(collection, plan, owner, transactionIdentity);
|
|
@@ -97,9 +97,9 @@ const includeRefinement = (value: unknown, owner: object, transactionIdentity: o
|
|
|
97
97
|
const branches = Object.fromEntries(plans.map(([name, queryPlan]) => [name, replayPlan(collection, queryPlan, owner, transactionIdentity)]));
|
|
98
98
|
return applyMethod(collection, "combine", [branches], model ?? "relation");
|
|
99
99
|
};
|
|
100
|
-
}
|
|
100
|
+
}
|
|
101
101
|
|
|
102
|
-
|
|
102
|
+
function replayRecipeFrom(root: unknown, recipe: RelationRecipe, owner: object, transactionIdentity: object | undefined): unknown {
|
|
103
103
|
let current = root;
|
|
104
104
|
for (const operation of operations(recipe)) {
|
|
105
105
|
const arguments_ =
|
|
@@ -109,7 +109,7 @@ const replayRecipeFrom = (root: unknown, recipe: RelationRecipe, owner: object,
|
|
|
109
109
|
current = applyMethod(current, operation.name, arguments_, recipe.model);
|
|
110
110
|
}
|
|
111
111
|
return current;
|
|
112
|
-
}
|
|
112
|
+
}
|
|
113
113
|
|
|
114
114
|
export const replayRecipe = (models: object, recipe: RelationRecipe, owner: object, transactionIdentity: object | undefined): unknown => {
|
|
115
115
|
const current: unknown = Reflect.get(models, recipe.model);
|