@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.
Files changed (95) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/dist/bin/normalize-contract.js +1 -0
  3. package/dist/bin/normalize-contract.js.map +1 -1
  4. package/dist/error.js +4 -4
  5. package/dist/error.js.map +1 -1
  6. package/dist/internal/adapters/transaction-settlement.d.ts.map +1 -1
  7. package/dist/internal/adapters/transaction-settlement.js +1 -1
  8. package/dist/internal/adapters/transaction-settlement.js.map +1 -1
  9. package/dist/internal/client-lifecycle.d.ts +2 -2
  10. package/dist/internal/client-lifecycle.d.ts.map +1 -1
  11. package/dist/internal/client-lifecycle.js.map +1 -1
  12. package/dist/internal/contract-normalization.d.ts +0 -4
  13. package/dist/internal/contract-normalization.d.ts.map +1 -1
  14. package/dist/internal/contract-normalization.js +4 -7
  15. package/dist/internal/contract-normalization.js.map +1 -1
  16. package/dist/internal/database-facade.d.ts +0 -5
  17. package/dist/internal/database-facade.d.ts.map +1 -1
  18. package/dist/internal/database-facade.js +2 -7
  19. package/dist/internal/database-facade.js.map +1 -1
  20. package/dist/internal/database-factory.d.ts +0 -4
  21. package/dist/internal/database-factory.d.ts.map +1 -1
  22. package/dist/internal/database-factory.js +1 -7
  23. package/dist/internal/database-factory.js.map +1 -1
  24. package/dist/internal/database-types.d.ts +2 -6
  25. package/dist/internal/database-types.d.ts.map +1 -1
  26. package/dist/internal/dynamic.d.ts +1 -1
  27. package/dist/internal/dynamic.d.ts.map +1 -1
  28. package/dist/internal/dynamic.js.map +1 -1
  29. package/dist/internal/executor.d.ts +3 -7
  30. package/dist/internal/executor.d.ts.map +1 -1
  31. package/dist/internal/promise.js +1 -1
  32. package/dist/internal/promise.js.map +1 -1
  33. package/dist/internal/recipe.d.ts +3 -3
  34. package/dist/internal/recipe.d.ts.map +1 -1
  35. package/dist/internal/recipe.js +2 -2
  36. package/dist/internal/recipe.js.map +1 -1
  37. package/dist/internal/relation-result.js.map +1 -1
  38. package/dist/internal/relation-runtime.js +3 -3
  39. package/dist/internal/relation-runtime.js.map +1 -1
  40. package/dist/internal/relation-stream.d.ts.map +1 -1
  41. package/dist/internal/relation-stream.js +12 -14
  42. package/dist/internal/relation-stream.js.map +1 -1
  43. package/dist/internal/sqlite-datetime.d.ts +0 -26
  44. package/dist/internal/sqlite-datetime.d.ts.map +1 -1
  45. package/dist/internal/sqlite-datetime.js +13 -34
  46. package/dist/internal/sqlite-datetime.js.map +1 -1
  47. package/dist/internal/sqlite-pragmas.d.ts +1 -13
  48. package/dist/internal/sqlite-pragmas.d.ts.map +1 -1
  49. package/dist/internal/sqlite-pragmas.js +2 -12
  50. package/dist/internal/sqlite-pragmas.js.map +1 -1
  51. package/dist/internal/transaction.d.ts +1 -1
  52. package/dist/internal/transaction.d.ts.map +1 -1
  53. package/dist/internal/transaction.js +2 -2
  54. package/dist/internal/transaction.js.map +1 -1
  55. package/dist/relation/include-metadata.d.ts +1 -1
  56. package/dist/relation/include-metadata.d.ts.map +1 -1
  57. package/dist/relation/include.d.ts +6 -3
  58. package/dist/relation/include.d.ts.map +1 -1
  59. package/dist/relation/prisma-methods.d.ts +14 -14
  60. package/dist/relation/prisma-methods.d.ts.map +1 -1
  61. package/dist/relation.d.ts +2 -2
  62. package/dist/relation.d.ts.map +1 -1
  63. package/dist/sqlite.d.ts +2 -7
  64. package/dist/sqlite.d.ts.map +1 -1
  65. package/dist/sqlite.js.map +1 -1
  66. package/dist/testing/types.d.ts +5 -5
  67. package/dist/testing/types.d.ts.map +1 -1
  68. package/dist/testing/vitest.d.ts.map +1 -1
  69. package/dist/testing/vitest.js.map +1 -1
  70. package/package.json +34 -36
  71. package/src/bin/normalize-contract.ts +1 -0
  72. package/src/error.ts +4 -4
  73. package/src/internal/adapters/transaction-settlement.ts +4 -1
  74. package/src/internal/client-lifecycle.ts +2 -2
  75. package/src/internal/contract-normalization.ts +4 -7
  76. package/src/internal/database-facade.ts +2 -7
  77. package/src/internal/database-factory.ts +4 -8
  78. package/src/internal/database-types.ts +3 -7
  79. package/src/internal/dynamic.ts +1 -1
  80. package/src/internal/executor.ts +3 -7
  81. package/src/internal/promise.ts +1 -1
  82. package/src/internal/recipe.ts +8 -8
  83. package/src/internal/relation-result.ts +1 -1
  84. package/src/internal/relation-runtime.ts +4 -4
  85. package/src/internal/relation-stream.ts +4 -5
  86. package/src/internal/sqlite-datetime.ts +13 -34
  87. package/src/internal/sqlite-pragmas.ts +3 -13
  88. package/src/internal/transaction.ts +3 -3
  89. package/src/relation/include-metadata.ts +1 -1
  90. package/src/relation/include.ts +35 -33
  91. package/src/relation/prisma-methods.ts +19 -17
  92. package/src/relation.ts +2 -2
  93. package/src/sqlite.ts +2 -7
  94. package/src/testing/types.ts +6 -6
  95. 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.6.4",
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
- "engines": {
17
- "node": ">=24"
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
- "bin": {
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
- "publishConfig": {
51
- "access": "public",
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
- "check": "biome check .",
90
+ "ready": "pnpm typecheck && pnpm test && pnpm build",
90
91
  "test": "vitest run",
91
- "test:package": "node scripts/test-package.mjs",
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
  }
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  import { readFile, writeFile } from "node:fs/promises";
4
+ import process from "node:process";
4
5
  import { normalizePrismaNextContractTypes } from "../internal/contract-normalization.ts";
5
6
 
6
7
  const arguments_ = process.argv.slice(2);
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(private readonly connection: SettledConnection) {}
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: ReadonlyArray<string>): void => {
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)>/g;
2
- const timestampCodecReference = /\bCodecTypes\['pg\/(?:timestamp|timestamptz)@1'\]\['(?:input|output)'\]/g;
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 [name, namespace] = only;
41
+ const [postgresSchemaOrSqliteUnbound, namespace] = only;
47
42
  assertAvailableModelNames(Object.keys(namespace.models));
48
- return contractModels<Models>(Reflect.get(namespacedOrm, name));
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
- return Effect.flatMap(executorOf(facade), (current) =>
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
- ): Effect.Effect<A, E | PrismaError, Exclude<R, DatabaseIdentifier<Contract, Identifier>>>;
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_: ReadonlyArray<never>) => Layer.Any;
57
+ readonly layer: (...arguments_: readonly never[]) => Layer.Any;
62
58
  };
