@shivaedev/effect-prisma 0.8.0 → 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 +12 -0
- package/README.md +198 -155
- package/dist/database.d.ts +5 -5
- 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 +17 -17
- package/dist/databaseTypes.d.ts.map +1 -1
- 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 +11 -11
- 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 +1 -1
- package/dist/internal/database-factory.d.ts.map +1 -1
- package/dist/internal/database-factory.js +2 -2
- 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.map +1 -1
- package/dist/sqlite.js +8 -6
- package/dist/sqlite.js.map +1 -1
- package/docs/north-star.md +47 -0
- package/docs/roadmap.md +32 -0
- package/package.json +27 -23
- package/src/database.ts +29 -23
- package/src/databaseTypes.ts +31 -31
- package/src/internal/adapters/transaction-settlement.ts +9 -8
- package/src/internal/database-facade.ts +27 -27
- package/src/internal/database-factory.ts +12 -12
- 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 +8 -6
package/src/database.ts
CHANGED
|
@@ -12,9 +12,9 @@ export interface DatabaseLayerOptions extends PostgresOptionsBase {
|
|
|
12
12
|
readonly url: string | Redacted.Redacted<string>;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
type DatabaseFactoryOptions<
|
|
15
|
+
type DatabaseFactoryOptions<TContract extends AnySqlContract> =
|
|
16
16
|
| {
|
|
17
|
-
readonly contract:
|
|
17
|
+
readonly contract: TContract;
|
|
18
18
|
readonly contractJson?: never;
|
|
19
19
|
}
|
|
20
20
|
| {
|
|
@@ -22,38 +22,44 @@ type DatabaseFactoryOptions<Contract extends AnySqlContract> =
|
|
|
22
22
|
readonly contract?: never;
|
|
23
23
|
};
|
|
24
24
|
|
|
25
|
-
export interface DatabaseDefinition<
|
|
26
|
-
|
|
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>;
|
|
27
28
|
}
|
|
28
29
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
})
|
|
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
|
+
}
|
|
36
39
|
|
|
37
|
-
|
|
38
|
-
|
|
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
|
+
}
|
|
39
45
|
|
|
40
46
|
export const makeDatabase =
|
|
41
|
-
<const
|
|
42
|
-
<const
|
|
43
|
-
identifier: DatabaseIdentifierLiteral<
|
|
44
|
-
options: DatabaseFactoryOptions<
|
|
45
|
-
): DatabaseDefinition<
|
|
46
|
-
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>;
|
|
47
53
|
|
|
48
|
-
return makeSqlDatabase<
|
|
54
|
+
return makeSqlDatabase<TContract, TIdentifier, DatabaseLayerOptions>(identifier, (layerOptions) => {
|
|
49
55
|
const forwarded = clientOptions(layerOptions);
|
|
50
56
|
const client =
|
|
51
57
|
options.contract === undefined
|
|
52
|
-
? postgres<
|
|
58
|
+
? postgres<TContract>({
|
|
53
59
|
...forwarded,
|
|
54
60
|
contractJson: options.contractJson,
|
|
55
61
|
})
|
|
56
|
-
: postgres<
|
|
62
|
+
: postgres<TContract>({
|
|
57
63
|
...forwarded,
|
|
58
64
|
contract: options.contract,
|
|
59
65
|
});
|
|
@@ -67,7 +73,7 @@ export const makeDatabase =
|
|
|
67
73
|
open: true,
|
|
68
74
|
},
|
|
69
75
|
mode: "root",
|
|
70
|
-
models: defaultModels<
|
|
76
|
+
models: defaultModels<TContract, Models>(client),
|
|
71
77
|
querySemaphore: undefined,
|
|
72
78
|
transactionIdentity: undefined,
|
|
73
79
|
transactionSemaphore: undefined,
|
package/src/databaseTypes.ts
CHANGED
|
@@ -4,62 +4,62 @@ import type { PrismaError } from "#error.ts";
|
|
|
4
4
|
import type { AnySqlContract } from "#internal/executor.ts";
|
|
5
5
|
import type { Relation } from "#relation.ts";
|
|
6
6
|
|
|
7
|
-
type OrmFor<
|
|
8
|
-
type DefaultNamespaceId<
|
|
7
|
+
type OrmFor<TContract extends AnySqlContract> = ReturnType<typeof orm<TContract>>;
|
|
8
|
+
type DefaultNamespaceId<TContract extends AnySqlContract> = "__unbound__" extends keyof OrmFor<TContract>
|
|
9
9
|
? "__unbound__"
|
|
10
|
-
: "public" extends keyof OrmFor<
|
|
10
|
+
: "public" extends keyof OrmFor<TContract>
|
|
11
11
|
? "public"
|
|
12
|
-
: keyof OrmFor<
|
|
13
|
-
export type DefaultModels<
|
|
14
|
-
?
|
|
12
|
+
: keyof OrmFor<TContract>;
|
|
13
|
+
export type DefaultModels<TContract extends AnySqlContract> = OrmFor<TContract>[DefaultNamespaceId<TContract>] extends infer TModels extends object
|
|
14
|
+
? TModels
|
|
15
15
|
: never;
|
|
16
16
|
|
|
17
|
-
type IsUnion<
|
|
17
|
+
type IsUnion<TValue, TWhole = TValue> = TValue extends unknown ? ([TWhole] extends [TValue] ? false : true) : never;
|
|
18
18
|
|
|
19
|
-
type IsSingletonString<
|
|
19
|
+
type IsSingletonString<TValue extends string> = Record<never, never> extends Record<TValue, never> ? false : true;
|
|
20
20
|
|
|
21
21
|
export const internalContextIdentifierPrefix = "\0@shivaedev/effect-prisma/internal/";
|
|
22
22
|
|
|
23
|
-
export type DatabaseIdentifierLiteral<
|
|
23
|
+
export type DatabaseIdentifierLiteral<TIdentifier extends string> = [TIdentifier] extends [`\0@shivaedev/effect-prisma/internal/${string}`]
|
|
24
24
|
? never
|
|
25
|
-
: string extends
|
|
25
|
+
: string extends TIdentifier
|
|
26
26
|
? never
|
|
27
|
-
: true extends IsUnion<
|
|
27
|
+
: true extends IsUnion<TIdentifier>
|
|
28
28
|
? never
|
|
29
|
-
: IsSingletonString<
|
|
30
|
-
?
|
|
29
|
+
: IsSingletonString<TIdentifier> extends true
|
|
30
|
+
? TIdentifier
|
|
31
31
|
: never;
|
|
32
32
|
|
|
33
|
-
export interface DatabaseIdentifier<
|
|
34
|
-
readonly _contract: (contract:
|
|
35
|
-
readonly _databaseIdentifier: (identifier:
|
|
33
|
+
export interface DatabaseIdentifier<TContract extends AnySqlContract, TIdentifier extends string> {
|
|
34
|
+
readonly _contract: (contract: TContract) => TContract;
|
|
35
|
+
readonly _databaseIdentifier: (identifier: TIdentifier) => TIdentifier;
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
type DatabaseModels<
|
|
39
|
-
readonly [
|
|
40
|
-
DefaultModels<
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
DatabaseIdentifier<
|
|
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
44
|
>;
|
|
45
45
|
};
|
|
46
46
|
|
|
47
|
-
export type DatabaseService<
|
|
47
|
+
export type DatabaseService<TContract extends AnySqlContract, TIdentifier extends string> = DatabaseModels<TContract, TIdentifier> & {
|
|
48
48
|
transaction: <A, E, R>(
|
|
49
|
-
program: Effect.Effect<A, E, R> & (DatabaseIdentifier<
|
|
50
|
-
) => Effect.Effect<A, E | PrismaError, Exclude<R, DatabaseIdentifier<
|
|
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
51
|
};
|
|
52
52
|
|
|
53
|
-
export interface DatabaseServiceHolder<
|
|
54
|
-
extends Context.Service<DatabaseIdentifier<
|
|
53
|
+
export interface DatabaseServiceHolder<TContract extends AnySqlContract, TIdentifier extends string>
|
|
54
|
+
extends Context.Service<DatabaseIdentifier<TContract, TIdentifier>, DatabaseService<TContract, TIdentifier>> {}
|
|
55
55
|
|
|
56
56
|
export type AnyDatabase = Effect.Effect<unknown, never, unknown> & {
|
|
57
57
|
readonly layer: (...arguments_: readonly never[]) => Layer.Any;
|
|
58
58
|
};
|
|
59
59
|
|
|
60
|
-
export type DatabaseServiceOf<
|
|
60
|
+
export type DatabaseServiceOf<TDatabase extends AnyDatabase> = Effect.Success<TDatabase>;
|
|
61
61
|
|
|
62
|
-
export interface SqlDatabase<
|
|
63
|
-
extends DatabaseServiceHolder<
|
|
64
|
-
readonly layer: (options:
|
|
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
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);
|
|
@@ -6,33 +6,33 @@ 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)) {
|
|
@@ -15,31 +15,31 @@ 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);
|
|
@@ -3,10 +3,13 @@ import type { PrismaError } from "#error.ts";
|
|
|
3
3
|
import { hasMethod, invokeMethod, isPromiseLike } from "./dynamic.ts";
|
|
4
4
|
import { fromPrismaPromise } from "./promise.ts";
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
isPromiseLike(value) ? fromPrismaPromise(() => value).pipe(Effect.map(map)) : Effect.succeed(map(value));
|
|
6
|
+
function settle(value: unknown, map: (result: unknown) => unknown): Effect.Effect<unknown, PrismaError> {
|
|
7
|
+
return isPromiseLike(value) ? fromPrismaPromise(() => value).pipe(Effect.map(map)) : Effect.succeed(map(value));
|
|
8
|
+
}
|
|
8
9
|
|
|
9
|
-
|
|
10
|
+
function countOf(result: unknown): unknown {
|
|
11
|
+
return typeof result === "object" && result !== null ? Reflect.get(result, "count") : undefined;
|
|
12
|
+
}
|
|
10
13
|
|
|
11
14
|
export const evaluateResult = (value: unknown, terminal: PropertyKey | undefined): Effect.Effect<unknown, PrismaError> => {
|
|
12
15
|
if (terminal === "count" && hasMethod(value, "aggregate")) {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Effect, Effectable, type Stream } from "effect";
|
|
2
2
|
import type { PrismaError } from "#error.ts";
|
|
3
3
|
import type { Relation } from "#relation.ts";
|
|
4
|
+
import { hasMethod } from "./dynamic.ts";
|
|
4
5
|
import type { AnyPostgresContract, DatabaseExecutor } from "./executor.ts";
|
|
5
6
|
import { executeQuery } from "./query-execution.ts";
|
|
6
7
|
import { appendOperation, type RelationRecipe, replayRecipe, rootRecipe } from "./recipe.ts";
|
|
@@ -8,10 +9,10 @@ import { setRelationPlan } from "./relation-plan.ts";
|
|
|
8
9
|
import { evaluateResult } from "./relation-result.ts";
|
|
9
10
|
import { makeRelationStream } from "./relation-stream.ts";
|
|
10
11
|
|
|
11
|
-
interface RelationRuntime<
|
|
12
|
-
readonly executor: DatabaseExecutor<
|
|
12
|
+
interface RelationRuntime<TModels extends object, TContract extends AnyPostgresContract> {
|
|
13
|
+
readonly executor: DatabaseExecutor<TModels, TContract>;
|
|
13
14
|
readonly recipe: RelationRecipe;
|
|
14
|
-
readonly resolveExecutor: Effect.Effect<DatabaseExecutor<
|
|
15
|
+
readonly resolveExecutor: Effect.Effect<DatabaseExecutor<TModels, TContract>, PrismaError>;
|
|
15
16
|
readonly terminal?: PropertyKey;
|
|
16
17
|
}
|
|
17
18
|
|
|
@@ -23,15 +24,15 @@ type AnyRelationRuntime = RelationRuntime<object, AnyPostgresContract>;
|
|
|
23
24
|
|
|
24
25
|
const runtimes = new WeakMap<object, AnyRelationRuntime>();
|
|
25
26
|
|
|
26
|
-
|
|
27
|
+
function runtimeOf(self: object): AnyRelationRuntime {
|
|
27
28
|
const runtime = runtimes.get(self);
|
|
28
29
|
if (runtime === undefined) {
|
|
29
30
|
throw new TypeError("Relation runtime is unavailable");
|
|
30
31
|
}
|
|
31
32
|
return runtime;
|
|
32
|
-
}
|
|
33
|
+
}
|
|
33
34
|
|
|
34
|
-
|
|
35
|
+
function relationEffect(self: object): Effect.Effect<unknown, PrismaError> {
|
|
35
36
|
const runtime = runtimeOf(self);
|
|
36
37
|
return Effect.flatMap(runtime.resolveExecutor, (executor) =>
|
|
37
38
|
executeQuery(
|
|
@@ -50,11 +51,11 @@ const relationEffect = (self: object): Effect.Effect<unknown, PrismaError> => {
|
|
|
50
51
|
kind: "client",
|
|
51
52
|
}),
|
|
52
53
|
);
|
|
53
|
-
}
|
|
54
|
+
}
|
|
54
55
|
|
|
55
56
|
const RelationPrototype = {
|
|
56
57
|
...Effectable.Prototype<RelationValue>({
|
|
57
|
-
evaluate() {
|
|
58
|
+
evaluate(this: RelationValue) {
|
|
58
59
|
return relationEffect(this);
|
|
59
60
|
},
|
|
60
61
|
label: "EffectPrismaRelation",
|
|
@@ -65,7 +66,7 @@ const RelationPrototype = {
|
|
|
65
66
|
},
|
|
66
67
|
};
|
|
67
68
|
|
|
68
|
-
|
|
69
|
+
function makeRelationProxy(runtime: AnyRelationRuntime): object {
|
|
69
70
|
const target: object = Object.create(RelationPrototype);
|
|
70
71
|
const proxy = new Proxy(target, {
|
|
71
72
|
get(self, property, receiver) {
|
|
@@ -89,6 +90,10 @@ const makeRelationProxy = (runtime: AnyRelationRuntime): object => {
|
|
|
89
90
|
terminal: "count",
|
|
90
91
|
});
|
|
91
92
|
}
|
|
93
|
+
// Effect reads protocol keys such as its Exit identifier from every yielded value, so only the collection's own methods become Relation calls.
|
|
94
|
+
if (!hasMethod(Reflect.get(runtime.executor.models, runtime.recipe.model), property)) {
|
|
95
|
+
return undefined;
|
|
96
|
+
}
|
|
92
97
|
return (...arguments_: readonly unknown[]) =>
|
|
93
98
|
makeRelationProxy({
|
|
94
99
|
...runtime,
|
|
@@ -109,25 +114,25 @@ const makeRelationProxy = (runtime: AnyRelationRuntime): object => {
|
|
|
109
114
|
setRelationPlan(value, plan);
|
|
110
115
|
}
|
|
111
116
|
return proxy;
|
|
112
|
-
}
|
|
117
|
+
}
|
|
113
118
|
|
|
114
119
|
// The proxy replays every Relation call onto the Prisma collection for `model`, so it has that collection's Relation surface.
|
|
115
|
-
function asRelation<
|
|
120
|
+
function asRelation<TCollection, TModel extends string>(proxy: object): Relation<TCollection, undefined, TModel>;
|
|
116
121
|
function asRelation(proxy: object): unknown {
|
|
117
122
|
return proxy;
|
|
118
123
|
}
|
|
119
124
|
|
|
120
125
|
export const makeModelRelation = <
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
126
|
+
TCollection,
|
|
127
|
+
TModels extends object,
|
|
128
|
+
TExecutorContract extends AnyPostgresContract = AnyPostgresContract,
|
|
129
|
+
TModel extends string = string,
|
|
125
130
|
>(
|
|
126
|
-
executor: DatabaseExecutor<
|
|
127
|
-
model:
|
|
128
|
-
resolveExecutor: Effect.Effect<DatabaseExecutor<
|
|
129
|
-
): Relation<
|
|
130
|
-
asRelation<
|
|
131
|
+
executor: DatabaseExecutor<TModels, TExecutorContract>,
|
|
132
|
+
model: TModel,
|
|
133
|
+
resolveExecutor: Effect.Effect<DatabaseExecutor<TModels, TExecutorContract>, PrismaError> = Effect.succeed(executor),
|
|
134
|
+
): Relation<TCollection, undefined, TModel> =>
|
|
135
|
+
asRelation<TCollection, TModel>(
|
|
131
136
|
makeRelationProxy({
|
|
132
137
|
executor,
|
|
133
138
|
recipe: rootRecipe(model),
|