@shivaedev/platform 0.5.2 → 0.6.1

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 (214) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/README.md +359 -106
  3. package/dist/errors/rejectedField.d.ts +7 -0
  4. package/dist/errors/rejectedField.d.ts.map +1 -0
  5. package/dist/errors/{rejected-field.js → rejectedField.js} +4 -2
  6. package/dist/errors/rejectedField.js.map +1 -0
  7. package/dist/errors/taxonomy.d.ts +10 -0
  8. package/dist/errors/taxonomy.d.ts.map +1 -1
  9. package/dist/errors/taxonomy.js +4 -0
  10. package/dist/errors/taxonomy.js.map +1 -1
  11. package/dist/rpc/identity.d.ts.map +1 -1
  12. package/dist/rpc/identity.js.map +1 -1
  13. package/dist/rpc/middleware.d.ts.map +1 -1
  14. package/dist/rpc/middleware.js.map +1 -1
  15. package/dist/server/appHost.d.ts +56 -0
  16. package/dist/server/appHost.d.ts.map +1 -0
  17. package/dist/server/appHost.js +55 -0
  18. package/dist/server/appHost.js.map +1 -0
  19. package/dist/server/better-auth/adapter.d.ts +10 -0
  20. package/dist/server/better-auth/adapter.d.ts.map +1 -0
  21. package/dist/{better-auth → server/better-auth}/adapter.js +26 -14
  22. package/dist/server/better-auth/adapter.js.map +1 -0
  23. package/dist/{better-auth → server/better-auth}/relation.d.ts +15 -15
  24. package/dist/server/better-auth/relation.d.ts.map +1 -0
  25. package/dist/{better-auth → server/better-auth}/relation.js +3 -3
  26. package/dist/server/better-auth/relation.js.map +1 -0
  27. package/dist/server/better-auth/rowAdapter.d.ts +8 -0
  28. package/dist/server/better-auth/rowAdapter.d.ts.map +1 -0
  29. package/dist/server/better-auth/rowAdapter.js +52 -0
  30. package/dist/server/better-auth/rowAdapter.js.map +1 -0
  31. package/dist/{better-auth → server/better-auth}/where.d.ts +2 -2
  32. package/dist/server/better-auth/where.d.ts.map +1 -0
  33. package/dist/{better-auth → server/better-auth}/where.js +23 -19
  34. package/dist/server/better-auth/where.js.map +1 -0
  35. package/dist/{node-http/subscription-signal.d.ts → server/node-http/subscriptionSignal.d.ts} +2 -2
  36. package/dist/server/node-http/subscriptionSignal.d.ts.map +1 -0
  37. package/dist/{node-http/subscription-signal.js → server/node-http/subscriptionSignal.js} +20 -13
  38. package/dist/server/node-http/subscriptionSignal.js.map +1 -0
  39. package/dist/server/rpc-server/adapters/betterAuthSessions.d.ts +4 -0
  40. package/dist/server/rpc-server/adapters/betterAuthSessions.d.ts.map +1 -0
  41. package/dist/server/rpc-server/adapters/betterAuthSessions.js +14 -0
  42. package/dist/server/rpc-server/adapters/betterAuthSessions.js.map +1 -0
  43. package/dist/server/rpc-server/boundary.d.ts +9 -0
  44. package/dist/server/rpc-server/boundary.d.ts.map +1 -0
  45. package/dist/server/rpc-server/boundary.js +18 -0
  46. package/dist/server/rpc-server/boundary.js.map +1 -0
  47. package/dist/server/rpc-server/local.d.ts +7 -0
  48. package/dist/server/rpc-server/local.d.ts.map +1 -0
  49. package/dist/server/rpc-server/local.js +66 -0
  50. package/dist/server/rpc-server/local.js.map +1 -0
  51. package/dist/{rpc-server → server/rpc-server}/origin.d.ts +2 -2
  52. package/dist/server/rpc-server/origin.d.ts.map +1 -0
  53. package/dist/server/rpc-server/origin.js +16 -0
  54. package/dist/server/rpc-server/origin.js.map +1 -0
  55. package/dist/server/rpc-server/redact.d.ts +3 -0
  56. package/dist/server/rpc-server/redact.d.ts.map +1 -0
  57. package/dist/{rpc-server → server/rpc-server}/redact.js +9 -7
  58. package/dist/server/rpc-server/redact.js.map +1 -0
  59. package/dist/server/rpc-server/redactCause.d.ts +6 -0
  60. package/dist/server/rpc-server/redactCause.d.ts.map +1 -0
  61. package/dist/server/rpc-server/redactCause.js +36 -0
  62. package/dist/server/rpc-server/redactCause.js.map +1 -0
  63. package/dist/server/rpc-server/sensitive.d.ts +5 -0
  64. package/dist/server/rpc-server/sensitive.d.ts.map +1 -0
  65. package/dist/{rpc-server → server/rpc-server}/sensitive.js +21 -17
  66. package/dist/server/rpc-server/sensitive.js.map +1 -0
  67. package/dist/server/rpc-server/session.d.ts +21 -0
  68. package/dist/server/rpc-server/session.d.ts.map +1 -0
  69. package/dist/server/rpc-server/session.js +37 -0
  70. package/dist/server/rpc-server/session.js.map +1 -0
  71. package/dist/{rpc-server → server/rpc-server}/tracing.d.ts +1 -1
  72. package/dist/server/rpc-server/tracing.d.ts.map +1 -0
  73. package/dist/server/rpc-server/tracing.js +42 -0
  74. package/dist/server/rpc-server/tracing.js.map +1 -0
  75. package/dist/server/rpc-server/transportHeaders.d.ts +4 -0
  76. package/dist/server/rpc-server/transportHeaders.d.ts.map +1 -0
  77. package/dist/server/rpc-server/transportHeaders.js +6 -0
  78. package/dist/server/rpc-server/transportHeaders.js.map +1 -0
  79. package/dist/server/runtime/makePlatform.d.ts +4 -0
  80. package/dist/server/runtime/makePlatform.d.ts.map +1 -0
  81. package/dist/{runtime/make.js → server/runtime/makePlatform.js} +10 -8
  82. package/dist/server/runtime/makePlatform.js.map +1 -0
  83. package/dist/server/runtime/types.d.ts +19 -0
  84. package/dist/server/runtime/types.d.ts.map +1 -0
  85. package/dist/{runtime → server/runtime}/types.js.map +1 -1
  86. package/dist/server/testing/makePlatformIt.d.ts +8 -0
  87. package/dist/server/testing/makePlatformIt.d.ts.map +1 -0
  88. package/dist/{testing/vitest.js → server/testing/makePlatformIt.js} +9 -5
  89. package/dist/server/testing/makePlatformIt.js.map +1 -0
  90. package/dist/server/testing/types.d.ts +28 -0
  91. package/dist/server/testing/types.d.ts.map +1 -0
  92. package/dist/{testing → server/testing}/types.js.map +1 -1
  93. package/docs/north-star.md +36 -0
  94. package/docs/roadmap.md +30 -0
  95. package/package.json +324 -54
  96. package/src/errors/{rejected-field.ts → rejectedField.ts} +3 -6
  97. package/src/errors/taxonomy.ts +2 -8
  98. package/src/rpc/identity.ts +0 -4
  99. package/src/rpc/middleware.ts +18 -9
  100. package/src/server/appHost.ts +134 -0
  101. package/src/{better-auth → server/better-auth}/adapter.ts +27 -31
  102. package/src/{better-auth → server/better-auth}/relation.ts +18 -20
  103. package/src/{better-auth/row-adapter.ts → server/better-auth/rowAdapter.ts} +19 -21
  104. package/src/{better-auth → server/better-auth}/where.ts +23 -33
  105. package/src/{node-http/subscription-signal.ts → server/node-http/subscriptionSignal.ts} +19 -22
  106. package/src/{rpc-server/adapters/better-auth-sessions.ts → server/rpc-server/adapters/betterAuthSessions.ts} +9 -10
  107. package/src/server/rpc-server/boundary.ts +26 -0
  108. package/src/server/rpc-server/local.ts +70 -0
  109. package/src/{rpc-server → server/rpc-server}/origin.ts +10 -13
  110. package/src/{rpc-server → server/rpc-server}/redact.ts +9 -11
  111. package/src/server/rpc-server/redactCause.ts +40 -0
  112. package/src/{rpc-server → server/rpc-server}/sensitive.ts +17 -22
  113. package/src/{rpc-server → server/rpc-server}/session.ts +22 -23
  114. package/src/{rpc-server → server/rpc-server}/tracing.ts +15 -18
  115. package/src/{rpc-server/transport.ts → server/rpc-server/transportHeaders.ts} +3 -3
  116. package/src/{runtime/make.ts → server/runtime/makePlatform.ts} +18 -26
  117. package/src/server/runtime/types.ts +24 -0
  118. package/src/server/testing/makePlatformIt.ts +76 -0
  119. package/src/server/testing/types.ts +38 -0
  120. package/dist/better-auth/adapter.d.ts +0 -10
  121. package/dist/better-auth/adapter.d.ts.map +0 -1
  122. package/dist/better-auth/adapter.js.map +0 -1
  123. package/dist/better-auth/relation.d.ts.map +0 -1
  124. package/dist/better-auth/relation.js.map +0 -1
  125. package/dist/better-auth/row-adapter.d.ts +0 -8
  126. package/dist/better-auth/row-adapter.d.ts.map +0 -1
  127. package/dist/better-auth/row-adapter.js +0 -46
  128. package/dist/better-auth/row-adapter.js.map +0 -1
  129. package/dist/better-auth/where.d.ts.map +0 -1
  130. package/dist/better-auth/where.js.map +0 -1
  131. package/dist/better-auth.d.ts +0 -2
  132. package/dist/better-auth.d.ts.map +0 -1
  133. package/dist/better-auth.js +0 -2
  134. package/dist/better-auth.js.map +0 -1
  135. package/dist/errors/rejected-field.d.ts +0 -7
  136. package/dist/errors/rejected-field.d.ts.map +0 -1
  137. package/dist/errors/rejected-field.js.map +0 -1
  138. package/dist/errors.d.ts +0 -3
  139. package/dist/errors.d.ts.map +0 -1
  140. package/dist/errors.js +0 -3
  141. package/dist/errors.js.map +0 -1
  142. package/dist/node-http/subscription-signal.d.ts.map +0 -1
  143. package/dist/node-http/subscription-signal.js.map +0 -1
  144. package/dist/node-http.d.ts +0 -2
  145. package/dist/node-http.d.ts.map +0 -1
  146. package/dist/node-http.js +0 -2
  147. package/dist/node-http.js.map +0 -1
  148. package/dist/rpc-server/adapters/better-auth-sessions.d.ts +0 -4
  149. package/dist/rpc-server/adapters/better-auth-sessions.d.ts.map +0 -1
  150. package/dist/rpc-server/adapters/better-auth-sessions.js +0 -11
  151. package/dist/rpc-server/adapters/better-auth-sessions.js.map +0 -1
  152. package/dist/rpc-server/origin.d.ts.map +0 -1
  153. package/dist/rpc-server/origin.js +0 -12
  154. package/dist/rpc-server/origin.js.map +0 -1
  155. package/dist/rpc-server/redact-cause.d.ts +0 -6
  156. package/dist/rpc-server/redact-cause.d.ts.map +0 -1
  157. package/dist/rpc-server/redact-cause.js +0 -28
  158. package/dist/rpc-server/redact-cause.js.map +0 -1
  159. package/dist/rpc-server/redact.d.ts +0 -3
  160. package/dist/rpc-server/redact.d.ts.map +0 -1
  161. package/dist/rpc-server/redact.js.map +0 -1
  162. package/dist/rpc-server/sensitive.d.ts +0 -5
  163. package/dist/rpc-server/sensitive.d.ts.map +0 -1
  164. package/dist/rpc-server/sensitive.js.map +0 -1
  165. package/dist/rpc-server/session.d.ts +0 -21
  166. package/dist/rpc-server/session.d.ts.map +0 -1
  167. package/dist/rpc-server/session.js +0 -31
  168. package/dist/rpc-server/session.js.map +0 -1
  169. package/dist/rpc-server/tracing.d.ts.map +0 -1
  170. package/dist/rpc-server/tracing.js +0 -34
  171. package/dist/rpc-server/tracing.js.map +0 -1
  172. package/dist/rpc-server/transport.d.ts +0 -4
  173. package/dist/rpc-server/transport.d.ts.map +0 -1
  174. package/dist/rpc-server/transport.js +0 -4
  175. package/dist/rpc-server/transport.js.map +0 -1
  176. package/dist/rpc-server.d.ts +0 -9
  177. package/dist/rpc-server.d.ts.map +0 -1
  178. package/dist/rpc-server.js +0 -9
  179. package/dist/rpc-server.js.map +0 -1
  180. package/dist/rpc.d.ts +0 -3
  181. package/dist/rpc.d.ts.map +0 -1
  182. package/dist/rpc.js +0 -3
  183. package/dist/rpc.js.map +0 -1
  184. package/dist/runtime/make.d.ts +0 -4
  185. package/dist/runtime/make.d.ts.map +0 -1
  186. package/dist/runtime/make.js.map +0 -1
  187. package/dist/runtime/types.d.ts +0 -19
  188. package/dist/runtime/types.d.ts.map +0 -1
  189. package/dist/runtime.d.ts +0 -3
  190. package/dist/runtime.d.ts.map +0 -1
  191. package/dist/runtime.js +0 -2
  192. package/dist/runtime.js.map +0 -1
  193. package/dist/testing/types.d.ts +0 -26
  194. package/dist/testing/types.d.ts.map +0 -1
  195. package/dist/testing/vitest.d.ts +0 -8
  196. package/dist/testing/vitest.d.ts.map +0 -1
  197. package/dist/testing/vitest.js.map +0 -1
  198. package/dist/testing.d.ts +0 -3
  199. package/dist/testing.d.ts.map +0 -1
  200. package/dist/testing.js +0 -2
  201. package/dist/testing.js.map +0 -1
  202. package/src/better-auth.ts +0 -4
  203. package/src/errors.ts +0 -2
  204. package/src/node-http.ts +0 -5
  205. package/src/rpc-server/redact-cause.ts +0 -41
  206. package/src/rpc-server.ts +0 -15
  207. package/src/rpc.ts +0 -2
  208. package/src/runtime/types.ts +0 -24
  209. package/src/runtime.ts +0 -7
  210. package/src/testing/types.ts +0 -41
  211. package/src/testing/vitest.ts +0 -69
  212. package/src/testing.ts +0 -8
  213. /package/dist/{runtime → server/runtime}/types.js +0 -0
  214. /package/dist/{testing → server/testing}/types.js +0 -0
