@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.
Files changed (81) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +198 -155
  3. package/dist/database.d.ts +5 -5
  4. package/dist/database.d.ts.map +1 -1
  5. package/dist/database.js +12 -8
  6. package/dist/database.js.map +1 -1
  7. package/dist/databaseTypes.d.ts +17 -17
  8. package/dist/databaseTypes.d.ts.map +1 -1
  9. package/dist/error.d.ts.map +1 -1
  10. package/dist/internal/adapters/transaction-settlement.d.ts.map +1 -1
  11. package/dist/internal/adapters/transaction-settlement.js +9 -7
  12. package/dist/internal/adapters/transaction-settlement.js.map +1 -1
  13. package/dist/internal/client-lifecycle.d.ts.map +1 -1
  14. package/dist/internal/contract-normalization.d.ts.map +1 -1
  15. package/dist/internal/database-facade.d.ts +11 -11
  16. package/dist/internal/database-facade.d.ts.map +1 -1
  17. package/dist/internal/database-facade.js.map +1 -1
  18. package/dist/internal/database-factory.d.ts +1 -1
  19. package/dist/internal/database-factory.d.ts.map +1 -1
  20. package/dist/internal/database-factory.js +2 -2
  21. package/dist/internal/database-factory.js.map +1 -1
  22. package/dist/internal/dynamic.d.ts.map +1 -1
  23. package/dist/internal/dynamic.js +3 -1
  24. package/dist/internal/dynamic.js.map +1 -1
  25. package/dist/internal/executor.d.ts +6 -6
  26. package/dist/internal/executor.d.ts.map +1 -1
  27. package/dist/internal/promise.d.ts.map +1 -1
  28. package/dist/internal/query-execution.d.ts.map +1 -1
  29. package/dist/internal/recipe.d.ts.map +1 -1
  30. package/dist/internal/recipe.js +10 -10
  31. package/dist/internal/recipe.js.map +1 -1
  32. package/dist/internal/relation-plan.d.ts.map +1 -1
  33. package/dist/internal/relation-result.d.ts.map +1 -1
  34. package/dist/internal/relation-result.js +6 -2
  35. package/dist/internal/relation-result.js.map +1 -1
  36. package/dist/internal/relation-runtime.d.ts +1 -1
  37. package/dist/internal/relation-runtime.d.ts.map +1 -1
  38. package/dist/internal/relation-runtime.js +11 -6
  39. package/dist/internal/relation-runtime.js.map +1 -1
  40. package/dist/internal/relation-stream.d.ts +1 -1
  41. package/dist/internal/relation-stream.d.ts.map +1 -1
  42. package/dist/internal/relation-stream.js +9 -7
  43. package/dist/internal/relation-stream.js.map +1 -1
  44. package/dist/internal/sqlite-datetime.d.ts.map +1 -1
  45. package/dist/internal/sqlite-datetime.js +11 -9
  46. package/dist/internal/sqlite-datetime.js.map +1 -1
  47. package/dist/internal/sqlite-pragmas.d.ts.map +1 -1
  48. package/dist/internal/testing.d.ts +5 -5
  49. package/dist/internal/testing.d.ts.map +1 -1
  50. package/dist/internal/testing.js.map +1 -1
  51. package/dist/internal/transaction.d.ts +6 -6
  52. package/dist/internal/transaction.d.ts.map +1 -1
  53. package/dist/internal/transaction.js +5 -3
  54. package/dist/internal/transaction.js.map +1 -1
  55. package/dist/relation/include.d.ts +20 -20
  56. package/dist/relation/include.d.ts.map +1 -1
  57. package/dist/relation.d.ts +23 -23
  58. package/dist/relation.d.ts.map +1 -1
  59. package/dist/sqlite.d.ts.map +1 -1
  60. package/dist/sqlite.js +8 -6
  61. package/dist/sqlite.js.map +1 -1
  62. package/docs/north-star.md +47 -0
  63. package/docs/roadmap.md +32 -0
  64. package/package.json +27 -23
  65. package/src/database.ts +29 -23
  66. package/src/databaseTypes.ts +31 -31
  67. package/src/internal/adapters/transaction-settlement.ts +9 -8
  68. package/src/internal/database-facade.ts +27 -27
  69. package/src/internal/database-factory.ts +12 -12
  70. package/src/internal/dynamic.ts +3 -1
  71. package/src/internal/executor.ts +6 -6
  72. package/src/internal/recipe.ts +10 -10
  73. package/src/internal/relation-result.ts +6 -3
  74. package/src/internal/relation-runtime.ts +25 -20
  75. package/src/internal/relation-stream.ts +5 -4
  76. package/src/internal/sqlite-datetime.ts +11 -9
  77. package/src/internal/testing.ts +8 -8
  78. package/src/internal/transaction.ts +16 -15
  79. package/src/relation/include.ts +48 -48
  80. package/src/relation.ts +38 -38
  81. 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<Contract extends AnySqlContract> =