63
59
 
64
60
  export type DatabaseServiceOf<Database extends AnyDatabase> = Effect.Success<Database>;
@@ -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_: ReadonlyArray<unknown>): unknown => {
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`);
@@ -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
- readonly contract: Contract;
10
+ close: () => Promise<void>;
15
11
  readonly context: ExecutionContext<Contract>;
16
- runtime(): Runtime;
17
- close(): Promise<void>;
12
+ readonly contract: Contract;
13
+ runtime: () => Runtime;
18
14
  }
19
15
 
20
16
  export interface DatabaseExecutor<Models extends object, Contract extends AnySqlContract = AnySqlContract> {
@@ -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))));
@@ -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_: ReadonlyArray<unknown>): RelationRecipe => ({
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): ReadonlyArray<RelationOperation> => {
27
- const reversed: Array<RelationOperation> = [];
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_: ReadonlyArray<unknown>, model: string): unknown => {
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(): unknown }) => ({ count: summary.count() })]), countOf);
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_: ReadonlyArray<unknown>) =>
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<Array<unknown>, PrismaError> =>
9
+ const collectRows = (iterable: AsyncIterable<unknown>): Effect.Effect<unknown[], PrismaError> =>
10
10
  fromPrismaPromise(async () => {
11
- const rows: Array<unknown> = [];
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
- return Stream.unwrap(
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
- * Stamp `Z` onto a SQLite datetime string that carries no zone designator.
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
- * Replace the client's codec registry with one that decodes zone-less
36
- * `sqlite/datetime@1` values as UTC.
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
- * Apply connect-time pragmas.
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
- ? Array<Value>
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