@@ -1,41 +1,40 @@
1
1
  import type { BetterAuthOptions, DBAdapterInstance } from "better-auth";
2
2
  import { createAdapterFactory } from "better-auth/adapters";
3
3
  import { Effect } from "effect";
4
- import type { AnyDatabase } from "@shivaedev/effect-prisma";
4
+ import type { AnyDatabase } from "@shivaedev/effect-prisma/databaseTypes.ts";
5
5
  import type { PlatformRuntime } from "#runtime/types.ts";
6
6
  import { type DynamicRelation, refineRelation } from "./relation.ts";
7
- import { makeRowAdapter, type RelationQuery } from "./row-adapter.ts";
8
-
7
+ import { makeRowAdapter, type RelationQuery } from "./rowAdapter.ts";
9
8
  export interface EffectPrismaAdapterOptions {
10
9
  readonly debugLogs?: boolean;
11
10
  readonly modelName?: (model: string) => string;
12
11
  readonly usePlural?: boolean;
13
12
  }
14
-
15
13
  interface DynamicDatabase {
16
14
  readonly transaction: <A, E, R>(program: Effect.Effect<A, E, R>) => Effect.Effect<A, unknown, unknown>;
17
15
  }
18
-
19
- const isFunction = (value: unknown, key: string): boolean =>
20
- ((typeof value === "object" && value !== null) || typeof value === "function") && typeof Reflect.get(value, key) === "function";
21
-
22
- const isDynamicDatabase = (value: unknown): value is DynamicDatabase => isFunction(value, "transaction");
23
-
24
- const isDynamicRelation = (value: unknown): value is DynamicRelation<unknown> => isFunction(value, "where") && isFunction(value, "select");
25
-
26
- const modelRelation = (database: DynamicDatabase, model: string): DynamicRelation<unknown> => {
16
+ function isFunction(value: unknown, key: string): boolean {
17
+ return ((typeof value === "object" && value !== null) || typeof value === "function") && typeof Reflect.get(value, key) === "function";
18
+ }
19
+ function isDynamicDatabase(value: unknown): value is DynamicDatabase {
20
+ return isFunction(value, "transaction");
21
+ }
22
+ function isDynamicRelation(value: unknown): value is DynamicRelation<unknown> {
23
+ return isFunction(value, "where") && isFunction(value, "select");
24
+ }
25
+ function modelRelation(database: DynamicDatabase, model: string): DynamicRelation<unknown> {
27
26
  const relation: unknown = Reflect.get(database, model);
28
27
  if (!isDynamicRelation(relation)) {
29
28
  throw new TypeError(`Unknown database model: ${model}`);
30
29
  }
31
30
  return relation;
32
- };
33
-
34
- const defaultModelName = (model: string): string => (model.length === 0 ? model : `${model[0]?.toUpperCase()}${model.slice(1)}`);
35
-
36
- export function effectPrismaAdapter<Database extends AnyDatabase, Services, BuildError>(
37
- databaseTag: Database,
38
- runtime: PlatformRuntime<Services, BuildError>,
31
+ }
32
+ function defaultModelName(model: string): string {
33
+ return model.length === 0 ? model : `${model[0]?.toUpperCase()}${model.slice(1)}`;
34
+ }
35
+ export function effectPrismaAdapter<TDatabase extends AnyDatabase, TServices, TBuildError>(
36
+ databaseTag: TDatabase,
37
+ runtime: PlatformRuntime<TServices, TBuildError>,
39
38
  adapterOptions?: EffectPrismaAdapterOptions,
40
39
  ): DBAdapterInstance;
41
40
  export function effectPrismaAdapter(
@@ -48,21 +47,19 @@ export function effectPrismaAdapter(
48
47
  const databaseEffect = Effect.flatMap(databaseTag, (database) =>
49
48
  isDynamicDatabase(database) ? Effect.succeed(database) : Effect.die(new TypeError("The database service has no transaction")),
50
49
  );
51
-
52
- const run = <Value>(operation: (database: DynamicDatabase) => Effect.Effect<Value, unknown, unknown>): Promise<Value> =>
53
- runtime.runPromise(Effect.flatMap(databaseEffect, operation));
54
-
50
+ function run<TValue>(operation: (database: DynamicDatabase) => Effect.Effect<TValue, unknown, unknown>): Promise<TValue> {
51
+ return runtime.runPromise(Effect.flatMap(databaseEffect, operation));
52
+ }
55
53
  const query: RelationQuery = (model, refinement, use) =>
56
54
  run((database) => use(refineRelation(modelRelation(database, mapModelName(model)), refinement)));
57
-
58
- const inTransaction = <Value>(callback: () => Promise<Value>): Effect.Effect<Value, unknown, unknown> =>
59
- Effect.flatMap(Effect.context<unknown>(), (services) =>
55
+ function inTransaction<TValue>(callback: () => Promise<TValue>): Effect.Effect<TValue, unknown, unknown> {
56
+ return Effect.flatMap(Effect.context<unknown>(), (services) =>
60
57
  Effect.tryPromise({
61
58
  catch: (error) => error,
62
59
  try: () => runtime.runWithServices(services, callback),
63
60
  }),
64
61
  );
65
-
62
+ }
66
63
  let factory: ReturnType<typeof createAdapterFactory>;
67
64
  factory = createAdapterFactory({
68
65
  adapter: makeRowAdapter(query, adapterOptions.usePlural ?? false),
@@ -73,13 +70,12 @@ export function effectPrismaAdapter(
73
70
  supportsArrays: true,
74
71
  supportsBooleans: true,
75
72
  supportsDates: true,
76
- supportsJSON: true,
73
+ "supportsJSON": true,
77
74
  supportsNumericIds: true,
78
- supportsUUIDs: true,
75
+ "supportsUUIDs": true,
79
76
  transaction: (callback) => run((database) => database.transaction(inTransaction(() => callback(factory(authOptions))))),
80
77
  },
81
78
  });
82
-
83
79
  return factory(authOptions);
84
80
  };
85
81
  }
@@ -1,24 +1,22 @@
1
+ import { all } from "@prisma-next/sql-orm-client";
1
2
  import type { Effect, Option } from "effect";
2
- import { all } from "@shivaedev/effect-prisma";
3
3
  import { type CleanedWhere, type DynamicField, type Expression, whereExpression } from "./where.ts";
4
-
5
- export interface DynamicRelation<Requirements> extends Effect.Effect<readonly Record<string, unknown>[], unknown, Requirements> {
6
- count: () => Effect.Effect<number, unknown, Requirements>;
7
- create: (data: Record<string, unknown>) => Effect.Effect<Record<string, unknown>, unknown, Requirements>;
8
- delete: () => Effect.Effect<Record<string, unknown> | null, unknown, Requirements>;
9
- deleteAll: () => Effect.Effect<readonly Record<string, unknown>[], unknown, Requirements>;
10
- first: () => Effect.Effect<Option.Option<Record<string, unknown>>, unknown, Requirements>;
11
- orderBy: (ordering: (fields: Record<string, DynamicField>) => unknown) => DynamicRelation<Requirements>;
12
- select: (...fields: readonly string[]) => DynamicRelation<Requirements>;
13
- skip: (count: number) => DynamicRelation<Requirements>;
14
- take: (count: number) => DynamicRelation<Requirements>;
15
- update: (data: Record<string, unknown>) => Effect.Effect<Record<string, unknown> | null, unknown, Requirements>;
16
- updateAll: (data: Record<string, unknown>) => Effect.Effect<readonly Record<string, unknown>[], unknown, Requirements>;
17
- where: (predicate: (fields: Record<string, DynamicField>) => Expression) => DynamicRelation<Requirements>;
4
+ export interface DynamicRelation<TRequirements> extends Effect.Effect<readonly Record<string, unknown>[], unknown, TRequirements> {
5
+ count: () => Effect.Effect<number, unknown, TRequirements>;
6
+ create: (data: Record<string, unknown>) => Effect.Effect<Record<string, unknown>, unknown, TRequirements>;
7
+ delete: () => Effect.Effect<Record<string, unknown> | null, unknown, TRequirements>;
8
+ deleteAll: () => Effect.Effect<readonly Record<string, unknown>[], unknown, TRequirements>;
9
+ first: () => Effect.Effect<Option.Option<Record<string, unknown>>, unknown, TRequirements>;
10
+ orderBy: (ordering: (fields: Record<string, DynamicField>) => unknown) => DynamicRelation<TRequirements>;
11
+ select: (...fields: readonly string[]) => DynamicRelation<TRequirements>;
12
+ skip: (count: number) => DynamicRelation<TRequirements>;
13
+ take: (count: number) => DynamicRelation<TRequirements>;
14
+ update: (data: Record<string, unknown>) => Effect.Effect<Record<string, unknown> | null, unknown, TRequirements>;
15
+ updateAll: (data: Record<string, unknown>) => Effect.Effect<readonly Record<string, unknown>[], unknown, TRequirements>;
16
+ where: (predicate: (fields: Record<string, DynamicField>) => Expression) => DynamicRelation<TRequirements>;
18
17
  }
19
-
20
- export const refineRelation = <Requirements>(
21
- relation: DynamicRelation<Requirements>,
18
+ export function refineRelation<TRequirements>(
19
+ relation: DynamicRelation<TRequirements>,
22
20
  options: {
23
21
  readonly where?: readonly CleanedWhere[] | undefined;
24
22
  readonly select?: readonly string[] | undefined;
@@ -31,7 +29,7 @@ export const refineRelation = <Requirements>(
31
29
  }
32
30
  | undefined;
33
31
  },
34
- ): DynamicRelation<Requirements> => {
32
+ ): DynamicRelation<TRequirements> {
35
33
  let refined = relation;
36
34
  if (options.where !== undefined) {
37
35
  refined = refined.where((fields) => {
@@ -58,4 +56,4 @@ export const refineRelation = <Requirements>(
58
56
  refined = refined.select(...options.select);
59
57
  }
60
58
  return refined;
61
- };
59
+ }
@@ -9,43 +9,40 @@ type FindManyInput = Parameters<CustomAdapter["findMany"]>[0];
9
9
  type Row = Record<string, unknown>;
10
10
  type RowMethod = "create" | "findMany" | "findOne" | "update";
11
11
  type Refinement = Parameters<typeof refineRelation>[1];
12
-
13
12
  interface RowMethods {
14
13
  readonly create: (input: { readonly data: Row; readonly model: string; readonly select?: string[] | undefined }) => Promise<Row>;
15
14
  readonly findMany: (input: FindManyInput) => Promise<readonly Row[]>;
16
15
  readonly findOne: (input: FindOneInput) => Promise<Row | null>;
17
16
  readonly update: (input: { readonly model: string; readonly update: Row; readonly where: CleanedWhere[] }) => Promise<Row | null>;
18
17
  }
19
-
20
- export type RelationQuery = <Value>(
18
+ export type RelationQuery = <TValue>(
21
19
  model: string,
22
20
  refinement: Refinement,
23
- use: (relation: DynamicRelation<unknown>) => Effect.Effect<Value, unknown, unknown>,
24
- ) => Promise<Value>;
25
-
21
+ use: (relation: DynamicRelation<unknown>) => Effect.Effect<TValue, unknown, unknown>,
22
+ ) => Promise<TValue>;
26
23
  // Better Auth lets each call declare its row type; the rows are whatever its generated schema stored.
27
24
  function rowTyped(adapter: Omit<CustomAdapter, RowMethod> & RowMethods): CustomAdapter;
28
25
  function rowTyped(adapter: unknown): unknown {
29
26
  return adapter;
30
27
  }
31
-
32
- const rejectJoin = (join: unknown): void => {
28
+ function rejectJoin(join: unknown): void {
33
29
  if (join !== undefined) {
34
30
  throw new TypeError("The Effect Prisma Better Auth adapter does not support experimental native joins");
35
31
  }
36
- };
37
-
38
- const namesId = (condition: CleanedWhere): boolean => condition.connector !== "OR" && condition.field === "id" && condition.operator === "eq";
39
-
40
- export const makeRowAdapter =
41
- (query: RelationQuery, usePlural: boolean): AdapterFactoryCustomizeAdapterCreator =>
42
- ({ debugLog, getFieldAttributes }) => {
43
- const isUnique = (model: string, condition: CleanedWhere): boolean =>
44
- condition.connector !== "OR"
45
- && condition.operator === "eq"
46
- && condition.mode !== "insensitive"
47
- && (condition.field === "id" || getFieldAttributes({ field: condition.field, model }).unique === true);
48
-
32
+ }
33
+ function namesId(condition: CleanedWhere): boolean {
34
+ return condition.connector !== "OR" && condition.field === "id" && condition.operator === "eq";
35
+ }
36
+ export function makeRowAdapter(query: RelationQuery, usePlural: boolean): AdapterFactoryCustomizeAdapterCreator {
37
+ return ({ debugLog, getFieldAttributes }) => {
38
+ function isUnique(model: string, condition: CleanedWhere): boolean {
39
+ return (
40
+ condition.connector !== "OR"
41
+ && condition.operator === "eq"
42
+ && condition.mode !== "insensitive"
43
+ && (condition.field === "id" || getFieldAttributes({ field: condition.field, model }).unique === true)
44
+ );
45
+ }
49
46
  return rowTyped({
50
47
  count: ({ model, where }) => query(model, { where }, (relation) => relation.count()),
51
48
  create: ({ data, model, select }) => {
@@ -78,3 +75,4 @@ export const makeRowAdapter =
78
75
  updateMany: ({ model, update, where }) => query(model, { where }, (relation) => Effect.map(relation.updateAll(update), (rows) => rows.length)),
79
76
  });
80
77
  };
78
+ }
@@ -1,9 +1,7 @@
1
+ import { all, and, not, or } from "@prisma-next/sql-orm-client";
1
2
  import type { Where } from "better-auth";
2
- import { all, and, not, or } from "@shivaedev/effect-prisma";
3
-
4
3
  export type CleanedWhere = Required<Where>;
5
4
  export type Expression = Parameters<typeof and>[number];
6
-
7
5
  export interface DynamicField {
8
6
  asc: () => unknown;
9
7
  desc: () => unknown;
@@ -20,53 +18,49 @@ export interface DynamicField {
20
18
  neq: (value: unknown) => Expression;
21
19
  notIn: (value: readonly unknown[]) => Expression;
22
20
  }
23
-
24
- const escapeLike = (value: string): string => value.replaceAll("\\", "\\\\").replaceAll("%", "\\%").replaceAll("_", "\\_");
25
-
26
- const nonNullValues = (value: unknown): readonly unknown[] => (Array.isArray(value) ? value : [value]).filter((item) => item !== null);
27
-
28
- const insensitiveStrings = (field: DynamicField, where: CleanedWhere, values: readonly unknown[]) => {
21
+ function escapeLike(value: string): string {
22
+ return value.replaceAll("\\", "\\\\").replaceAll("%", "\\%").replaceAll("_", "\\_");
23
+ }
24
+ function nonNullValues(value: unknown): readonly unknown[] {
25
+ return (Array.isArray(value) ? value : [value]).filter((item) => item !== null);
26
+ }
27
+ function insensitiveStrings(field: DynamicField, where: CleanedWhere, values: readonly unknown[]) {
29
28
  const ilike = field.ilike;
30
29
  if (where.mode !== "insensitive" || ilike === undefined || !values.every((item) => typeof item === "string")) {
31
30
  return undefined;
32
31
  }
33
32
  return values.map((item) => ilike(escapeLike(String(item))));
34
- };
35
-
36
- const membership = (field: DynamicField, where: CleanedWhere): Expression => {
33
+ }
34
+ function membership(field: DynamicField, where: CleanedWhere): Expression {
37
35
  const values = nonNullValues(where.value);
38
36
  if (values.length === 0) {
39
37
  return and(field.isNull(), field.isNotNull());
40
38
  }
41
39
  const matches = insensitiveStrings(field, where, values);
42
40
  return matches === undefined ? field.in(values) : or(...matches);
43
- };
44
-
45
- const exclusion = (field: DynamicField, where: CleanedWhere): Expression | undefined => {
41
+ }
42
+ function exclusion(field: DynamicField, where: CleanedWhere): Expression | undefined {
46
43
  const values = nonNullValues(where.value);
47
44
  if (values.length === 0) {
48
45
  return undefined;
49
46
  }
50
47
  const matches = insensitiveStrings(field, where, values);
51
48
  return matches === undefined ? field.notIn(values) : and(...matches.map((match) => not(match)));
52
- };
53
-
54
- const likePattern = (operator: CleanedWhere["operator"], escaped: string): string => {
49
+ }
50
+ function likePattern(operator: CleanedWhere["operator"], escaped: string): string {
55
51
  if (operator === "contains") {
56
52
  return `%${escaped}%`;
57
53
  }
58
54
  return operator === "starts_with" ? `${escaped}%` : `%${escaped}`;
59
- };
60
-
61
- const pattern = (field: DynamicField, where: CleanedWhere): Expression => {
55
+ }
56
+ function pattern(field: DynamicField, where: CleanedWhere): Expression {
62
57
  if (typeof where.value !== "string") {
63
58
  throw new TypeError(`${where.operator} requires a string value`);
64
59
  }
65
60
  const text = likePattern(where.operator, escapeLike(where.value));
66
61
  return where.mode === "insensitive" && field.ilike !== undefined ? field.ilike(text) : field.like(text);
67
- };
68
-
69
- const insensitiveEquality = (field: DynamicField, where: CleanedWhere): Expression | undefined => {
62
+ }
63
+ function insensitiveEquality(field: DynamicField, where: CleanedWhere): Expression | undefined {
70
64
  const value = where.value;
71
65
  const operator = where.operator ?? "eq";
72
66
  if (where.mode !== "insensitive" || typeof value !== "string" || field.ilike === undefined) {
@@ -79,9 +73,8 @@ const insensitiveEquality = (field: DynamicField, where: CleanedWhere): Expressi
79
73
  return not(field.ilike(escapeLike(value)));
80
74
  }
81
75
  return undefined;
82
- };
83
-
84
- const comparison = (field: DynamicField, where: CleanedWhere): Expression | undefined => {
76
+ }
77
+ function comparison(field: DynamicField, where: CleanedWhere): Expression | undefined {
85
78
  const value = where.value;
86
79
  switch (where.operator ?? "eq") {
87
80
  case "eq":
@@ -105,12 +98,10 @@ const comparison = (field: DynamicField, where: CleanedWhere): Expression | unde
105
98
  case "ends_with":
106
99
  return pattern(field, where);
107
100
  }
108
- };
109
-
110
- export const whereExpression = (fields: Record<string, DynamicField>, where: readonly CleanedWhere[]): Expression | undefined => {
101
+ }
102
+ export function whereExpression(fields: Record<string, DynamicField>, where: readonly CleanedWhere[]): Expression | undefined {
111
103
  const conjunctions: Expression[] = [];
112
104
  const disjunctions: Expression[] = [];
113
-
114
105
  for (const condition of where) {
115
106
  const field = fields[condition.field];
116
107
  if (field === undefined) {
@@ -122,7 +113,6 @@ export const whereExpression = (fields: Record<string, DynamicField>, where: rea
122
113
  }
123
114
  (condition.connector === "OR" ? disjunctions : conjunctions).push(expression);
124
115
  }
125
-
126
116
  if (disjunctions.length > 0) {
127
117
  conjunctions.push(or(...disjunctions));
128
118
  }
@@ -130,4 +120,4 @@ export const whereExpression = (fields: Record<string, DynamicField>, where: rea
130
120
  return all();
131
121
  }
132
122
  return conjunctions.length === 1 ? conjunctions[0] : and(...conjunctions);
133
- };
123
+ }
@@ -1,28 +1,25 @@
1
1
  import type { IncomingMessage } from "node:http";
2
-
3
2
  export interface NodeSubscriptionSignalOptions {
4
3
  readonly nodeRequest?: IncomingMessage | undefined;
5
4
  readonly request?: Request | undefined;
6
5
  readonly signals?: Iterable<AbortSignal | undefined>;
7
6
  }
8
-
9
7
  export interface NodeSubscriptionSignal {
10
8
  readonly dispose: () => void;
11
9
  readonly signal: AbortSignal;
12
10
  }
13
-
14
- const property = (value: unknown, key: string): unknown => (typeof value === "object" && value !== null ? Reflect.get(value, key) : undefined);
15
-
16
- const isIncomingMessage = (value: unknown): value is IncomingMessage =>
17
- typeof property(value, "socket") === "object" && typeof property(value, "once") === "function";
18
-
19
- const nodeRequestFrom = (request: Request | undefined): IncomingMessage | undefined => {
11
+ function property(value: unknown, key: string): unknown {
12
+ return typeof value === "object" && value !== null ? Reflect.get(value, key) : undefined;
13
+ }
14
+ function isIncomingMessage(value: unknown): value is IncomingMessage {
15
+ return typeof property(value, "socket") === "object" && typeof property(value, "once") === "function";
16
+ }
17
+ function nodeRequestFrom(request: Request | undefined): IncomingMessage | undefined {
20
18
  const req = property(property(property(request, "runtime"), "node"), "req");
21
19
  return isIncomingMessage(req) ? req : undefined;
22
- };
23
-
20
+ }
24
21
  // Under Bun's node:http compatibility layer a Web request signal may miss an abandoned socket, so node close events cover long-lived responses.
25
- export const nodeSubscriptionSignal = (options: NodeSubscriptionSignalOptions): NodeSubscriptionSignal => {
22
+ export function nodeSubscriptionSignal(options: NodeSubscriptionSignalOptions): NodeSubscriptionSignal {
26
23
  const controller = new AbortController();
27
24
  const signals: AbortSignal[] = [controller.signal];
28
25
  if (options.request !== undefined) {
@@ -33,25 +30,25 @@ export const nodeSubscriptionSignal = (options: NodeSubscriptionSignalOptions):
33
30
  signals.push(signal);
34
31
  }
35
32
  }
36
-
37
33
  const nodeRequest = options.nodeRequest ?? nodeRequestFrom(options.request);
38
34
  if (nodeRequest === undefined) {
39
- return { dispose: () => {}, signal: AbortSignal.any(signals) };
35
+ return { dispose: (): void => undefined, signal: AbortSignal.any(signals) };
40
36
  }
41
-
42
- const socket = nodeRequest.socket;
43
- const onRequestClose = () => {
44
- if (!nodeRequest.complete) {
37
+ const request = nodeRequest;
38
+ const socket = request.socket;
39
+ function onRequestClose() {
40
+ if (!request.complete) {
45
41
  controller.abort();
46
42
  }
47
- };
48
- const onSocketClose = () => controller.abort();
43
+ }
44
+ function onSocketClose() {
45
+ return controller.abort();
46
+ }
49
47
  nodeRequest.once("close", onRequestClose);
50
48
  socket.once("close", onSocketClose);
51
49
  if (nodeRequest.destroyed || socket.destroyed) {
52
50
  controller.abort();
53
51
  }
54
-
55
52
  let disposed = false;
56
53
  return {
57
54
  dispose: () => {
@@ -64,4 +61,4 @@ export const nodeSubscriptionSignal = (options: NodeSubscriptionSignalOptions):
64
61
  },
65
62
  signal: AbortSignal.any(signals),
66
63
  };
67
- };
64
+ }
@@ -3,20 +3,19 @@ import type { Headers } from "effect/unstable/http";
3
3
  import { AuthUnavailable } from "#errors/taxonomy.ts";
4
4
  import { redact } from "#rpc-server/redact.ts";
5
5
  import type { SessionProvider, SessionShape } from "#rpc-server/session.ts";
6
-
7
- export type GetSession<Session extends SessionShape> = (headers: globalThis.Headers) => Promise<Session | null>;
8
-
9
- const unavailable = (cause: unknown) =>
10
- Effect.andThen(
6
+ export type GetSession<TSession extends SessionShape> = (headers: globalThis.Headers) => Promise<TSession | null>;
7
+ function unavailable(cause: unknown) {
8
+ return Effect.andThen(
11
9
  Effect.logError("Authentication provider failed", redact(cause)),
12
10
  Effect.fail(new AuthUnavailable({ message: "Authentication is temporarily unavailable" })),
13
11
  );
14
-
15
- export const betterAuthSessions = <Session extends SessionShape>(getSession: GetSession<Session>): SessionProvider<Session> => ({
16
- get: Effect.fn("PlatformRpc.betterAuthSession")(function* (headers: Headers.Headers) {
12
+ }
13
+ export function betterAuthSessions<TSession extends SessionShape>(getSession: GetSession<TSession>): SessionProvider<TSession> {
14
+ const betterAuthSession = Effect.fn("PlatformRpc.betterAuthSession")(function* (headers: Headers.Headers) {
17
15
  const session = yield* Effect.tryPromise(() => getSession(new globalThis.Headers(headers))).pipe(
18
16
  Effect.catch((error) => unavailable(error.cause)),
19
17
  );
20
18
  return Option.fromNullishOr(session);
21
- }),
22
- });
19
+ });
20
+ return { get: betterAuthSession };
21
+ }
@@ -0,0 +1,26 @@
1
+ import { Cause, Effect, Schema } from "effect";
2
+ import { isSensitiveKey, redactText } from "./sensitive.ts";
3
+ export interface RpcBoundaryOptions<E> {
4
+ readonly message?: string;
5
+ readonly unexpected: (message: string) => E;
6
+ }
7
+ export function rpcBoundary<
8
+ TSchema extends Schema.ConstraintDecoder<{
9
+ readonly message: string;
10
+ }>,
11
+ >(schema: TSchema, options: RpcBoundaryOptions<TSchema["Type"]>) {
12
+ const declared = Schema.is(schema);
13
+ return <A, E, R>(self: Effect.Effect<A, E, R>): Effect.Effect<A, TSchema["Type"], R> =>
14
+ Effect.catchCause(self, (cause) => {
15
+ if (Cause.hasInterruptsOnly(cause)) {
16
+ return Effect.failCause(Cause.fromReasons(cause.reasons.filter(Cause.isInterruptReason)));
17
+ }
18
+ const reason = cause.reasons[0];
19
+ if (cause.reasons.length === 1 && reason && Cause.isFailReason(reason) && declared(reason.error)) {
20
+ return Effect.fail(reason.error);
21
+ }
22
+ const rejection = options.unexpected(redactText(options.message ?? "Internal server error", isSensitiveKey));
23
+ Object.defineProperty(rejection, "cause", { configurable: true, enumerable: false, value: cause });
24
+ return Effect.failCause(Cause.fromReasons([...Cause.fail(rejection).reasons, ...cause.reasons.filter(Cause.isInterruptReason)]));
25
+ });
26
+ }
@@ -0,0 +1,70 @@
1
+ import { Effect, Option } from "effect";
2
+ import { HttpServerRequest, HttpServerResponse } from "effect/unstable/http";
3
+ import { RpcSerialization } from "effect/unstable/rpc";
4
+
5
+ function loopbackAddress(address: string): boolean {
6
+ const unmapped = unmapAddress(address);
7
+ return unmapped === "::1" || (/^127(?:\.\d{1,3}){3}$/u.test(unmapped) && unmapped.split(".").every((part) => Number(part) <= 255));
8
+ }
9
+ function unmapAddress(address: string): string {
10
+ return address.replace(/^::ffff:/iu, "");
11
+ }
12
+ function loopbackHost(host: string | undefined): boolean {
13
+ if (host === undefined) {
14
+ return false;
15
+ }
16
+ const authority = /^(?<hostname>\[[^\]]+\]|[^:]+)(?::(?<port>\d{1,5}))?$/u.exec(host)?.groups;
17
+ if (authority === undefined || (authority.port !== undefined && Number(authority.port) > 65_535)) {
18
+ return false;
19
+ }
20
+ const hostname = authority.hostname?.toLowerCase();
21
+ return hostname !== undefined && (hostname === "localhost" || hostname === "[::1]" || loopbackAddress(hostname));
22
+ }
23
+ export function sameOrigin(request: HttpServerRequest.HttpServerRequest): boolean {
24
+ const origin = request.headers.origin;
25
+ if (origin === undefined) {
26
+ return true;
27
+ }
28
+ try {
29
+ const url = new URL(origin);
30
+ return url.protocol === "http:" && url.origin === origin && url.host === request.headers.host;
31
+ } catch {
32
+ return false;
33
+ }
34
+ }
35
+ export function loopbackOnly<E, R>(
36
+ handler: (request: HttpServerRequest.HttpServerRequest) => Effect.Effect<HttpServerResponse.HttpServerResponse, E, R>,
37
+ ) {
38
+ return (request: HttpServerRequest.HttpServerRequest) =>
39
+ loopbackHost(request.headers.host) && Option.match(request.remoteAddress, { onNone: () => true, onSome: loopbackAddress })
40
+ ? handler(request)
41
+ : Effect.succeed(HttpServerResponse.text("Only loopback hosts are served", { status: 403 }));
42
+ }
43
+ function proxyOrigin(request: HttpServerRequest.HttpServerRequest, trustedProxy: string | undefined): boolean {
44
+ if (trustedProxy === undefined || !Option.exists(request.remoteAddress, (address) => unmapAddress(address) === unmapAddress(trustedProxy))) {
45
+ return false;
46
+ }
47
+ const host = request.headers["x-forwarded-host"];
48
+ const protocol = request.headers["x-forwarded-proto"];
49
+ if (host === undefined || !/^(?:\[[\da-f:]+\]|[\da-z.-]+)(?::\d{1,5})?$/iu.test(host) || (protocol !== "http" && protocol !== "https")) {
50
+ return false;
51
+ }
52
+ try {
53
+ const url = new URL(`${protocol}://${host}`);
54
+ return request.headers.origin === undefined || request.headers.origin === url.origin;
55
+ } catch {
56
+ return false;
57
+ }
58
+ }
59
+ export const localRpcGuard = Effect.fnUntraced(function* <E, R>(
60
+ effect: Effect.Effect<HttpServerResponse.HttpServerResponse, E, R>,
61
+ trustedProxy?: string,
62
+ ) {
63
+ const request = yield* HttpServerRequest.HttpServerRequest;
64
+ const serialization = yield* RpcSerialization.RpcSerialization;
65
+ const local = loopbackHost(request.headers.host) && Option.exists(request.remoteAddress, loopbackAddress);
66
+ const contentType = request.headers["content-type"]?.split(";")[0]?.trim().toLowerCase();
67
+ return ((local && sameOrigin(request)) || proxyOrigin(request, trustedProxy)) && contentType === serialization.contentType
68
+ ? yield* effect
69
+ : HttpServerResponse.text("Local same-origin requests only", { status: 403 });
70
+ });
@@ -1,29 +1,26 @@
1
1
  import { Option } from "effect";
2
2
  import { Headers } from "effect/unstable/http";
3
-
4
3
  export interface OriginRequest {
5
4
  readonly headers: Headers.Headers;
6
5
  readonly origin: Option.Option<string>;
7
6
  readonly rpc: string;
8
7
  }
9
-
10
8
  export type OriginPolicy = (request: OriginRequest) => boolean;
11
-
12
9
  export interface TrustedOriginsOptions {
13
10
  readonly allow: readonly string[];
14
11
  readonly missing: "allow" | "reject";
15
12
  }
16
-
17
- export const trustedOrigins =
18
- (options: TrustedOriginsOptions): OriginPolicy =>
19
- ({ origin }) =>
13
+ export function trustedOrigins(options: TrustedOriginsOptions): OriginPolicy {
14
+ return ({ origin }) =>
20
15
  Option.match(origin, {
21
16
  onNone: () => options.missing === "allow",
22
17
  onSome: (value) => options.allow.includes(value),
23
18
  });
24
-
25
- export const originRequest = (headers: Headers.Headers, rpc: string): OriginRequest => ({
26
- headers,
27
- origin: Headers.get(headers, "origin"),
28
- rpc,
29
- });
19
+ }
20
+ export function originRequest(headers: Headers.Headers, rpc: string): OriginRequest {
21
+ return {
22
+ headers,
23
+ origin: Headers.get(headers, "origin"),
24
+ rpc,
25
+ };
26
+ }
@@ -3,8 +3,7 @@ import { isSensitiveKey, REDACTED, redactText, type SensitiveKey } from "./sensi
3
3
 
4
4
  const MAX_DEPTH = 8;
5
5
  const MAX_ENTRIES = 50;
6
-
7
- const binary = (value: object): string | undefined => {
6
+ function binary(value: object): string | undefined {
8
7
  if (ArrayBuffer.isView(value)) {
9
8
  return `<${value.constructor.name} ${value.byteLength} bytes>`;
10
9
  }
@@ -12,9 +11,8 @@ const binary = (value: object): string | undefined => {
12
11
  return `<ArrayBuffer ${value.byteLength} bytes>`;
13
12
  }
14
13
  return undefined;
15
- };
16
-
17
- const entriesOf = (value: object): [string, unknown][] => {
14
+ }
15
+ function entriesOf(value: object): [string, unknown][] {
18
16
  const entries = Object.entries(value);
19
17
  if (!Predicate.isError(value)) {
20
18
  return entries;
@@ -25,9 +23,8 @@ const entriesOf = (value: object): [string, unknown][] => {
25
23
  ["stack", value.stack],
26
24
  ];
27
25
  return value.cause === undefined ? [...shape, ...entries] : [...shape, ...entries, ["cause", value.cause]];
28
- };
29
-
30
- const walk = (value: unknown, sensitive: SensitiveKey, depth: number): unknown => {
26
+ }
27
+ function walk(value: unknown, sensitive: SensitiveKey, depth: number): unknown {
31
28
  if (Redacted.isRedacted(value)) {
32
29
  return REDACTED;
33
30
  }
@@ -53,6 +50,7 @@ const walk = (value: unknown, sensitive: SensitiveKey, depth: number): unknown =
53
50
  .slice(0, MAX_ENTRIES)
54
51
  .map(([key, nested]): [string, unknown] => [key, sensitive(key) ? REDACTED : walk(nested, sensitive, depth + 1)]);
55
52
  return Object.fromEntries(entries.length > MAX_ENTRIES ? [...kept, ["<truncated>", `${entries.length - MAX_ENTRIES} more keys`]] : kept);
56
- };
57
-
58
- export const redact = (value: unknown, sensitive: SensitiveKey = isSensitiveKey): unknown => walk(value, sensitive, 0);
53
+ }
54
+ export function redact(value: unknown, sensitive: SensitiveKey = isSensitiveKey): unknown {
55
+ return walk(value, sensitive, 0);
56
+ }