15
+ type DatabaseFactoryOptions<TContract extends AnySqlContract> =
16
16
  | {
17
- readonly contract: 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<Contract extends AnySqlContract, Identifier extends string> extends DatabaseServiceHolder<Contract, Identifier> {
26
- readonly layer: (options: DatabaseLayerOptions) => Layer.Layer<DatabaseIdentifier<Contract, Identifier>, PrismaError>;
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
- const clientOptions = ({ url, extensions, middleware, poolOptions, verifyMarker }: DatabaseLayerOptions) => ({
30
- url: typeof url === "string" ? url : Redacted.value(url),
31
- ...(extensions === undefined ? {} : { extensions }),
32
- ...(middleware === undefined ? {} : { middleware }),
33
- ...(poolOptions === undefined ? {} : { poolOptions }),
34
- ...(verifyMarker === undefined ? {} : { verifyMarker }),
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
- const defaultModels = <Contract extends AnySqlContract, Models extends object>(client: Pick<PostgresClient<Contract>, "contract" | "orm">): Models =>
38
- namespaceModels<Contract, Models>(client.contract, client.orm);
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 Contract extends AnySqlContract>() =>
42
- <const Identifier extends string>(
43
- identifier: DatabaseIdentifierLiteral<Identifier>,
44
- options: DatabaseFactoryOptions<Contract>,
45
- ): DatabaseDefinition<Contract, Identifier> => {
46
- type Models = DefaultModels<Contract>;
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<Contract, Identifier, DatabaseLayerOptions>(identifier, (layerOptions) => {
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<Contract>({
58
+ ? postgres<TContract>({
53
59
  ...forwarded,
54
60
  contractJson: options.contractJson,
55
61
  })
56
- : postgres<Contract>({
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<Contract, Models>(client),
76
+ models: defaultModels<TContract, Models>(client),
71
77
  querySemaphore: undefined,
72
78
  transactionIdentity: undefined,
73
79
  transactionSemaphore: undefined,
@@ -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<Contract extends AnySqlContract> = ReturnType<typeof orm<Contract>>;
8
- type DefaultNamespaceId<Contract extends AnySqlContract> = "__unbound__" extends keyof OrmFor<Contract>
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<Contract>
10
+ : "public" extends keyof OrmFor<TContract>
11
11
  ? "public"
12
- : keyof OrmFor<Contract>;
13
- export type DefaultModels<Contract extends AnySqlContract> = OrmFor<Contract>[DefaultNamespaceId<Contract>] extends infer Models extends object
14
- ? Models
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<Value, Whole = Value> = Value extends unknown ? ([Whole] extends [Value] ? false : true) : never;
17
+ type IsUnion<TValue, TWhole = TValue> = TValue extends unknown ? ([TWhole] extends [TValue] ? false : true) : never;
18
18
 
19
- type IsSingletonString<Value extends string> = Record<never, never> extends Record<Value, never> ? false : true;
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<Identifier extends string> = [Identifier] extends [`\0@shivaedev/effect-prisma/internal/${string}`]
23
+ export type DatabaseIdentifierLiteral<TIdentifier extends string> = [TIdentifier] extends [`\0@shivaedev/effect-prisma/internal/${string}`]
24
24
  ? never
25
- : string extends Identifier
25
+ : string extends TIdentifier
26
26
  ? never
27
- : true extends IsUnion<Identifier>
27
+ : true extends IsUnion<TIdentifier>
28
28
  ? never
29
- : IsSingletonString<Identifier> extends true
30
- ? Identifier
29
+ : IsSingletonString<TIdentifier> extends true
30
+ ? TIdentifier
31
31
  : never;
32
32
 
33
- export interface DatabaseIdentifier<Contract extends AnySqlContract, Identifier extends string> {
34
- readonly _contract: (contract: Contract) => Contract;
35
- readonly _databaseIdentifier: (identifier: Identifier) => 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<Contract extends AnySqlContract, Identifier extends string> = {
39
- readonly [Model in keyof DefaultModels<Contract>]: Relation<
40
- DefaultModels<Contract>[Model],
41
- Contract,
42
- Model & string,
43
- DatabaseIdentifier<Contract, Identifier>
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<Contract extends AnySqlContract, Identifier extends string> = DatabaseModels<Contract, Identifier> & {
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<Contract, Identifier> extends R ? unknown : never),
50
- ) => Effect.Effect<A, E | PrismaError, Exclude<R, DatabaseIdentifier<Contract, Identifier>>>;
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<Contract extends AnySqlContract, Identifier extends string>
54
- extends Context.Service<DatabaseIdentifier<Contract, Identifier>, DatabaseService<Contract, Identifier>> {}
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<Database extends AnyDatabase> = Effect.Success<Database>;
60
+ export type DatabaseServiceOf<TDatabase extends AnyDatabase> = Effect.Success<TDatabase>;
61
61
 
62
- export interface SqlDatabase<Contract extends AnySqlContract, Identifier extends string, Options>
63
- extends DatabaseServiceHolder<Contract, Identifier> {
64
- readonly layer: (options: Options) => Layer.Layer<DatabaseIdentifier<Contract, Identifier>, PrismaError>;
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
- const runtimeFailure = (code: string, cause: unknown, details?: Readonly<Record<string, unknown>>): Error & { readonly code: string } =>
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
- const commit = async ({ transaction }: Settled, disposal: ConnectionDisposal): Promise<unknown> => {
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
- const rollback = async ({ transaction }: Settled, disposal: ConnectionDisposal): Promise<unknown> => {
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
- const release = async ({ connection }: Settled, disposal: ConnectionDisposal, failure: unknown): Promise<void> => {
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<Contract extends AnySqlContract, Identifier extends string> {
10
- readonly executor: DatabaseExecutor<DefaultModels<Contract>, Contract>;
11
- readonly facade: DatabaseService<Contract, Identifier>;
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<Contract extends AnySqlContract, Identifier extends string> {
15
- readonly activeTransaction: Context.Reference<ActiveTransaction<Contract, Identifier> | undefined>;
16
- readonly executors: WeakMap<DatabaseService<Contract, Identifier>, DatabaseExecutor<DefaultModels<Contract>, Contract>>;
17
- readonly service: Context.Service<DatabaseIdentifier<Contract, Identifier>, DatabaseService<Contract, Identifier>>;
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<Models extends object>(namespace: unknown): Models;
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<Contract extends AnySqlContract, Identifier extends string>(facade: object): DatabaseService<Contract, Identifier>;
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 = <Contract extends AnySqlContract, Models extends object>(
33
- contract: Contract,
34
- namespacedOrm: TransactionOrm<Contract>,
35
- ): Models => {
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<Models>(Reflect.get(namespacedOrm, postgresSchemaOrSqliteUnbound));
43
+ return contractModels<TModels>(Reflect.get(namespacedOrm, postgresSchemaOrSqliteUnbound));
44
44
  };
45
45
 
46
- export const runTransaction = <Contract extends AnySqlContract, Identifier extends string, A, E, R>(
47
- scope: FacadeScope<Contract, Identifier>,
48
- current: DatabaseExecutor<DefaultModels<Contract>, Contract>,
49
- currentFacade: DatabaseService<Contract, Identifier>,
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<Contract, Identifier>>> => {
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<Contract, DefaultModels<Contract>>(current.client.contract, transactionOrm),
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<Contract extends AnySqlContract, Identifier extends string>(
92
- scope: FacadeScope<Contract, Identifier>,
93
- current: DatabaseExecutor<DefaultModels<Contract>, Contract>,
94
- ): DatabaseService<Contract, Identifier> {
95
- let facade: DatabaseService<Contract, Identifier>;
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<Contract, Identifier> extends R ? unknown : never)) =>
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<Contract, Identifier>(
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 Contract extends AnySqlContract, const Identifier extends string, Options>(
19
- identifier: DatabaseIdentifierLiteral<Identifier>,
20
- acquireExecutor: (options: Options) => Effect.Effect<DatabaseExecutor<DefaultModels<Contract>, Contract>, PrismaError>,
21
- ): SqlDatabase<Contract, Identifier, Options> => {
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<Contract>;
26
- type DatabaseId = DatabaseIdentifier<Contract, Identifier>;
25
+ type Models = DefaultModels<TContract>;
26
+ type DatabaseId = DatabaseIdentifier<TContract, TIdentifier>;
27
27
 
28
- const Service = Context.Service<DatabaseId, DatabaseService<Contract, Identifier>>(identifier);
29
- const scope: FacadeScope<Contract, Identifier> = {
30
- activeTransaction: Context.Reference<ActiveTransaction<Contract, Identifier> | undefined>(
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
- const executorOf = (facade: DatabaseService<Contract, Identifier>): Effect.Effect<DatabaseExecutor<Models, Contract>> => {
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: Options): Layer.Layer<DatabaseId, PrismaError> => {
53
+ const layer = (layerOptions: TOptions): Layer.Layer<DatabaseId, PrismaError> => {
54
54
  const acquire = Effect.acquireRelease(
55
55
  Effect.suspend(() => acquireExecutor(layerOptions)),
56
56
  (executor) => {
@@ -1,4 +1,6 @@
1
- const isObjectLike = (value: unknown): value is object => (typeof value === "object" && value !== null) || typeof value === "function";
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";
@@ -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<Contract extends AnySqlContract> {
9
+ export interface SqlDatabaseClient<TContract extends AnySqlContract> {
10
10
  close: () => Promise<void>;
11
- readonly context: ExecutionContext<Contract>;
12
- readonly contract: Contract;
11
+ readonly context: ExecutionContext<TContract>;
12
+ readonly contract: TContract;
13
13
  runtime: () => Runtime;
14
14
  }
15
15
 
16
- export interface DatabaseExecutor<Models extends object, Contract extends AnySqlContract = AnySqlContract> {
17
- readonly client: SqlDatabaseClient<Contract>;
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: 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;
@@ -23,7 +23,7 @@ export const appendOperation = (parent: RelationRecipe, name: PropertyKey, argum
23
23
  parent,
24
24
  });
25
25
 
26
- const operations = (recipe: RelationRecipe): readonly RelationOperation[] => {
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
- const applyMethod = (current: unknown, name: PropertyKey, arguments_: readonly unknown[], model: string): unknown => {
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
- const replayPlan = (collection: unknown, plan: RelationPlan, owner: object, transactionIdentity: object | undefined): unknown => {
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
- const includeRefinement = (value: unknown, owner: object, transactionIdentity: object | undefined): ((collection: unknown) => unknown) => {
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
- const replayRecipeFrom = (root: unknown, recipe: RelationRecipe, owner: object, transactionIdentity: object | undefined): unknown => {
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
- const settle = (value: unknown, map: (result: unknown) => unknown): Effect.Effect<unknown, PrismaError> =>
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
- const countOf = (result: unknown): unknown => (typeof result === "object" && result !== null ? Reflect.get(result, "count") : undefined);
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<Models extends object, Contract extends AnyPostgresContract> {
12
- readonly executor: DatabaseExecutor<Models, Contract>;
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<Models, Contract>, PrismaError>;
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
- const runtimeOf = (self: object): AnyRelationRuntime => {
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
- const relationEffect = (self: object): Effect.Effect<unknown, PrismaError> => {
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
- const makeRelationProxy = (runtime: AnyRelationRuntime): object => {
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<Collection, Model extends string>(proxy: object): Relation<Collection, undefined, Model>;
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
- Collection,
122
- Models extends object,
123
- ExecutorContract extends AnyPostgresContract = AnyPostgresContract,
124
- Model extends string = string,
126
+ TCollection,
127
+ TModels extends object,
128
+ TExecutorContract extends AnyPostgresContract = AnyPostgresContract,
129
+ TModel extends string = string,
125
130
  >(
126
- executor: DatabaseExecutor<Models, ExecutorContract>,
127
- model: Model,
128
- resolveExecutor: Effect.Effect<DatabaseExecutor<Models, ExecutorContract>, PrismaError> = Effect.succeed(executor),
129
- ): Relation<Collection, undefined, Model> =>
130
- asRelation<Collection, Model>(
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),