@shivaedev/effect-prisma 0.6.4 → 0.7.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 +21 -0
- package/dist/bin/normalize-contract.js +1 -0
- package/dist/bin/normalize-contract.js.map +1 -1
- package/dist/error.js +4 -4
- package/dist/error.js.map +1 -1
- package/dist/internal/adapters/transaction-settlement.d.ts.map +1 -1
- package/dist/internal/adapters/transaction-settlement.js +1 -1
- package/dist/internal/adapters/transaction-settlement.js.map +1 -1
- package/dist/internal/client-lifecycle.d.ts +2 -2
- package/dist/internal/client-lifecycle.d.ts.map +1 -1
- package/dist/internal/client-lifecycle.js.map +1 -1
- package/dist/internal/contract-normalization.d.ts +0 -4
- package/dist/internal/contract-normalization.d.ts.map +1 -1
- package/dist/internal/contract-normalization.js +4 -7
- package/dist/internal/contract-normalization.js.map +1 -1
- package/dist/internal/database-facade.d.ts +0 -5
- package/dist/internal/database-facade.d.ts.map +1 -1
- package/dist/internal/database-facade.js +2 -7
- package/dist/internal/database-facade.js.map +1 -1
- package/dist/internal/database-factory.d.ts +0 -4
- package/dist/internal/database-factory.d.ts.map +1 -1
- package/dist/internal/database-factory.js +1 -7
- package/dist/internal/database-factory.js.map +1 -1
- package/dist/internal/database-types.d.ts +2 -6
- package/dist/internal/database-types.d.ts.map +1 -1
- package/dist/internal/dynamic.d.ts +1 -1
- package/dist/internal/dynamic.d.ts.map +1 -1
- package/dist/internal/dynamic.js.map +1 -1
- package/dist/internal/executor.d.ts +3 -7
- package/dist/internal/executor.d.ts.map +1 -1
- package/dist/internal/promise.js +1 -1
- package/dist/internal/promise.js.map +1 -1
- package/dist/internal/recipe.d.ts +3 -3
- package/dist/internal/recipe.d.ts.map +1 -1
- package/dist/internal/recipe.js +2 -2
- package/dist/internal/recipe.js.map +1 -1
- package/dist/internal/relation-result.js.map +1 -1
- package/dist/internal/relation-runtime.js +3 -3
- package/dist/internal/relation-runtime.js.map +1 -1
- package/dist/internal/relation-stream.d.ts.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 +0 -26
- package/dist/internal/sqlite-datetime.d.ts.map +1 -1
- package/dist/internal/sqlite-datetime.js +13 -34
- package/dist/internal/sqlite-datetime.js.map +1 -1
- package/dist/internal/sqlite-pragmas.d.ts +1 -13
- package/dist/internal/sqlite-pragmas.d.ts.map +1 -1
- package/dist/internal/sqlite-pragmas.js +2 -12
- package/dist/internal/sqlite-pragmas.js.map +1 -1
- package/dist/internal/transaction.d.ts +1 -1
- package/dist/internal/transaction.d.ts.map +1 -1
- package/dist/internal/transaction.js +2 -2
- package/dist/internal/transaction.js.map +1 -1
- package/dist/relation/include-metadata.d.ts +1 -1
- package/dist/relation/include-metadata.d.ts.map +1 -1
- package/dist/relation/include.d.ts +6 -3
- package/dist/relation/include.d.ts.map +1 -1
- package/dist/relation/prisma-methods.d.ts +14 -14
- 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 +2 -7
- package/dist/sqlite.d.ts.map +1 -1
- package/dist/sqlite.js.map +1 -1
- package/dist/testing/types.d.ts +5 -5
- package/dist/testing/types.d.ts.map +1 -1
- package/dist/testing/vitest.d.ts.map +1 -1
- package/dist/testing/vitest.js.map +1 -1
- package/package.json +34 -36
- package/src/bin/normalize-contract.ts +1 -0
- package/src/error.ts +4 -4
- package/src/internal/adapters/transaction-settlement.ts +4 -1
- package/src/internal/client-lifecycle.ts +2 -2
- package/src/internal/contract-normalization.ts +4 -7
- package/src/internal/database-facade.ts +2 -7
- package/src/internal/database-factory.ts +4 -8
- package/src/internal/database-types.ts +3 -7
- package/src/internal/dynamic.ts +1 -1
- package/src/internal/executor.ts +3 -7
- package/src/internal/promise.ts +1 -1
- package/src/internal/recipe.ts +8 -8
- package/src/internal/relation-result.ts +1 -1
- package/src/internal/relation-runtime.ts +4 -4
- package/src/internal/relation-stream.ts +4 -5
- package/src/internal/sqlite-datetime.ts +13 -34
- package/src/internal/sqlite-pragmas.ts +3 -13
- package/src/internal/transaction.ts +3 -3
- package/src/relation/include-metadata.ts +1 -1
- package/src/relation/include.ts +35 -33
- package/src/relation/prisma-methods.ts +19 -17
- package/src/relation.ts +2 -2
- package/src/sqlite.ts +2 -7
- package/src/testing/types.ts +6 -6
- package/src/testing/vitest.ts +1 -1
package/package.json
CHANGED
|
@@ -1,31 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shivaedev/effect-prisma",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "Effect-native PostgreSQL queries and transactions for Prisma Next",
|
|
5
|
-
"type": "module",
|
|
6
|
-
"license": "MIT",
|
|
7
|
-
"repository": {
|
|
8
|
-
"type": "git",
|
|
9
|
-
"url": "git+https://github.com/ShivaeDev/platform.git",
|
|
10
|
-
"directory": "packages/effect-prisma"
|
|
11
|
-
},
|
|
12
5
|
"homepage": "https://github.com/ShivaeDev/platform/tree/main/packages/effect-prisma#readme",
|
|
13
6
|
"bugs": {
|
|
14
7
|
"url": "https://github.com/ShivaeDev/platform/issues"
|
|
15
8
|
},
|
|
16
|
-
"
|
|
17
|
-
"
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+https://github.com/ShivaeDev/platform.git",
|
|
12
|
+
"directory": "packages/effect-prisma"
|
|
18
13
|
},
|
|
14
|
+
"license": "MIT",
|
|
19
15
|
"sideEffects": false,
|
|
20
|
-
"
|
|
21
|
-
"effect-prisma-normalize": "./dist/bin/normalize-contract.js"
|
|
22
|
-
},
|
|
23
|
-
"files": [
|
|
24
|
-
"dist",
|
|
25
|
-
"src",
|
|
26
|
-
"CHANGELOG.md",
|
|
27
|
-
"README.md"
|
|
28
|
-
],
|
|
16
|
+
"type": "module",
|
|
29
17
|
"exports": {
|
|
30
18
|
".": {
|
|
31
19
|
"types": "./dist/index.d.ts",
|
|
@@ -47,10 +35,15 @@
|
|
|
47
35
|
},
|
|
48
36
|
"./package.json": "./package.json"
|
|
49
37
|
},
|
|
50
|
-
"
|
|
51
|
-
"
|
|
52
|
-
"provenance": true
|
|
38
|
+
"bin": {
|
|
39
|
+
"effect-prisma-normalize": "./dist/bin/normalize-contract.js"
|
|
53
40
|
},
|
|
41
|
+
"files": [
|
|
42
|
+
"dist",
|
|
43
|
+
"src",
|
|
44
|
+
"CHANGELOG.md",
|
|
45
|
+
"README.md"
|
|
46
|
+
],
|
|
54
47
|
"dependencies": {
|
|
55
48
|
"@prisma-next/contract": "0.15.0",
|
|
56
49
|
"@prisma-next/postgres": "0.15.0",
|
|
@@ -61,15 +54,6 @@
|
|
|
61
54
|
"@prisma-next/sqlite": "0.15.0",
|
|
62
55
|
"@shivaedev/effect-test": "0.1.2"
|
|
63
56
|
},
|
|
64
|
-
"peerDependencies": {
|
|
65
|
-
"@effect/vitest": "4.0.0-rc.112",
|
|
66
|
-
"effect": "4.0.0-rc.112"
|
|
67
|
-
},
|
|
68
|
-
"peerDependenciesMeta": {
|
|
69
|
-
"@effect/vitest": {
|
|
70
|
-
"optional": true
|
|
71
|
-
}
|
|
72
|
-
},
|
|
73
57
|
"devDependencies": {
|
|
74
58
|
"@effect/vitest": "4.0.0-rc.112",
|
|
75
59
|
"@prisma-next/adapter-postgres": "0.15.0",
|
|
@@ -77,6 +61,7 @@
|
|
|
77
61
|
"@prisma-next/cli": "0.15.0",
|
|
78
62
|
"@prisma-next/target-postgres": "0.15.0",
|
|
79
63
|
"@prisma-next/target-sqlite": "0.15.0",
|
|
64
|
+
"@shivaedev/quality": "0.7.0",
|
|
80
65
|
"@types/node": "24.10.1",
|
|
81
66
|
"@types/pg": "8.20.0",
|
|
82
67
|
"@typescript/native": "npm:typescript@7.0.2",
|
|
@@ -84,13 +69,26 @@
|
|
|
84
69
|
"typescript": "npm:@typescript/typescript6@6.0.2",
|
|
85
70
|
"vitest": "4.1.9"
|
|
86
71
|
},
|
|
72
|
+
"peerDependencies": {
|
|
73
|
+
"@effect/vitest": "4.0.0-rc.112",
|
|
74
|
+
"effect": "4.0.0-rc.112"
|
|
75
|
+
},
|
|
76
|
+
"peerDependenciesMeta": {
|
|
77
|
+
"@effect/vitest": {
|
|
78
|
+
"optional": true
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
"engines": {
|
|
82
|
+
"node": ">=24"
|
|
83
|
+
},
|
|
84
|
+
"publishConfig": {
|
|
85
|
+
"access": "public",
|
|
86
|
+
"provenance": true
|
|
87
|
+
},
|
|
87
88
|
"scripts": {
|
|
88
89
|
"build": "node --eval \"import('node:fs').then(({ rmSync }) => rmSync('dist', { force: true, recursive: true }))\" && tsc6 --project tsconfig.build.json",
|
|
89
|
-
"
|
|
90
|
+
"ready": "pnpm typecheck && pnpm test && pnpm build",
|
|
90
91
|
"test": "vitest run",
|
|
91
|
-
"
|
|
92
|
-
"typecheck": "tsc --noEmit",
|
|
93
|
-
"typecheck:compat": "tsc6 --noEmit",
|
|
94
|
-
"ready": "pnpm check && pnpm typecheck && pnpm typecheck:compat && pnpm test && pnpm build && pnpm test:package"
|
|
92
|
+
"typecheck": "tsc --noEmit"
|
|
95
93
|
}
|
|
96
94
|
}
|
package/src/error.ts
CHANGED
|
@@ -54,19 +54,19 @@ export const toPrismaError = (error: PrismaFailure): PrismaError => {
|
|
|
54
54
|
if ("kind" in error && error.kind === "sql_query") {
|
|
55
55
|
return new PrismaError({
|
|
56
56
|
reason: new PrismaQueryFailure({
|
|
57
|
-
sqlState: error.sqlState,
|
|
58
|
-
constraint: error.constraint,
|
|
59
|
-
table: error.table,
|
|
60
57
|
column: error.column,
|
|
58
|
+
constraint: error.constraint,
|
|
61
59
|
original,
|
|
60
|
+
sqlState: error.sqlState,
|
|
61
|
+
table: error.table,
|
|
62
62
|
}),
|
|
63
63
|
});
|
|
64
64
|
}
|
|
65
65
|
if ("kind" in error && error.kind === "sql_connection") {
|
|
66
66
|
return new PrismaError({
|
|
67
67
|
reason: new PrismaConnectionFailure({
|
|
68
|
-
transient: error.transient,
|
|
69
68
|
original,
|
|
69
|
+
transient: error.transient,
|
|
70
70
|
}),
|
|
71
71
|
});
|
|
72
72
|
}
|
|
@@ -13,8 +13,11 @@ const runtimeFailure = (code: string, cause: unknown, details?: Readonly<Record<
|
|
|
13
13
|
|
|
14
14
|
class ConnectionDisposal {
|
|
15
15
|
disposed = false;
|
|
16
|
+
private readonly connection: SettledConnection;
|
|
16
17
|
|
|
17
|
-
constructor(
|
|
18
|
+
constructor(connection: SettledConnection) {
|
|
19
|
+
this.connection = connection;
|
|
20
|
+
}
|
|
18
21
|
|
|
19
22
|
async destroy(reason: unknown): Promise<void> {
|
|
20
23
|
if (this.disposed) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export interface ClientLifecycle {
|
|
2
|
-
readonly connect: () => PromiseLike<unknown>;
|
|
3
2
|
readonly close: () => PromiseLike<void>;
|
|
3
|
+
readonly connect: () => PromiseLike<unknown>;
|
|
4
4
|
}
|
|
5
5
|
|
|
6
6
|
export const acquireConnectedClient = async <A>(client: ClientLifecycle, initialize: () => A): Promise<A> => {
|
|
@@ -15,7 +15,7 @@ export const acquireConnectedClient = async <A>(client: ClientLifecycle, initial
|
|
|
15
15
|
|
|
16
16
|
const reservedModelNames = new Set(["transaction"]);
|
|
17
17
|
|
|
18
|
-
export const assertAvailableModelNames = (modelNames:
|
|
18
|
+
export const assertAvailableModelNames = (modelNames: readonly string[]): void => {
|
|
19
19
|
for (const modelName of modelNames) {
|
|
20
20
|
if (reservedModelNames.has(modelName)) {
|
|
21
21
|
throw new TypeError(`Prisma model name conflicts with the database facade: ${modelName}`);
|
|
@@ -1,11 +1,8 @@
|
|
|
1
|
-
const timestampReference = /\b(?:Timestamp|Timestamptz)<(?:\d+|undefined)>/
|
|
2
|
-
const timestampCodecReference = /\bCodecTypes\['pg\/(?:timestamp|timestamptz)@1'\]\['(?:input|output)'\]/
|
|
3
|
-
const unsupportedTimestampReference = /\b(?:Timestamp|Timestamptz)\s
|
|
1
|
+
const timestampReference = /\b(?:Timestamp|Timestamptz)<(?:\d+|undefined)>/gu;
|
|
2
|
+
const timestampCodecReference = /\bCodecTypes\['pg\/(?:timestamp|timestamptz)@1'\]\['(?:input|output)'\]/gu;
|
|
3
|
+
const unsupportedTimestampReference = /\b(?:Timestamp|Timestamptz)\s*</u;
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
* Align Prisma Next's generated PostgreSQL timestamp declarations with the
|
|
7
|
-
* JavaScript Dates accepted and returned by its runtime codecs.
|
|
8
|
-
*/
|
|
5
|
+
// Prisma Next declares PostgreSQL timestamps with types other than the JavaScript Dates its runtime codecs accept and return.
|
|
9
6
|
export const normalizePrismaNextContractTypes = (source: string): string => {
|
|
10
7
|
const normalized = source.replaceAll(timestampReference, "Date").replaceAll(timestampCodecReference, "Date");
|
|
11
8
|
|
|
@@ -29,11 +29,6 @@ function databaseFacade(facade: object): unknown {
|
|
|
29
29
|
return facade;
|
|
30
30
|
}
|
|
31
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
32
|
export const namespaceModels = <Contract extends AnySqlContract, Models extends object>(
|
|
38
33
|
contract: Contract,
|
|
39
34
|
namespacedOrm: TransactionOrm<Contract>,
|
|
@@ -43,9 +38,9 @@ export const namespaceModels = <Contract extends AnySqlContract, Models extends
|
|
|
43
38
|
if (namespaces.length !== 1 || only === undefined) {
|
|
44
39
|
throw new TypeError("Effect Prisma currently requires exactly one domain namespace");
|
|
45
40
|
}
|
|
46
|
-
const [
|
|
41
|
+
const [postgresSchemaOrSqliteUnbound, namespace] = only;
|
|
47
42
|
assertAvailableModelNames(Object.keys(namespace.models));
|
|
48
|
-
return contractModels<Models>(Reflect.get(namespacedOrm,
|
|
43
|
+
return contractModels<Models>(Reflect.get(namespacedOrm, postgresSchemaOrSqliteUnbound));
|
|
49
44
|
};
|
|
50
45
|
|
|
51
46
|
export const runTransaction = <Contract extends AnySqlContract, Identifier extends string, A, E, R>(
|
|
@@ -15,10 +15,6 @@ import { releaseTestTransaction } from "./transaction.ts";
|
|
|
15
15
|
|
|
16
16
|
let activeTransactionReferenceSequence = 0;
|
|
17
17
|
|
|
18
|
-
/**
|
|
19
|
-
* Assemble the database service and its Layer around a driver-specific
|
|
20
|
-
* executor acquisition.
|
|
21
|
-
*/
|
|
22
18
|
export const makeSqlDatabase = <const Contract extends AnySqlContract, const Identifier extends string, Options>(
|
|
23
19
|
identifier: DatabaseIdentifierLiteral<Identifier>,
|
|
24
20
|
acquireExecutor: (options: Options) => Effect.Effect<DatabaseExecutor<DefaultModels<Contract>, Contract>, PrismaError>,
|
|
@@ -48,11 +44,11 @@ export const makeSqlDatabase = <const Contract extends AnySqlContract, const Ide
|
|
|
48
44
|
const withTestTransaction = <A, E, R>(
|
|
49
45
|
program: Effect.Effect<A, E, R> & (DatabaseId extends R ? unknown : never),
|
|
50
46
|
): Effect.Effect<A, E | PrismaError, DatabaseId | Exclude<R, DatabaseId>> =>
|
|
51
|
-
Effect.flatMap(Service, (facade) =>
|
|
52
|
-
|
|
47
|
+
Effect.flatMap(Service, (facade) =>
|
|
48
|
+
Effect.flatMap(executorOf(facade), (current) =>
|
|
53
49
|
runTransaction(scope, current, facade, program, releaseTestTransaction, "test", "prisma.testTransaction"),
|
|
54
|
-
)
|
|
55
|
-
|
|
50
|
+
),
|
|
51
|
+
);
|
|
56
52
|
|
|
57
53
|
const layer = (layerOptions: Options): Layer.Layer<DatabaseId, PrismaError> => {
|
|
58
54
|
const acquire = Effect.acquireRelease(
|
|
@@ -45,20 +45,16 @@ type DatabaseModels<Contract extends AnySqlContract, Identifier extends string>
|
|
|
45
45
|
};
|
|
46
46
|
|
|
47
47
|
export type DatabaseService<Contract extends AnySqlContract, Identifier extends string> = DatabaseModels<Contract, Identifier> & {
|
|
48
|
-
transaction<A, E, R>(
|
|
48
|
+
transaction: <A, E, R>(
|
|
49
49
|
program: Effect.Effect<A, E, R> & (DatabaseIdentifier<Contract, Identifier> extends R ? unknown : never),
|
|
50
|
-
)
|
|
50
|
+
) => Effect.Effect<A, E | PrismaError, Exclude<R, DatabaseIdentifier<Contract, Identifier>>>;
|
|
51
51
|
};
|
|
52
52
|
|
|
53
|
-
/**
|
|
54
|
-
* The driver-independent part of a database definition: the Context service
|
|
55
|
-
* carrying the typed facade. Driver entrypoints add their own `layer` options.
|
|
56
|
-
*/
|
|
57
53
|
export interface DatabaseServiceHolder<Contract extends AnySqlContract, Identifier extends string>
|
|
58
54
|
extends Context.Service<DatabaseIdentifier<Contract, Identifier>, DatabaseService<Contract, Identifier>> {}
|
|
59
55
|
|
|
60
56
|
export type AnyDatabase = Effect.Effect<unknown, never, unknown> & {
|
|
61
|
-
readonly layer: (...arguments_:
|
|
57
|
+
readonly layer: (...arguments_: readonly never[]) => Layer.Any;
|
|
62
58
|
};
|
|
63
59
|
|
|
64
60
|
export type DatabaseServiceOf<Database extends AnyDatabase> = Effect.Success<Database>;
|
package/src/internal/dynamic.ts
CHANGED
|
@@ -3,7 +3,7 @@ const isObjectLike = (value: unknown): value is object => (typeof value === "obj
|
|
|
3
3
|
export const hasMethod = (value: unknown, name: PropertyKey): value is object =>
|
|
4
4
|
isObjectLike(value) && typeof Reflect.get(value, name) === "function";
|
|
5
5
|
|
|
6
|
-
export const invokeMethod = (target: object, name: PropertyKey, arguments_:
|
|
6
|
+
export const invokeMethod = (target: object, name: PropertyKey, arguments_: readonly unknown[]): unknown => {
|
|
7
7
|
const method: unknown = Reflect.get(target, name);
|
|
8
8
|
if (typeof method !== "function") {
|
|
9
9
|
throw new TypeError(`${String(name)} is not a method`);
|
package/src/internal/executor.ts
CHANGED
|
@@ -6,15 +6,11 @@ import type { Semaphore } from "effect";
|
|
|
6
6
|
export type AnySqlContract = PrismaContract<SqlStorage>;
|
|
7
7
|
export type AnyPostgresContract = AnySqlContract;
|
|
8
8
|
|
|
9
|
-
/**
|
|
10
|
-
* The subset of a Prisma Next client the package depends on. Both the
|
|
11
|
-
* PostgreSQL and the SQLite client satisfy it.
|
|
12
|
-
*/
|
|
13
9
|
export interface SqlDatabaseClient<Contract extends AnySqlContract> {
|
|
14
|
-
|
|
10
|
+
close: () => Promise<void>;
|
|
15
11
|
readonly context: ExecutionContext<Contract>;
|
|
16
|
-
|
|
17
|
-
|
|
12
|
+
readonly contract: Contract;
|
|
13
|
+
runtime: () => Runtime;
|
|
18
14
|
}
|
|
19
15
|
|
|
20
16
|
export interface DatabaseExecutor<Models extends object, Contract extends AnySqlContract = AnySqlContract> {
|
package/src/internal/promise.ts
CHANGED
|
@@ -3,6 +3,6 @@ import { isPrismaFailure, type PrismaError, toPrismaError } from "../error.ts";
|
|
|
3
3
|
|
|
4
4
|
export const fromPrismaPromise = <A>(evaluate: (signal: AbortSignal) => PromiseLike<A>): Effect.Effect<A, PrismaError> =>
|
|
5
5
|
Effect.tryPromise({
|
|
6
|
-
try: (signal) => Promise.resolve(evaluate(signal)),
|
|
7
6
|
catch: (error) => error,
|
|
7
|
+
try: (signal) => Promise.resolve(evaluate(signal)),
|
|
8
8
|
}).pipe(Effect.catch((error) => (isPrismaFailure(error) ? Effect.fail(toPrismaError(error)) : Effect.die(error))));
|
package/src/internal/recipe.ts
CHANGED
|
@@ -2,29 +2,29 @@ import { hasMethod, invokeMethod } from "./dynamic.ts";
|
|
|
2
2
|
import { getRelationPlan, type RelationPlan } from "./relation-plan.ts";
|
|
3
3
|
|
|
4
4
|
export interface RelationOperation {
|
|
5
|
+
readonly arguments: readonly unknown[];
|
|
5
6
|
readonly name: PropertyKey;
|
|
6
|
-
readonly arguments: ReadonlyArray<unknown>;
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
export interface RelationRecipe {
|
|
10
10
|
readonly model: string;
|
|
11
|
-
readonly parent?: RelationRecipe;
|
|
12
11
|
readonly operation?: RelationOperation;
|
|
12
|
+
readonly parent?: RelationRecipe;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
export const rootRecipe = (model: string): RelationRecipe => ({ model });
|
|
16
16
|
|
|
17
|
-
export const appendOperation = (parent: RelationRecipe, name: PropertyKey, arguments_:
|
|
17
|
+
export const appendOperation = (parent: RelationRecipe, name: PropertyKey, arguments_: readonly unknown[]): RelationRecipe => ({
|
|
18
18
|
model: parent.model,
|
|
19
|
-
parent,
|
|
20
19
|
operation: {
|
|
21
|
-
name,
|
|
22
20
|
arguments: arguments_,
|
|
21
|
+
name,
|
|
23
22
|
},
|
|
23
|
+
parent,
|
|
24
24
|
});
|
|
25
25
|
|
|
26
|
-
const operations = (recipe: RelationRecipe):
|
|
27
|
-
const reversed:
|
|
26
|
+
const operations = (recipe: RelationRecipe): readonly RelationOperation[] => {
|
|
27
|
+
const reversed: RelationOperation[] = [];
|
|
28
28
|
let current: RelationRecipe | undefined = recipe;
|
|
29
29
|
|
|
30
30
|
while (current !== undefined) {
|
|
@@ -37,7 +37,7 @@ const operations = (recipe: RelationRecipe): ReadonlyArray<RelationOperation> =>
|
|
|
37
37
|
return reversed.reverse();
|
|
38
38
|
};
|
|
39
39
|
|
|
40
|
-
const applyMethod = (current: unknown, name: PropertyKey, arguments_:
|
|
40
|
+
const applyMethod = (current: unknown, name: PropertyKey, arguments_: readonly unknown[], model: string): unknown => {
|
|
41
41
|
if (!hasMethod(current, name)) {
|
|
42
42
|
throw new TypeError(`Cannot call ${String(name)} while replaying ${model}`);
|
|
43
43
|
}
|
|
@@ -10,7 +10,7 @@ const countOf = (result: unknown): unknown => (typeof result === "object" && res
|
|
|
10
10
|
|
|
11
11
|
export const evaluateResult = (value: unknown, terminal: PropertyKey | undefined): Effect.Effect<unknown, PrismaError> => {
|
|
12
12
|
if (terminal === "count" && hasMethod(value, "aggregate")) {
|
|
13
|
-
return settle(invokeMethod(value, "aggregate", [(summary: { count()
|
|
13
|
+
return settle(invokeMethod(value, "aggregate", [(summary: { count: () => unknown }) => ({ count: summary.count() })]), countOf);
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
if (terminal === "exists" && hasMethod(value, "first")) {
|
|
@@ -42,22 +42,22 @@ const relationEffect = (self: object): Effect.Effect<unknown, PrismaError> => {
|
|
|
42
42
|
),
|
|
43
43
|
).pipe(
|
|
44
44
|
Effect.withSpan(`prisma.${runtime.recipe.model}.${String(runtime.terminal ?? "all")}`, {
|
|
45
|
-
kind: "client",
|
|
46
45
|
attributes: {
|
|
47
|
-
"db.system": "postgresql",
|
|
48
46
|
"db.model": runtime.recipe.model,
|
|
49
47
|
"db.operation": String(runtime.terminal ?? "all"),
|
|
48
|
+
"db.system": "postgresql",
|
|
50
49
|
},
|
|
50
|
+
kind: "client",
|
|
51
51
|
}),
|
|
52
52
|
);
|
|
53
53
|
};
|
|
54
54
|
|
|
55
55
|
const RelationPrototype = {
|
|
56
56
|
...Effectable.Prototype<RelationValue>({
|
|
57
|
-
label: "EffectPrismaRelation",
|
|
58
57
|
evaluate() {
|
|
59
58
|
return relationEffect(this);
|
|
60
59
|
},
|
|
60
|
+
label: "EffectPrismaRelation",
|
|
61
61
|
}),
|
|
62
62
|
get stream(): Stream.Stream<unknown, PrismaError> {
|
|
63
63
|
const runtime = runtimeOf(this);
|
|
@@ -89,7 +89,7 @@ const makeRelationProxy = (runtime: AnyRelationRuntime): object => {
|
|
|
89
89
|
terminal: "count",
|
|
90
90
|
});
|
|
91
91
|
}
|
|
92
|
-
return (...arguments_:
|
|
92
|
+
return (...arguments_: readonly unknown[]) =>
|
|
93
93
|
makeRelationProxy({
|
|
94
94
|
...runtime,
|
|
95
95
|
recipe: appendOperation(runtime.recipe, property, arguments_),
|
|
@@ -6,9 +6,9 @@ import { fromPrismaPromise } from "./promise.ts";
|
|
|
6
6
|
import { executeQuery } from "./query-execution.ts";
|
|
7
7
|
import { type RelationRecipe, replayRecipe } from "./recipe.ts";
|
|
8
8
|
|
|
9
|
-
const collectRows = (iterable: AsyncIterable<unknown>): Effect.Effect<
|
|
9
|
+
const collectRows = (iterable: AsyncIterable<unknown>): Effect.Effect<unknown[], PrismaError> =>
|
|
10
10
|
fromPrismaPromise(async () => {
|
|
11
|
-
const rows:
|
|
11
|
+
const rows: unknown[] = [];
|
|
12
12
|
for await (const row of iterable) {
|
|
13
13
|
rows.push(row);
|
|
14
14
|
}
|
|
@@ -18,8 +18,8 @@ const collectRows = (iterable: AsyncIterable<unknown>): Effect.Effect<Array<unkn
|
|
|
18
18
|
export const makeRelationStream = <Models extends object, Contract extends AnyPostgresContract>(
|
|
19
19
|
resolveExecutor: Effect.Effect<DatabaseExecutor<Models, Contract>, PrismaError>,
|
|
20
20
|
recipe: RelationRecipe,
|
|
21
|
-
): Stream.Stream<unknown, PrismaError> =>
|
|
22
|
-
|
|
21
|
+
): Stream.Stream<unknown, PrismaError> =>
|
|
22
|
+
Stream.unwrap(
|
|
23
23
|
Effect.flatMap(resolveExecutor, (executor) =>
|
|
24
24
|
executeQuery(
|
|
25
25
|
executor,
|
|
@@ -43,4 +43,3 @@ export const makeRelationStream = <Models extends object, Contract extends AnyPo
|
|
|
43
43
|
),
|
|
44
44
|
),
|
|
45
45
|
);
|
|
46
|
-
};
|
|
@@ -8,44 +8,23 @@ type CodecJson = Parameters<ContractCodec["decodeJson"]>[0];
|
|
|
8
8
|
|
|
9
9
|
const sqliteDatetimeCodecId = "sqlite/datetime@1";
|
|
10
10
|
|
|
11
|
-
const zonelessDatetime = /^(\d{4}-\d{2}-\d{2})[T ](\d{2}:\d{2}(?::\d{2})?(?:\.\d+)?)
|
|
11
|
+
const zonelessDatetime = /^(\d{4}-\d{2}-\d{2})[T ](\d{2}:\d{2}(?::\d{2})?(?:\.\d+)?)$/u;
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
*
|
|
16
|
-
* SQLite stores `DateTime` as text and computes `datetime('now')` — the default
|
|
17
|
-
* `prisma-next db init` generates — in UTC, but writes it as
|
|
18
|
-
* `YYYY-MM-DD HH:MM:SS` with no zone. `new Date` reads that form as local time,
|
|
19
|
-
* so decoding it unchanged shifts the instant by the process' UTC offset.
|
|
20
|
-
* Values that already carry `Z` or a numeric offset, date-only values (which
|
|
21
|
-
* `new Date` already reads as UTC), and anything that is not a datetime string
|
|
22
|
-
* are returned unchanged.
|
|
23
|
-
*/
|
|
13
|
+
// SQLite computes `datetime('now')`, the default `prisma-next db init` generates, in UTC but writes it as `YYYY-MM-DD HH:MM:SS`
|
|
14
|
+
// with no zone, and `new Date` reads that form as local time. Values with a zone and date-only values already read as UTC.
|
|
24
15
|
export const normalizeSqliteDatetime = (value: string): string => value.replace(zonelessDatetime, "$1T$2Z");
|
|
25
16
|
|
|
26
17
|
const decodeAsUtc = (codec: ContractCodec): ContractCodec => ({
|
|
27
|
-
id: sqliteDatetimeCodecId,
|
|
28
|
-
encode: (value, context) => codec.encode(value, context),
|
|
29
18
|
decode: (wire: CodecWire, context) => codec.decode(typeof wire === "string" ? normalizeSqliteDatetime(wire) : wire, context),
|
|
30
|
-
encodeJson: (value) => codec.encodeJson(value),
|
|
31
19
|
decodeJson: (json: CodecJson) => codec.decodeJson(typeof json === "string" ? normalizeSqliteDatetime(json) : json),
|
|
20
|
+
encode: (value, context) => codec.encode(value, context),
|
|
21
|
+
encodeJson: (value) => codec.encodeJson(value),
|
|
22
|
+
id: sqliteDatetimeCodecId,
|
|
32
23
|
});
|
|
33
24
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
*
|
|
38
|
-
* The registry is the single resolution point for both row decoding and the
|
|
39
|
-
* JSON decoding of included relations, so wrapping it covers every read path
|
|
40
|
-
* without touching encoding. Prisma Next rejects a second descriptor for an
|
|
41
|
-
* already-registered codec id, so an extension pack cannot replace the codec
|
|
42
|
-
* itself. Must run before the first query, because the runtime reads the
|
|
43
|
-
* registry off the context when it is created.
|
|
44
|
-
*
|
|
45
|
-
* Codecs are identified by the resolved reference rather than by `codec.id`:
|
|
46
|
-
* Prisma Next materializes a codec through an unbound descriptor factory, so
|
|
47
|
-
* the instance's `id` accessor throws.
|
|
48
|
-
*/
|
|
25
|
+
// The registry resolves codecs for rows and included relations alike, and Prisma Next rejects a second descriptor for a registered
|
|
26
|
+
// codec id, so wrapping the registry is the one way to cover every read. It must run before the first query, which reads the
|
|
27
|
+
// registry off the context. Codecs are matched by reference because a materialized codec's `id` accessor throws.
|
|
49
28
|
export const decodeSqliteDatetimesAsUtc = (context: ExecutionContext<AnySqlContract>): void => {
|
|
50
29
|
const registry = context.contractCodecs;
|
|
51
30
|
const descriptors = context.codecDescriptors;
|
|
@@ -62,6 +41,10 @@ export const decodeSqliteDatetimesAsUtc = (context: ExecutionContext<AnySqlContr
|
|
|
62
41
|
};
|
|
63
42
|
|
|
64
43
|
const utcRegistry: CodecRegistry = {
|
|
44
|
+
forCodecRef: (reference) => {
|
|
45
|
+
const codec = registry.forCodecRef(reference);
|
|
46
|
+
return reference.codecId === sqliteDatetimeCodecId ? wrap(codec) : codec;
|
|
47
|
+
},
|
|
65
48
|
forColumn: (namespaceId, table, column) => {
|
|
66
49
|
const codec = registry.forColumn(namespaceId, table, column);
|
|
67
50
|
if (codec === undefined) {
|
|
@@ -70,10 +53,6 @@ export const decodeSqliteDatetimesAsUtc = (context: ExecutionContext<AnySqlContr
|
|
|
70
53
|
const reference = descriptors.codecRefForColumn(namespaceId, table, column);
|
|
71
54
|
return reference?.codecId === sqliteDatetimeCodecId ? wrap(codec) : codec;
|
|
72
55
|
},
|
|
73
|
-
forCodecRef: (reference) => {
|
|
74
|
-
const codec = registry.forCodecRef(reference);
|
|
75
|
-
return reference.codecId === sqliteDatetimeCodecId ? wrap(codec) : codec;
|
|
76
|
-
},
|
|
77
56
|
};
|
|
78
57
|
|
|
79
58
|
if (!Reflect.set(context, "contractCodecs", utcRegistry)) {
|
|
@@ -2,11 +2,7 @@ import { DatabaseSync } from "node:sqlite";
|
|
|
2
2
|
|
|
3
3
|
export const MEMORY_PATH = ":memory:";
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
* Prisma Next's SQLite driver opens a fresh `node:sqlite` connection for every
|
|
7
|
-
* `RuntimeConnection`, so an in-memory database would give each transaction its
|
|
8
|
-
* own empty database.
|
|
9
|
-
*/
|
|
5
|
+
// Prisma Next's SQLite driver opens a fresh `node:sqlite` connection per `RuntimeConnection`, so each transaction would get its own empty in-memory database.
|
|
10
6
|
export const assertFileBackedPath = (path: string): void => {
|
|
11
7
|
if (path === MEMORY_PATH || path.trim().length === 0) {
|
|
12
8
|
throw new TypeError(
|
|
@@ -15,14 +11,8 @@ export const assertFileBackedPath = (path: string): void => {
|
|
|
15
11
|
}
|
|
16
12
|
};
|
|
17
13
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
*
|
|
21
|
-
* The driver already sets `foreign_keys` and `busy_timeout` on every connection
|
|
22
|
-
* it opens, but it has no hook for anything else, so durable pragmas such as
|
|
23
|
-
* `journal_mode` are applied once against the database file itself.
|
|
24
|
-
*/
|
|
25
|
-
export const applySqlitePragmas = (path: string, pragmas: ReadonlyArray<string>): void => {
|
|
14
|
+
// The driver has no hook for pragmas beyond `foreign_keys` and `busy_timeout`, so durable ones such as `journal_mode` go to the file once.
|
|
15
|
+
export const applySqlitePragmas = (path: string, pragmas: readonly string[]): void => {
|
|
26
16
|
if (pragmas.length === 0) {
|
|
27
17
|
return;
|
|
28
18
|
}
|
|
@@ -14,10 +14,10 @@ export const withTransactionSemaphore = <A, E, R>(
|
|
|
14
14
|
|
|
15
15
|
export interface TransactionResource<Models extends object, Contract extends AnySqlContract> {
|
|
16
16
|
readonly connection: SettledConnection;
|
|
17
|
-
readonly transaction: SettledTransaction;
|
|
18
17
|
readonly executor: DatabaseExecutor<Models, Contract> & {
|
|
19
18
|
readonly querySemaphore: Semaphore.Semaphore;
|
|
20
19
|
};
|
|
20
|
+
readonly transaction: SettledTransaction;
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
export const acquireTransaction = <Models extends object, Contract extends AnySqlContract>(
|
|
@@ -31,13 +31,12 @@ export const acquireTransaction = <Models extends object, Contract extends AnySq
|
|
|
31
31
|
try {
|
|
32
32
|
const transaction = await connection.transaction();
|
|
33
33
|
const transactionOrm = orm({
|
|
34
|
-
runtime: transaction,
|
|
35
34
|
context: current.client.context,
|
|
35
|
+
runtime: transaction,
|
|
36
36
|
});
|
|
37
37
|
|
|
38
38
|
return {
|
|
39
39
|
connection,
|
|
40
|
-
transaction,
|
|
41
40
|
executor: {
|
|
42
41
|
client: current.client,
|
|
43
42
|
identity: current.identity,
|
|
@@ -51,6 +50,7 @@ export const acquireTransaction = <Models extends object, Contract extends AnySq
|
|
|
51
50
|
transactionIdentity: {},
|
|
52
51
|
transactionSemaphore: current.transactionSemaphore,
|
|
53
52
|
},
|
|
53
|
+
transaction,
|
|
54
54
|
};
|
|
55
55
|
} catch (error) {
|
|
56
56
|
await connection.destroy(error).catch(() => undefined);
|
|
@@ -62,7 +62,7 @@ type AnyNullableField<Fields, Names extends readonly string[]> = Names extends r
|
|
|
62
62
|
|
|
63
63
|
export type IncludedRelationValue<Contract extends AnyPostgresContract, Model extends string, RelationName extends string, Value> =
|
|
64
64
|
RelationCardinalityOf<Contract, Model, RelationName> extends "1:N" | "N:M"
|
|
65
|
-
?
|
|
65
|
+
? Value[]
|
|
66
66
|
: RelationCardinalityOf<Contract, Model, RelationName> extends "N:1"
|
|
67
67
|
? AnyNullableField<ModelFields<Contract, Model>, RelationLocalFields<Contract, Model, RelationName>> extends true
|
|
68
68
|
? Value | null
|