@shivaedev/platform 0.1.0 → 0.2.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 (40) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/README.md +40 -3
  3. package/dist/better-auth/adapter.d.ts +11 -0
  4. package/dist/better-auth/adapter.d.ts.map +1 -0
  5. package/dist/better-auth/adapter.js +101 -0
  6. package/dist/better-auth/adapter.js.map +1 -0
  7. package/dist/better-auth/relation.d.ts +47 -0
  8. package/dist/better-auth/relation.d.ts.map +1 -0
  9. package/dist/better-auth/relation.js +111 -0
  10. package/dist/better-auth/relation.js.map +1 -0
  11. package/dist/better-auth.d.ts +2 -0
  12. package/dist/better-auth.d.ts.map +1 -0
  13. package/dist/better-auth.js +2 -0
  14. package/dist/better-auth.js.map +1 -0
  15. package/dist/runtime/make.d.ts +4 -0
  16. package/dist/runtime/make.d.ts.map +1 -0
  17. package/dist/runtime/make.js +59 -0
  18. package/dist/runtime/make.js.map +1 -0
  19. package/dist/runtime/types.d.ts +20 -0
  20. package/dist/runtime/types.d.ts.map +1 -0
  21. package/dist/runtime/types.js +2 -0
  22. package/dist/runtime/types.js.map +1 -0
  23. package/dist/runtime.d.ts +3 -0
  24. package/dist/runtime.d.ts.map +1 -0
  25. package/dist/runtime.js +2 -0
  26. package/dist/runtime.js.map +1 -0
  27. package/dist/testing/types.d.ts +3 -1
  28. package/dist/testing/types.d.ts.map +1 -1
  29. package/dist/testing/vitest.d.ts.map +1 -1
  30. package/dist/testing/vitest.js +3 -1
  31. package/dist/testing/vitest.js.map +1 -1
  32. package/package.json +26 -6
  33. package/src/better-auth/adapter.ts +226 -0
  34. package/src/better-auth/relation.ts +199 -0
  35. package/src/better-auth.ts +4 -0
  36. package/src/runtime/make.ts +84 -0
  37. package/src/runtime/types.ts +34 -0
  38. package/src/runtime.ts +7 -0
  39. package/src/testing/types.ts +6 -1
  40. package/src/testing/vitest.ts +6 -1
package/CHANGELOG.md CHANGED
@@ -2,6 +2,17 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.2.0 - 2026-08-03
6
+
7
+ ### Added
8
+
9
+ - Add a shared application runtime that carries Effect services across tRPC,
10
+ Better Auth, and other promise boundaries.
11
+ - Add a Better Auth adapter backed by Effect Prisma, including transaction
12
+ propagation and PostgreSQL filter support.
13
+ - Add a `promise` test-harness helper for driving promise-based boundaries
14
+ inside the test rollback transaction.
15
+
5
16
  ## 0.1.0 - 2026-08-03
6
17
 
7
18
  ### Added
package/README.md CHANGED
@@ -4,10 +4,44 @@ Opinionated shared setup for ShivaeDev Effect applications. This package favors
4
4
  one consistent application shape over supporting every possible combination of
5
5
  database, transport, and test framework.
6
6
 
7
- The first public surface is the combined integration test harness. Generic
8
- Prisma and tRPC integrations remain available separately from
7
+ Generic Prisma and tRPC integrations remain available separately from
9
8
  `@shivaedev/effect-prisma` and `@shivaedev/effect-trpc`.
10
9
 
10
+ ## Runtime
11
+
12
+ Build one managed runtime for the application and share it with integrations:
13
+
14
+ ```ts
15
+ import { makePlatformRuntime } from "@shivaedev/platform/runtime"
16
+
17
+ export const runtime = makePlatformRuntime(ApplicationLive, {
18
+ developmentCacheKey: "application",
19
+ })
20
+ ```
21
+
22
+ The optional cache key keeps one runtime across development module reloads.
23
+ The runtime also carries transaction- and request-scoped service overrides
24
+ across promise boundaries.
25
+
26
+ ## Better Auth
27
+
28
+ `effectPrismaAdapter` stores Better Auth data through the application's Effect
29
+ Prisma database and runtime, including Better Auth transactions:
30
+
31
+ ```ts
32
+ import { effectPrismaAdapter } from "@shivaedev/platform/better-auth"
33
+ import { betterAuth } from "better-auth"
34
+
35
+ const auth = betterAuth({
36
+ database: effectPrismaAdapter(Database, runtime),
37
+ })
38
+ ```
39
+
40
+ The adapter owns database translation only. Providers, plugins, cookies,
41
+ session policy, and authorization remain application configuration. Native
42
+ experimental Better Auth joins are not supported; the standard adapter join
43
+ fallback remains available.
44
+
11
45
  ## Testing
12
46
 
13
47
  Configure the application database, tRPC caller, and test Layer once:
@@ -33,7 +67,7 @@ database calls, factories, and fixtures share that transaction, which is rolled
33
67
  back after both successful and failed tests:
34
68
 
35
69
  ```ts
36
- it.effectApp("creates a movie", function* ({ trpc, db, factories }) {
70
+ it.effectApp("creates a movie", function* ({ trpc, db, factories, promise }) {
37
71
  const input = factories.movie()
38
72
  const movie = yield* trpc.movie.create(input)
39
73
  const stored = yield* db.Movie.where({ id: movie.id }).first()
@@ -42,6 +76,9 @@ it.effectApp("creates a movie", function* ({ trpc, db, factories }) {
42
76
  })
43
77
  ```
44
78
 
79
+ `promise(() => ...)` runs a promise-based application boundary, such as Better
80
+ Auth or an HTTP handler, with the same transaction-scoped Effect services.
81
+
45
82
  Calling `trpc(options)` creates a caller for another application actor without
46
83
  rebuilding the worker-scoped test Layer. `effectApp` also supports `skip`,
47
84
  `skipIf`, `runIf`, `only`, `each`, and `fails`.
@@ -0,0 +1,11 @@
1
+ import type { AnyDatabase } from "@shivaedev/effect-prisma";
2
+ import type { DBAdapterInstance } from "better-auth";
3
+ import type { PlatformRuntime } from "../runtime/types.js";
4
+ export interface EffectPrismaAdapterOptions {
5
+ readonly debugLogs?: boolean;
6
+ /** Map Better Auth model names to Prisma contract model names. */
7
+ readonly modelName?: (model: string) => string;
8
+ readonly usePlural?: boolean;
9
+ }
10
+ export declare const effectPrismaAdapter: <Database extends AnyDatabase, Services, BuildError>(databaseTag: Database, runtime: PlatformRuntime<Services, BuildError>, adapterOptions?: EffectPrismaAdapterOptions) => DBAdapterInstance;
11
+ //# sourceMappingURL=adapter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../../src/better-auth/adapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAqB,MAAM,0BAA0B,CAAC;AAC/E,OAAO,KAAK,EAAqB,iBAAiB,EAAS,MAAM,aAAa,CAAC;AAG/E,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAY3D,MAAM,WAAW,0BAA0B;IAC1C,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC;IAC7B,kEAAkE;IAClE,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;IAC/C,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC;CAC7B;AA2BD,eAAO,MAAM,mBAAmB,GAC9B,QAAQ,SAAS,WAAW,EAAE,QAAQ,EAAE,UAAU,EAClD,aAAa,QAAQ,EACrB,SAAS,eAAe,CAAC,QAAQ,EAAE,UAAU,CAAC,EAC9C,iBAAgB,0BAA+B,KAC7C,iBA4KF,CAAC"}
@@ -0,0 +1,101 @@
1
+ import { createAdapterFactory } from "better-auth/adapters";
2
+ import { Effect, Option } from "effect";
3
+ import { refineRelation } from "./relation.js";
4
+ const modelRelation = (database, model, mapModelName) => Reflect.get(database, mapModelName(model));
5
+ const defaultModelName = (model) => model.length === 0 ? model : `${model[0]?.toUpperCase()}${model.slice(1)}`;
6
+ const rejectJoin = (join) => {
7
+ if (join !== undefined) {
8
+ throw new TypeError("The Effect Prisma Better Auth adapter does not support experimental native joins");
9
+ }
10
+ };
11
+ export const effectPrismaAdapter = (databaseTag, runtime, adapterOptions = {}) => (authOptions) => {
12
+ const mapModelName = adapterOptions.modelName ?? defaultModelName;
13
+ const databaseEffect = databaseTag;
14
+ const run = (operation) => runtime.runPromise(Effect.flatMap(databaseEffect, (database) => operation(database)));
15
+ let factory;
16
+ factory = createAdapterFactory({
17
+ config: {
18
+ adapterId: "effect-prisma",
19
+ adapterName: "Effect Prisma",
20
+ debugLogs: adapterOptions.debugLogs ?? false,
21
+ supportsArrays: true,
22
+ supportsBooleans: true,
23
+ supportsDates: true,
24
+ supportsJSON: true,
25
+ supportsNumericIds: true,
26
+ supportsUUIDs: true,
27
+ transaction: (callback) => run((database) => database.transaction(Effect.flatMap(Effect.context(), (services) => Effect.tryPromise({
28
+ try: () => runtime.runWithServices(services, () => callback(factory(authOptions))),
29
+ catch: (error) => error,
30
+ })))),
31
+ },
32
+ adapter: ({ debugLog, getFieldAttributes }) => ({
33
+ create: async ({ data, model, select }) => {
34
+ debugLog("create", { model });
35
+ return (await run((database) => refineRelation(modelRelation(database, model, mapModelName), {
36
+ select,
37
+ }).create(data)));
38
+ },
39
+ findOne: async ({ join, model, select, where, }) => {
40
+ rejectJoin(join);
41
+ return (await run((database) => Effect.map(refineRelation(modelRelation(database, model, mapModelName), {
42
+ select,
43
+ where,
44
+ }).first(), Option.getOrNull)));
45
+ },
46
+ findMany: async ({ join, limit, model, offset, select, sortBy, where, }) => {
47
+ rejectJoin(join);
48
+ return (await run((database) => refineRelation(modelRelation(database, model, mapModelName), {
49
+ limit,
50
+ offset,
51
+ select,
52
+ sortBy,
53
+ where,
54
+ })));
55
+ },
56
+ update: async ({ model, update, where }) => {
57
+ if (where.length === 0)
58
+ return null;
59
+ const hasUniqueCondition = where.some((condition) => condition.connector !== "OR" &&
60
+ condition.operator === "eq" &&
61
+ condition.mode !== "insensitive" &&
62
+ (condition.field === "id" ||
63
+ getFieldAttributes({
64
+ field: condition.field,
65
+ model,
66
+ }).unique === true));
67
+ return (await run((database) => {
68
+ const relation = refineRelation(modelRelation(database, model, mapModelName), { where });
69
+ return hasUniqueCondition
70
+ ? relation.update(update)
71
+ : Effect.map(relation.updateAll(update), (rows) => rows[0] ?? null);
72
+ }));
73
+ },
74
+ updateMany: ({ model, update, where }) => run((database) => Effect.map(refineRelation(modelRelation(database, model, mapModelName), {
75
+ where,
76
+ }).updateAll(update), (rows) => rows.length)),
77
+ delete: ({ model, where }) => {
78
+ const hasId = where.some((condition) => condition.connector !== "OR" &&
79
+ condition.field === "id" &&
80
+ condition.operator === "eq");
81
+ return run((database) => {
82
+ const relation = refineRelation(modelRelation(database, model, mapModelName), { where });
83
+ return hasId
84
+ ? Effect.asVoid(relation.delete())
85
+ : Effect.asVoid(relation.deleteAll());
86
+ });
87
+ },
88
+ deleteMany: ({ model, where }) => run((database) => Effect.map(refineRelation(modelRelation(database, model, mapModelName), {
89
+ where,
90
+ }).deleteAll(), (rows) => rows.length)),
91
+ count: ({ model, where }) => run((database) => refineRelation(modelRelation(database, model, mapModelName), {
92
+ where: where,
93
+ }).count()),
94
+ options: {
95
+ usePlural: adapterOptions.usePlural ?? false,
96
+ },
97
+ }),
98
+ });
99
+ return factory(authOptions);
100
+ };
101
+ //# sourceMappingURL=adapter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adapter.js","sourceRoot":"","sources":["../../src/better-auth/adapter.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,47 @@
1
+ import { and } from "@shivaedev/effect-prisma";
2
+ import type { Where } from "better-auth";
3
+ import type { Effect, Option } from "effect";
4
+ type CleanedWhere = Required<Where>;
5
+ type Expression = Parameters<typeof and>[number];
6
+ interface DynamicField {
7
+ eq(value: unknown): Expression;
8
+ neq(value: unknown): Expression;
9
+ gt(value: unknown): Expression;
10
+ gte(value: unknown): Expression;
11
+ lt(value: unknown): Expression;
12
+ lte(value: unknown): Expression;
13
+ in(value: ReadonlyArray<unknown>): Expression;
14
+ notIn(value: ReadonlyArray<unknown>): Expression;
15
+ like(value: string): Expression;
16
+ ilike?(value: string): Expression;
17
+ isNull(): Expression;
18
+ isNotNull(): Expression;
19
+ asc(): unknown;
20
+ desc(): unknown;
21
+ }
22
+ export interface DynamicRelation<Requirements> {
23
+ where(predicate: (fields: Record<string, DynamicField>) => Expression): DynamicRelation<Requirements>;
24
+ orderBy(ordering: (fields: Record<string, DynamicField>) => unknown): DynamicRelation<Requirements>;
25
+ take(count: number): DynamicRelation<Requirements>;
26
+ skip(count: number): DynamicRelation<Requirements>;
27
+ select(...fields: ReadonlyArray<string>): DynamicRelation<Requirements>;
28
+ first(): Effect.Effect<Option.Option<Record<string, unknown>>, unknown, Requirements>;
29
+ count(): Effect.Effect<number, unknown, Requirements>;
30
+ create(data: Record<string, unknown>): Effect.Effect<Record<string, unknown>, unknown, Requirements>;
31
+ update(data: Record<string, unknown>): Effect.Effect<Record<string, unknown> | null, unknown, Requirements>;
32
+ updateAll(data: Record<string, unknown>): Effect.Effect<ReadonlyArray<Record<string, unknown>>, unknown, Requirements>;
33
+ delete(): Effect.Effect<Record<string, unknown> | null, unknown, Requirements>;
34
+ deleteAll(): Effect.Effect<ReadonlyArray<Record<string, unknown>>, unknown, Requirements>;
35
+ }
36
+ export declare const refineRelation: <Requirements>(relation: DynamicRelation<Requirements>, options: {
37
+ readonly where?: ReadonlyArray<CleanedWhere>;
38
+ readonly select?: ReadonlyArray<string>;
39
+ readonly limit?: number;
40
+ readonly offset?: number;
41
+ readonly sortBy?: {
42
+ readonly direction: "asc" | "desc";
43
+ readonly field: string;
44
+ };
45
+ }) => DynamicRelation<Requirements>;
46
+ export {};
47
+ //# sourceMappingURL=relation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"relation.d.ts","sourceRoot":"","sources":["../../src/better-auth/relation.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,111 @@
1
+ import { all, and, not, or } from "@shivaedev/effect-prisma";
2
+ const escapeLike = (value) => value.replaceAll("\\", "\\\\").replaceAll("%", "\\%").replaceAll("_", "\\_");
3
+ const comparison = (field, where) => {
4
+ const value = where.value;
5
+ if (where.mode === "insensitive" &&
6
+ typeof value === "string" &&
7
+ field.ilike !== undefined) {
8
+ if (where.operator === "eq")
9
+ return field.ilike(escapeLike(value));
10
+ if (where.operator === "ne")
11
+ return not(field.ilike(escapeLike(value)));
12
+ }
13
+ switch (where.operator) {
14
+ case "eq":
15
+ return value === null ? field.isNull() : field.eq(value);
16
+ case "ne":
17
+ return value === null ? field.isNotNull() : field.neq(value);
18
+ case "lt":
19
+ return field.lt(value);
20
+ case "lte":
21
+ return field.lte(value);
22
+ case "gt":
23
+ return field.gt(value);
24
+ case "gte":
25
+ return field.gte(value);
26
+ case "in": {
27
+ const values = (Array.isArray(value) ? value : [value]).filter((item) => item !== null);
28
+ const ilike = field.ilike;
29
+ return values.length === 0
30
+ ? and(field.isNull(), field.isNotNull())
31
+ : where.mode === "insensitive" &&
32
+ ilike !== undefined &&
33
+ values.every((item) => typeof item === "string")
34
+ ? or(...values.map((item) => ilike(escapeLike(String(item)))))
35
+ : field.in(values);
36
+ }
37
+ case "not_in": {
38
+ const values = (Array.isArray(value) ? value : [value]).filter((item) => item !== null);
39
+ if (values.length === 0)
40
+ return undefined;
41
+ const ilike = field.ilike;
42
+ return where.mode === "insensitive" &&
43
+ ilike !== undefined &&
44
+ values.every((item) => typeof item === "string")
45
+ ? and(...values.map((item) => not(ilike(escapeLike(String(item))))))
46
+ : field.notIn(values);
47
+ }
48
+ case "contains":
49
+ case "starts_with":
50
+ case "ends_with": {
51
+ if (typeof value !== "string") {
52
+ throw new TypeError(`${where.operator} requires a string value`);
53
+ }
54
+ const escaped = escapeLike(value);
55
+ const pattern = where.operator === "contains"
56
+ ? `%${escaped}%`
57
+ : where.operator === "starts_with"
58
+ ? `${escaped}%`
59
+ : `%${escaped}`;
60
+ return where.mode === "insensitive" && field.ilike !== undefined
61
+ ? field.ilike(pattern)
62
+ : field.like(pattern);
63
+ }
64
+ }
65
+ };
66
+ const whereExpression = (fields, where) => {
67
+ const conjunctions = [];
68
+ const disjunctions = [];
69
+ for (const condition of where) {
70
+ const field = fields[condition.field];
71
+ if (field === undefined) {
72
+ throw new TypeError(`Unknown database field: ${condition.field}`);
73
+ }
74
+ const expression = comparison(field, condition);
75
+ if (expression === undefined)
76
+ continue;
77
+ (condition.connector === "OR" ? disjunctions : conjunctions).push(expression);
78
+ }
79
+ if (disjunctions.length > 0)
80
+ conjunctions.push(or(...disjunctions));
81
+ if (conjunctions.length === 0)
82
+ return all();
83
+ return conjunctions.length === 1 ? conjunctions[0] : and(...conjunctions);
84
+ };
85
+ export const refineRelation = (relation, options) => {
86
+ let refined = relation;
87
+ if (options.where !== undefined) {
88
+ refined = refined.where((fields) => {
89
+ const expression = whereExpression(fields, options.where ?? []);
90
+ return expression ?? all();
91
+ });
92
+ }
93
+ if (options.sortBy !== undefined) {
94
+ refined = refined.orderBy((fields) => {
95
+ const field = fields[options.sortBy?.field ?? ""];
96
+ if (field === undefined) {
97
+ throw new TypeError(`Unknown database field: ${options.sortBy?.field}`);
98
+ }
99
+ return options.sortBy?.direction === "desc" ? field.desc() : field.asc();
100
+ });
101
+ }
102
+ if (options.offset !== undefined)
103
+ refined = refined.skip(options.offset);
104
+ if (options.limit !== undefined)
105
+ refined = refined.take(options.limit);
106
+ if (options.select !== undefined && options.select.length > 0) {
107
+ refined = refined.select(...options.select);
108
+ }
109
+ return refined;
110
+ };
111
+ //# sourceMappingURL=relation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"relation.js","sourceRoot":"","sources":["../../src/better-auth/relation.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export { type EffectPrismaAdapterOptions, effectPrismaAdapter, } from "./better-auth/adapter.js";
2
+ //# sourceMappingURL=better-auth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"better-auth.d.ts","sourceRoot":"","sources":["../src/better-auth.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,KAAK,0BAA0B,EAC/B,mBAAmB,GACnB,MAAM,0BAA0B,CAAC"}
@@ -0,0 +1,2 @@
1
+ export { effectPrismaAdapter, } from "./better-auth/adapter.js";
2
+ //# sourceMappingURL=better-auth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"better-auth.js","sourceRoot":"","sources":["../src/better-auth.ts"],"names":[],"mappings":"AAAA,OAAO,EAEN,mBAAmB,GACnB,MAAM,0BAA0B,CAAC"}
@@ -0,0 +1,4 @@
1
+ import type { Layer } from "effect";
2
+ import type { PlatformRuntime, PlatformRuntimeOptions } from "./types.js";
3
+ export declare const makePlatformRuntime: <Services, BuildError>(layer: Layer.Layer<Services, BuildError>, options?: PlatformRuntimeOptions) => PlatformRuntime<Services, BuildError>;
4
+ //# sourceMappingURL=make.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"make.d.ts","sourceRoot":"","sources":["../../src/runtime/make.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAC;AAEpC,OAAO,KAAK,EAAE,eAAe,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAuD1E,eAAO,MAAM,mBAAmB,GAAI,QAAQ,EAAE,UAAU,EACvD,OAAO,KAAK,CAAC,KAAK,CAAC,QAAQ,EAAE,UAAU,CAAC,EACxC,UAAS,sBAA2B,KAClC,eAAe,CAAC,QAAQ,EAAE,UAAU,CAsBtC,CAAC"}
@@ -0,0 +1,59 @@
1
+ import { AsyncLocalStorage } from "node:async_hooks";
2
+ import { Cause, Context, Effect, Exit, ManagedRuntime } from "effect";
3
+ const RuntimeCacheKey = Symbol.for("@shivaedev/platform/runtime-cache");
4
+ const globalRuntimeCache = globalThis;
5
+ const runtimeCache = globalRuntimeCache[RuntimeCacheKey] ??
6
+ new Map();
7
+ globalRuntimeCache[RuntimeCacheKey] = runtimeCache;
8
+ const make = (layer) => {
9
+ const managed = ManagedRuntime.make(layer);
10
+ const services = new AsyncLocalStorage();
11
+ const currentServices = () => services.getStore();
12
+ const withAmbient = (effect) => {
13
+ const ambient = currentServices();
14
+ return ambient === undefined
15
+ ? effect
16
+ : Effect.flatMap(managed.contextEffect, (base) => Effect.provideContext(effect, Context.merge(base, ambient)));
17
+ };
18
+ const runPromiseExit = (effect, options) => managed.runPromiseExit(withAmbient(effect), options);
19
+ const runtime = {
20
+ contextEffect: Effect.map(managed.contextEffect, (base) => {
21
+ const ambient = currentServices();
22
+ return ambient === undefined ? base : Context.merge(base, ambient);
23
+ }),
24
+ currentServices,
25
+ dispose: () => managed.dispose(),
26
+ runPromise: async (effect, options) => {
27
+ const exit = await runPromiseExit(effect, options);
28
+ if (Exit.isSuccess(exit))
29
+ return exit.value;
30
+ throw Cause.squash(exit.cause);
31
+ },
32
+ runPromiseExit,
33
+ runWithServices: (context, evaluate) => services.run(context, evaluate),
34
+ };
35
+ return runtime;
36
+ };
37
+ export const makePlatformRuntime = (layer, options = {}) => {
38
+ const key = process.env.NODE_ENV === "production"
39
+ ? undefined
40
+ : options.developmentCacheKey;
41
+ if (key === undefined)
42
+ return make(layer);
43
+ const cached = runtimeCache.get(key);
44
+ if (cached !== undefined) {
45
+ return cached;
46
+ }
47
+ const runtime = make(layer);
48
+ const cachedRuntime = {
49
+ ...runtime,
50
+ dispose: async () => {
51
+ if (runtimeCache.get(key) === cachedRuntime)
52
+ runtimeCache.delete(key);
53
+ await runtime.dispose();
54
+ },
55
+ };
56
+ runtimeCache.set(key, cachedRuntime);
57
+ return cachedRuntime;
58
+ };
59
+ //# sourceMappingURL=make.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"make.js","sourceRoot":"","sources":["../../src/runtime/make.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ import type { Context, Effect, Exit } from "effect";
2
+ export interface PlatformRuntimeOptions {
3
+ /** Reuse one runtime across development module reloads. */
4
+ readonly developmentCacheKey?: string | symbol;
5
+ }
6
+ export interface PlatformRuntime<Services, BuildError> {
7
+ readonly contextEffect: Effect.Effect<Context.Context<Services>, BuildError>;
8
+ readonly currentServices: () => Context.Context<never> | undefined;
9
+ readonly dispose: () => Promise<void>;
10
+ readonly runPromise: <A, E, Requirements extends Services>(effect: Effect.Effect<A, E, Requirements>, options?: {
11
+ readonly signal?: AbortSignal;
12
+ }) => Promise<A>;
13
+ readonly runPromiseExit: <A, E, Requirements extends Services>(effect: Effect.Effect<A, E, Requirements>, options?: {
14
+ readonly signal?: AbortSignal;
15
+ }) => Promise<Exit.Exit<A, E | BuildError>>;
16
+ readonly runWithServices: <Provided, Value>(services: Context.Context<Provided>, evaluate: () => Value) => Value;
17
+ }
18
+ export type PlatformRuntimeServices<Runtime> = Runtime extends PlatformRuntime<infer Services, infer _BuildError> ? Services : never;
19
+ export type PlatformRuntimeBuildError<Runtime> = Runtime extends PlatformRuntime<infer _Services, infer BuildError> ? BuildError : never;
20
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/runtime/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAEpD,MAAM,WAAW,sBAAsB;IACtC,2DAA2D;IAC3D,QAAQ,CAAC,mBAAmB,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;CAC/C;AAED,MAAM,WAAW,eAAe,CAAC,QAAQ,EAAE,UAAU;IACpD,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,UAAU,CAAC,CAAC;IAC7E,QAAQ,CAAC,eAAe,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,SAAS,CAAC;IACnE,QAAQ,CAAC,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACtC,QAAQ,CAAC,UAAU,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,YAAY,SAAS,QAAQ,EACxD,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,YAAY,CAAC,EACzC,OAAO,CAAC,EAAE;QAAE,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAA;KAAE,KACvC,OAAO,CAAC,CAAC,CAAC,CAAC;IAChB,QAAQ,CAAC,cAAc,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,YAAY,SAAS,QAAQ,EAC5D,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,YAAY,CAAC,EACzC,OAAO,CAAC,EAAE;QAAE,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAA;KAAE,KACvC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC;IAC3C,QAAQ,CAAC,eAAe,EAAE,CAAC,QAAQ,EAAE,KAAK,EACzC,QAAQ,EAAE,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,EACnC,QAAQ,EAAE,MAAM,KAAK,KACjB,KAAK,CAAC;CACX;AAED,MAAM,MAAM,uBAAuB,CAAC,OAAO,IAC1C,OAAO,SAAS,eAAe,CAAC,MAAM,QAAQ,EAAE,MAAM,WAAW,CAAC,GAC/D,QAAQ,GACR,KAAK,CAAC;AAEV,MAAM,MAAM,yBAAyB,CAAC,OAAO,IAC5C,OAAO,SAAS,eAAe,CAAC,MAAM,SAAS,EAAE,MAAM,UAAU,CAAC,GAC/D,UAAU,GACV,KAAK,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/runtime/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,3 @@
1
+ export { makePlatformRuntime } from "./runtime/make.js";
2
+ export type { PlatformRuntime, PlatformRuntimeBuildError, PlatformRuntimeOptions, PlatformRuntimeServices, } from "./runtime/types.js";
3
+ //# sourceMappingURL=runtime.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AACxD,YAAY,EACX,eAAe,EACf,yBAAyB,EACzB,sBAAsB,EACtB,uBAAuB,GACvB,MAAM,oBAAoB,CAAC"}
@@ -0,0 +1,2 @@
1
+ export { makePlatformRuntime } from "./runtime/make.js";
2
+ //# sourceMappingURL=runtime.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime.js","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC"}
@@ -6,7 +6,9 @@ import type { Effect, Layer } from "effect";
6
6
  export type PlatformHarness<Database extends AnyDatabase, Options, Caller, Extension> = Readonly<{
7
7
  db: DatabaseService<Database>;
8
8
  trpc: EffectCallerFactory<Options, Caller>;
9
- }> & Omit<Extension, "db" | "trpc">;
9
+ }> & Readonly<{
10
+ promise: <Value>(evaluate: () => Promise<Value>) => Effect.Effect<Value, unknown>;
11
+ }> & Omit<Extension, "db" | "promise" | "trpc">;
10
12
  export type PlatformTest<Harness, Provided> = TrpcHarnessTest<Harness, Provided>;
11
13
  export type PlatformTester<Harness, Provided> = TrpcHarnessTester<Harness, Provided>;
12
14
  export type PlatformIt<Harness, Provided> = Vitest.Methods & {
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/testing/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAC1D,OAAO,KAAK,EACX,WAAW,EACX,mBAAmB,EACnB,eAAe,EACf,MAAM,kCAAkC,CAAC;AAC1C,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAChE,OAAO,KAAK,EACX,aAAa,EACb,YAAY,EACZ,mBAAmB,EACnB,eAAe,EACf,iBAAiB,EACjB,MAAM,gCAAgC,CAAC;AACxC,OAAO,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAC;AAE5C,MAAM,MAAM,eAAe,CAC1B,QAAQ,SAAS,WAAW,EAC5B,OAAO,EACP,MAAM,EACN,SAAS,IACN,QAAQ,CAAC;IACZ,EAAE,EAAE,eAAe,CAAC,QAAQ,CAAC,CAAC;IAC9B,IAAI,EAAE,mBAAmB,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;CAC3C,CAAC,GACD,IAAI,CAAC,SAAS,EAAE,IAAI,GAAG,MAAM,CAAC,CAAC;AAEhC,MAAM,MAAM,YAAY,CAAC,OAAO,EAAE,QAAQ,IAAI,eAAe,CAC5D,OAAO,EACP,QAAQ,CACR,CAAC;AAEF,MAAM,MAAM,cAAc,CAAC,OAAO,EAAE,QAAQ,IAAI,iBAAiB,CAChE,OAAO,EACP,QAAQ,CACR,CAAC;AAEF,MAAM,MAAM,UAAU,CAAC,OAAO,EAAE,QAAQ,IAAI,MAAM,CAAC,OAAO,GAAG;IAC5D,QAAQ,CAAC,SAAS,EAAE,cAAc,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;CACtD,CAAC;AAEF,MAAM,WAAW,qBAAqB,CACrC,QAAQ,SAAS,WAAW,EAC5B,YAAY,SAAS,CAAC,GAAG,UAAU,EAAE,KAAK,EAAE,KAAK,MAAM,EACvD,QAAQ,EACR,UAAU,EACV,SAAS;IAET,QAAQ,CAAC,OAAO,EAAE,IAAI,CAAC,iBAAiB,CAAC,KAAK,CAAC,EAAE,iBAAiB,CAAC,CAAC;IACpE,QAAQ,CAAC,YAAY,EAAE,YAAY,CAAC;IACpC,QAAQ,CAAC,MAAM,CAAC,EAAE,CACjB,IAAI,EAAE,QAAQ,CAAC;QACd,EAAE,EAAE,eAAe,CAAC,QAAQ,CAAC,CAAC;QAC9B,IAAI,EAAE,mBAAmB,CACxB,aAAa,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC,EACpC,YAAY,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC,CACnC,CAAC;KACF,CAAC,EACF,OAAO,EAAE,WAAW,KAChB,MAAM,CAAC,MAAM,CACjB,SAAS,EACT,OAAO,EACP,QAAQ,GAAG,mBAAmB,CAAC,QAAQ,CAAC,GAAG,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,CACpE,CAAC;IACF,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC;CAClD"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/testing/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAC1D,OAAO,KAAK,EACX,WAAW,EACX,mBAAmB,EACnB,eAAe,EACf,MAAM,kCAAkC,CAAC;AAC1C,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAChE,OAAO,KAAK,EACX,aAAa,EACb,YAAY,EACZ,mBAAmB,EACnB,eAAe,EACf,iBAAiB,EACjB,MAAM,gCAAgC,CAAC;AACxC,OAAO,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAC;AAE5C,MAAM,MAAM,eAAe,CAC1B,QAAQ,SAAS,WAAW,EAC5B,OAAO,EACP,MAAM,EACN,SAAS,IACN,QAAQ,CAAC;IACZ,EAAE,EAAE,eAAe,CAAC,QAAQ,CAAC,CAAC;IAC9B,IAAI,EAAE,mBAAmB,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;CAC3C,CAAC,GACD,QAAQ,CAAC;IACR,OAAO,EAAE,CAAC,KAAK,EACd,QAAQ,EAAE,MAAM,OAAO,CAAC,KAAK,CAAC,KAC1B,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;CACnC,CAAC,GACF,IAAI,CAAC,SAAS,EAAE,IAAI,GAAG,SAAS,GAAG,MAAM,CAAC,CAAC;AAE5C,MAAM,MAAM,YAAY,CAAC,OAAO,EAAE,QAAQ,IAAI,eAAe,CAC5D,OAAO,EACP,QAAQ,CACR,CAAC;AAEF,MAAM,MAAM,cAAc,CAAC,OAAO,EAAE,QAAQ,IAAI,iBAAiB,CAChE,OAAO,EACP,QAAQ,CACR,CAAC;AAEF,MAAM,MAAM,UAAU,CAAC,OAAO,EAAE,QAAQ,IAAI,MAAM,CAAC,OAAO,GAAG;IAC5D,QAAQ,CAAC,SAAS,EAAE,cAAc,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;CACtD,CAAC;AAEF,MAAM,WAAW,qBAAqB,CACrC,QAAQ,SAAS,WAAW,EAC5B,YAAY,SAAS,CAAC,GAAG,UAAU,EAAE,KAAK,EAAE,KAAK,MAAM,EACvD,QAAQ,EACR,UAAU,EACV,SAAS;IAET,QAAQ,CAAC,OAAO,EAAE,IAAI,CAAC,iBAAiB,CAAC,KAAK,CAAC,EAAE,iBAAiB,CAAC,CAAC;IACpE,QAAQ,CAAC,YAAY,EAAE,YAAY,CAAC;IACpC,QAAQ,CAAC,MAAM,CAAC,EAAE,CACjB,IAAI,EAAE,QAAQ,CAAC;QACd,EAAE,EAAE,eAAe,CAAC,QAAQ,CAAC,CAAC;QAC9B,IAAI,EAAE,mBAAmB,CACxB,aAAa,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC,EACpC,YAAY,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC,CACnC,CAAC;KACF,CAAC,EACF,OAAO,EAAE,WAAW,KAChB,MAAM,CAAC,MAAM,CACjB,SAAS,EACT,OAAO,EACP,QAAQ,GAAG,mBAAmB,CAAC,QAAQ,CAAC,GAAG,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,CACpE,CAAC;IACF,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC;CAClD"}
@@ -1 +1 @@
1
- {"version":3,"file":"vitest.d.ts","sourceRoot":"","sources":["../../src/testing/vitest.ts"],"names":[],"mappings":"AACA,OAAO,EACN,KAAK,WAAW,EAChB,KAAK,mBAAmB,EAGxB,MAAM,kCAAkC,CAAC;AAC1C,OAAO,EACN,KAAK,aAAa,EAClB,KAAK,YAAY,EAGjB,MAAM,gCAAgC,CAAC;AACxC,OAAO,EAAE,MAAM,EAAE,KAAK,KAAK,EAAE,MAAM,QAAQ,CAAC;AAC5C,OAAO,KAAK,EACX,qBAAqB,EACrB,eAAe,EACf,UAAU,EACV,MAAM,YAAY,CAAC;AAEpB,eAAO,MAAM,cAAc,GACzB,KAAK,CAAC,QAAQ,SAAS,WAAW,EAAE,UAAU,QAAQ,MAEtD,YAAY,SAAS,CAAC,GAAG,UAAU,EAAE,KAAK,EAAE,KAAK,MAAM,EACvD,QAAQ,EACR,UAAU,EACV,SAAS,SAAS,MAAM,GAAG,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,EAE/C,SAAS,qBAAqB,CAC7B,QAAQ,EACR,YAAY,EACZ,QAAQ,EACR,UAAU,EACV,SAAS,CACT,GAAG;IACH,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC,KAAK,CAC1B,QAAQ,GAAG,mBAAmB,CAAC,QAAQ,CAAC,GAAG,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,EACpE,UAAU,CACV,CAAC;CACF,KACC,UAAU,CACZ,eAAe,CACd,QAAQ,EACR,aAAa,CAAC,YAAY,CAAC,EAC3B,YAAY,CAAC,YAAY,CAAC,EAC1B,SAAS,CACT,EACD,QAAQ,GAAG,mBAAmB,CAAC,QAAQ,CAAC,GAAG,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,CA+CpE,CAAC"}
1
+ {"version":3,"file":"vitest.d.ts","sourceRoot":"","sources":["../../src/testing/vitest.ts"],"names":[],"mappings":"AACA,OAAO,EACN,KAAK,WAAW,EAChB,KAAK,mBAAmB,EAGxB,MAAM,kCAAkC,CAAC;AAC1C,OAAO,EACN,KAAK,aAAa,EAClB,KAAK,YAAY,EAGjB,MAAM,gCAAgC,CAAC;AACxC,OAAO,EAAE,MAAM,EAAE,KAAK,KAAK,EAAE,MAAM,QAAQ,CAAC;AAC5C,OAAO,KAAK,EACX,qBAAqB,EACrB,eAAe,EACf,UAAU,EACV,MAAM,YAAY,CAAC;AAEpB,eAAO,MAAM,cAAc,GACzB,KAAK,CAAC,QAAQ,SAAS,WAAW,EAAE,UAAU,QAAQ,MAEtD,YAAY,SAAS,CAAC,GAAG,UAAU,EAAE,KAAK,EAAE,KAAK,MAAM,EACvD,QAAQ,EACR,UAAU,EACV,SAAS,SAAS,MAAM,GAAG,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,EAE/C,SAAS,qBAAqB,CAC7B,QAAQ,EACR,YAAY,EACZ,QAAQ,EACR,UAAU,EACV,SAAS,CACT,GAAG;IACH,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC,KAAK,CAC1B,QAAQ,GAAG,mBAAmB,CAAC,QAAQ,CAAC,GAAG,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,EACpE,UAAU,CACV,CAAC;CACF,KACC,UAAU,CACZ,eAAe,CACd,QAAQ,EACR,aAAa,CAAC,YAAY,CAAC,EAC3B,YAAY,CAAC,YAAY,CAAC,EAC1B,SAAS,CACT,EACD,QAAQ,GAAG,mBAAmB,CAAC,QAAQ,CAAC,GAAG,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAoDpE,CAAC"}
@@ -8,12 +8,14 @@ export const makePlatformIt = (database) => (options) => {
8
8
  layer: options.layer,
9
9
  around: (effect) => withTestTransaction(database, effect),
10
10
  makeHarness: (trpc, context) => Effect.gen(function* () {
11
+ const services = yield* Effect.context();
11
12
  const databaseEffect = database;
12
13
  const db = yield* databaseEffect;
13
14
  const extension = options.extend
14
15
  ? yield* options.extend({ db, trpc }, context)
15
16
  : {};
16
- return { ...extension, db, trpc };
17
+ const promise = (evaluate) => Effect.tryPromise(() => options.adapter.runWithServices(services, evaluate));
18
+ return { ...extension, db, promise, trpc };
17
19
  }),
18
20
  });
19
21
  return new Proxy(trpcIt, {
@@ -1 +1 @@
1
- {"version":3,"file":"vitest.js","sourceRoot":"","sources":["../../src/testing/vitest.ts"],"names":[],"mappings":"AACA,OAAO,EAIN,mBAAmB,GACnB,MAAM,kCAAkC,CAAC;AAC1C,OAAO,EAIN,iBAAiB,GACjB,MAAM,gCAAgC,CAAC;AACxC,OAAO,EAAE,MAAM,EAAc,MAAM,QAAQ,CAAC;AAO5C,MAAM,CAAC,MAAM,cAAc,GAC1B,CAAqC,QAAkB,EAAE,EAAE,CAC3D,CAMC,OAWC,EASA,EAAE;IASH,MAAM,MAAM,GAAG,iBAAiB,CAI9B;QACD,OAAO,EAAE,OAAO,CAAC,OAAO;QACxB,YAAY,EAAE,OAAO,CAAC,YAAY;QAClC,KAAK,EAAE,OAAO,CAAC,KAAK;QACpB,MAAM,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,mBAAmB,CAAC,QAAQ,EAAE,MAAM,CAAC;QACzD,WAAW,EAAE,CACZ,IAA0C,EAC1C,OAAO,EACqC,EAAE,CAC9C,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;YACnB,MAAM,cAAc,GAAG,QAItB,CAAC;YACF,MAAM,EAAE,GAAG,KAAK,CAAC,CAAC,cAAc,CAAC;YACjC,MAAM,SAAS,GAAG,OAAO,CAAC,MAAM;gBAC/B,CAAC,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,OAAO,CAAC;gBAC9C,CAAC,CAAE,EAAgB,CAAC;YACrB,OAAO,EAAE,GAAG,SAAS,EAAE,EAAE,EAAE,IAAI,EAAa,CAAC;QAC9C,CAAC,CAA8C;KAChD,CAAC,CAAC;IAEH,OAAO,IAAI,KAAK,CAAC,MAAM,EAAE;QACxB,GAAG,CAAC,MAAM,EAAE,QAAQ,EAAE,QAAQ;YAC7B,IAAI,QAAQ,KAAK,WAAW,EAAE,CAAC;gBAC9B,OAAO,MAAM,CAAC,UAAU,CAAC;YAC1B,CAAC;YACD,OAAO,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC;QAChD,CAAC;KACD,CAEiC,CAAC;AACpC,CAAC,CAAC"}
1
+ {"version":3,"file":"vitest.js","sourceRoot":"","sources":["../../src/testing/vitest.ts"],"names":[],"mappings":"AACA,OAAO,EAIN,mBAAmB,GACnB,MAAM,kCAAkC,CAAC;AAC1C,OAAO,EAIN,iBAAiB,GACjB,MAAM,gCAAgC,CAAC;AACxC,OAAO,EAAE,MAAM,EAAc,MAAM,QAAQ,CAAC;AAO5C,MAAM,CAAC,MAAM,cAAc,GAC1B,CAAqC,QAAkB,EAAE,EAAE,CAC3D,CAMC,OAWC,EASA,EAAE;IASH,MAAM,MAAM,GAAG,iBAAiB,CAI9B;QACD,OAAO,EAAE,OAAO,CAAC,OAAO;QACxB,YAAY,EAAE,OAAO,CAAC,YAAY;QAClC,KAAK,EAAE,OAAO,CAAC,KAAK;QACpB,MAAM,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,mBAAmB,CAAC,QAAQ,EAAE,MAAM,CAAC;QACzD,WAAW,EAAE,CACZ,IAA0C,EAC1C,OAAO,EACqC,EAAE,CAC9C,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;YACnB,MAAM,QAAQ,GAAG,KAAK,CAAC,CAAC,MAAM,CAAC,OAAO,EAAY,CAAC;YACnD,MAAM,cAAc,GAAG,QAItB,CAAC;YACF,MAAM,EAAE,GAAG,KAAK,CAAC,CAAC,cAAc,CAAC;YACjC,MAAM,SAAS,GAAG,OAAO,CAAC,MAAM;gBAC/B,CAAC,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,OAAO,CAAC;gBAC9C,CAAC,CAAE,EAAgB,CAAC;YACrB,MAAM,OAAO,GAAG,CAAQ,QAA8B,EAAE,EAAE,CACzD,MAAM,CAAC,UAAU,CAAC,GAAG,EAAE,CACtB,OAAO,CAAC,OAAO,CAAC,eAAe,CAAC,QAAQ,EAAE,QAAQ,CAAC,CACnD,CAAC;YACH,OAAO,EAAE,GAAG,SAAS,EAAE,EAAE,EAAE,OAAO,EAAE,IAAI,EAAa,CAAC;QACvD,CAAC,CAA8C;KAChD,CAAC,CAAC;IAEH,OAAO,IAAI,KAAK,CAAC,MAAM,EAAE;QACxB,GAAG,CAAC,MAAM,EAAE,QAAQ,EAAE,QAAQ;YAC7B,IAAI,QAAQ,KAAK,WAAW,EAAE,CAAC;gBAC9B,OAAO,MAAM,CAAC,UAAU,CAAC;YAC1B,CAAC;YACD,OAAO,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC;QAChD,CAAC;KACD,CAEiC,CAAC;AACpC,CAAC,CAAC"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@shivaedev/platform",
3
- "version": "0.1.0",
4
- "description": "Opinionated Effect application testing for ShivaeDev services",
3
+ "version": "0.2.0",
4
+ "description": "Opinionated Effect application infrastructure for ShivaeDev services",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "repository": {
@@ -24,6 +24,18 @@
24
24
  "README.md"
25
25
  ],
26
26
  "exports": {
27
+ "./better-auth": {
28
+ "types": "./dist/better-auth.d.ts",
29
+ "source": "./src/better-auth.ts",
30
+ "import": "./dist/better-auth.js",
31
+ "default": "./dist/better-auth.js"
32
+ },
33
+ "./runtime": {
34
+ "types": "./dist/runtime.d.ts",
35
+ "source": "./src/runtime.ts",
36
+ "import": "./dist/runtime.js",
37
+ "default": "./dist/runtime.js"
38
+ },
27
39
  "./testing": {
28
40
  "types": "./dist/testing.d.ts",
29
41
  "source": "./src/testing.ts",
@@ -38,22 +50,30 @@
38
50
  },
39
51
  "peerDependencies": {
40
52
  "@effect/vitest": "4.0.0-beta.102",
41
- "@shivaedev/effect-prisma": "0.3.0",
42
- "@shivaedev/effect-trpc": "0.2.0",
53
+ "@shivaedev/effect-prisma": "0.4.0",
54
+ "@shivaedev/effect-trpc": "0.3.0",
43
55
  "@trpc/server": "11.18.0",
56
+ "better-auth": "1.6.25",
44
57
  "effect": "4.0.0-beta.102"
45
58
  },
59
+ "peerDependenciesMeta": {
60
+ "better-auth": {
61
+ "optional": true
62
+ }
63
+ },
46
64
  "devDependencies": {
47
65
  "@effect/vitest": "4.0.0-beta.102",
48
66
  "@prisma-next/adapter-postgres": "0.15.0",
49
67
  "@prisma-next/contract": "0.15.0",
68
+ "@prisma-next/postgres": "0.15.0",
50
69
  "@prisma-next/sql-contract": "0.15.0",
51
70
  "@prisma-next/target-postgres": "0.15.0",
52
- "@shivaedev/effect-prisma": "0.3.0",
53
- "@shivaedev/effect-trpc": "0.2.0",
71
+ "@shivaedev/effect-prisma": "0.4.0",
72
+ "@shivaedev/effect-trpc": "0.3.0",
54
73
  "@trpc/server": "11.18.0",
55
74
  "@types/node": "24.10.1",
56
75
  "@typescript/native": "npm:typescript@7.0.2",
76
+ "better-auth": "1.6.25",
57
77
  "effect": "4.0.0-beta.102",
58
78
  "typescript": "npm:@typescript/typescript6@6.0.2",
59
79
  "vitest": "4.1.9"
@@ -0,0 +1,226 @@
1
+ import type { AnyDatabase, DatabaseServiceOf } from "@shivaedev/effect-prisma";
2
+ import type { BetterAuthOptions, DBAdapterInstance, Where } from "better-auth";
3
+ import { type CustomAdapter, createAdapterFactory } from "better-auth/adapters";
4
+ import { Effect, Option } from "effect";
5
+ import type { PlatformRuntime } from "../runtime/types.js";
6
+ import { type DynamicRelation, refineRelation } from "./relation.js";
7
+
8
+ type CleanedWhere = Required<Where>;
9
+ type FindOneInput = Parameters<CustomAdapter["findOne"]>[0];
10
+ type FindManyInput = Parameters<CustomAdapter["findMany"]>[0];
11
+ type UpdateInput<Value> = {
12
+ readonly model: string;
13
+ readonly update: Value;
14
+ readonly where: CleanedWhere[];
15
+ };
16
+
17
+ export interface EffectPrismaAdapterOptions {
18
+ readonly debugLogs?: boolean;
19
+ /** Map Better Auth model names to Prisma contract model names. */
20
+ readonly modelName?: (model: string) => string;
21
+ readonly usePlural?: boolean;
22
+ }
23
+
24
+ interface DynamicDatabase<Requirements> {
25
+ readonly transaction: <A, E, R>(
26
+ program: Effect.Effect<A, E, R>,
27
+ ) => Effect.Effect<A, E | unknown, R | Requirements>;
28
+ readonly [model: string]: DynamicRelation<Requirements> | unknown;
29
+ }
30
+
31
+ const modelRelation = <Requirements>(
32
+ database: DynamicDatabase<Requirements>,
33
+ model: string,
34
+ mapModelName: (model: string) => string,
35
+ ): DynamicRelation<Requirements> =>
36
+ Reflect.get(database, mapModelName(model)) as DynamicRelation<Requirements>;
37
+
38
+ const defaultModelName = (model: string): string =>
39
+ model.length === 0 ? model : `${model[0]?.toUpperCase()}${model.slice(1)}`;
40
+
41
+ const rejectJoin = (join: unknown): void => {
42
+ if (join !== undefined) {
43
+ throw new TypeError(
44
+ "The Effect Prisma Better Auth adapter does not support experimental native joins",
45
+ );
46
+ }
47
+ };
48
+
49
+ export const effectPrismaAdapter =
50
+ <Database extends AnyDatabase, Services, BuildError>(
51
+ databaseTag: Database,
52
+ runtime: PlatformRuntime<Services, BuildError>,
53
+ adapterOptions: EffectPrismaAdapterOptions = {},
54
+ ): DBAdapterInstance =>
55
+ (authOptions: BetterAuthOptions) => {
56
+ const mapModelName = adapterOptions.modelName ?? defaultModelName;
57
+ const databaseEffect = databaseTag as Effect.Effect<
58
+ DatabaseServiceOf<Database>,
59
+ never,
60
+ Services
61
+ >;
62
+
63
+ const run = <Value>(
64
+ operation: (
65
+ database: DynamicDatabase<Services>,
66
+ ) => Effect.Effect<Value, unknown, Services>,
67
+ ): Promise<Value> =>
68
+ runtime.runPromise(
69
+ Effect.flatMap(databaseEffect, (database) =>
70
+ operation(database as DynamicDatabase<Services>),
71
+ ),
72
+ );
73
+
74
+ let factory: ReturnType<typeof createAdapterFactory>;
75
+ factory = createAdapterFactory({
76
+ config: {
77
+ adapterId: "effect-prisma",
78
+ adapterName: "Effect Prisma",
79
+ debugLogs: adapterOptions.debugLogs ?? false,
80
+ supportsArrays: true,
81
+ supportsBooleans: true,
82
+ supportsDates: true,
83
+ supportsJSON: true,
84
+ supportsNumericIds: true,
85
+ supportsUUIDs: true,
86
+ transaction: (callback) =>
87
+ run((database) =>
88
+ database.transaction(
89
+ Effect.flatMap(Effect.context<Services>(), (services) =>
90
+ Effect.tryPromise({
91
+ try: () =>
92
+ runtime.runWithServices(services, () =>
93
+ callback(factory(authOptions)),
94
+ ),
95
+ catch: (error) => error,
96
+ }),
97
+ ),
98
+ ),
99
+ ),
100
+ },
101
+ adapter: ({ debugLog, getFieldAttributes }) => ({
102
+ create: async ({ data, model, select }) => {
103
+ debugLog("create", { model });
104
+ return (await run((database) =>
105
+ refineRelation(modelRelation(database, model, mapModelName), {
106
+ select,
107
+ }).create(data),
108
+ )) as typeof data;
109
+ },
110
+ findOne: async <Value>({
111
+ join,
112
+ model,
113
+ select,
114
+ where,
115
+ }: FindOneInput) => {
116
+ rejectJoin(join);
117
+ return (await run((database) =>
118
+ Effect.map(
119
+ refineRelation(modelRelation(database, model, mapModelName), {
120
+ select,
121
+ where,
122
+ }).first(),
123
+ Option.getOrNull,
124
+ ),
125
+ )) as Value | null;
126
+ },
127
+ findMany: async <Value>({
128
+ join,
129
+ limit,
130
+ model,
131
+ offset,
132
+ select,
133
+ sortBy,
134
+ where,
135
+ }: FindManyInput) => {
136
+ rejectJoin(join);
137
+ return (await run(
138
+ (database) =>
139
+ refineRelation(modelRelation(database, model, mapModelName), {
140
+ limit,
141
+ offset,
142
+ select,
143
+ sortBy,
144
+ where,
145
+ }) as unknown as Effect.Effect<
146
+ ReadonlyArray<Record<string, unknown>>,
147
+ unknown,
148
+ Services
149
+ >,
150
+ )) as Value[];
151
+ },
152
+ update: async <Value>({ model, update, where }: UpdateInput<Value>) => {
153
+ if (where.length === 0) return null;
154
+ const hasUniqueCondition = where.some(
155
+ (condition) =>
156
+ condition.connector !== "OR" &&
157
+ condition.operator === "eq" &&
158
+ condition.mode !== "insensitive" &&
159
+ (condition.field === "id" ||
160
+ getFieldAttributes({
161
+ field: condition.field,
162
+ model,
163
+ }).unique === true),
164
+ );
165
+ return (await run((database) => {
166
+ const relation = refineRelation(
167
+ modelRelation(database, model, mapModelName),
168
+ { where },
169
+ );
170
+ return hasUniqueCondition
171
+ ? relation.update(update as Record<string, unknown>)
172
+ : Effect.map(
173
+ relation.updateAll(update as Record<string, unknown>),
174
+ (rows) => rows[0] ?? null,
175
+ );
176
+ })) as Value | null;
177
+ },
178
+ updateMany: ({ model, update, where }) =>
179
+ run((database) =>
180
+ Effect.map(
181
+ refineRelation(modelRelation(database, model, mapModelName), {
182
+ where,
183
+ }).updateAll(update),
184
+ (rows) => rows.length,
185
+ ),
186
+ ),
187
+ delete: ({ model, where }) => {
188
+ const hasId = where.some(
189
+ (condition) =>
190
+ condition.connector !== "OR" &&
191
+ condition.field === "id" &&
192
+ condition.operator === "eq",
193
+ );
194
+ return run((database) => {
195
+ const relation = refineRelation(
196
+ modelRelation(database, model, mapModelName),
197
+ { where },
198
+ );
199
+ return hasId
200
+ ? Effect.asVoid(relation.delete())
201
+ : Effect.asVoid(relation.deleteAll());
202
+ });
203
+ },
204
+ deleteMany: ({ model, where }) =>
205
+ run((database) =>
206
+ Effect.map(
207
+ refineRelation(modelRelation(database, model, mapModelName), {
208
+ where,
209
+ }).deleteAll(),
210
+ (rows) => rows.length,
211
+ ),
212
+ ),
213
+ count: ({ model, where }) =>
214
+ run((database) =>
215
+ refineRelation(modelRelation(database, model, mapModelName), {
216
+ where: where as CleanedWhere[] | undefined,
217
+ }).count(),
218
+ ),
219
+ options: {
220
+ usePlural: adapterOptions.usePlural ?? false,
221
+ },
222
+ }),
223
+ });
224
+
225
+ return factory(authOptions);
226
+ };
@@ -0,0 +1,199 @@
1
+ import { all, and, not, or } from "@shivaedev/effect-prisma";
2
+ import type { Where } from "better-auth";
3
+ import type { Effect, Option } from "effect";
4
+
5
+ type CleanedWhere = Required<Where>;
6
+ type Expression = Parameters<typeof and>[number];
7
+
8
+ interface DynamicField {
9
+ eq(value: unknown): Expression;
10
+ neq(value: unknown): Expression;
11
+ gt(value: unknown): Expression;
12
+ gte(value: unknown): Expression;
13
+ lt(value: unknown): Expression;
14
+ lte(value: unknown): Expression;
15
+ in(value: ReadonlyArray<unknown>): Expression;
16
+ notIn(value: ReadonlyArray<unknown>): Expression;
17
+ like(value: string): Expression;
18
+ ilike?(value: string): Expression;
19
+ isNull(): Expression;
20
+ isNotNull(): Expression;
21
+ asc(): unknown;
22
+ desc(): unknown;
23
+ }
24
+
25
+ export interface DynamicRelation<Requirements> {
26
+ where(
27
+ predicate: (fields: Record<string, DynamicField>) => Expression,
28
+ ): DynamicRelation<Requirements>;
29
+ orderBy(
30
+ ordering: (fields: Record<string, DynamicField>) => unknown,
31
+ ): DynamicRelation<Requirements>;
32
+ take(count: number): DynamicRelation<Requirements>;
33
+ skip(count: number): DynamicRelation<Requirements>;
34
+ select(...fields: ReadonlyArray<string>): DynamicRelation<Requirements>;
35
+ first(): Effect.Effect<
36
+ Option.Option<Record<string, unknown>>,
37
+ unknown,
38
+ Requirements
39
+ >;
40
+ count(): Effect.Effect<number, unknown, Requirements>;
41
+ create(
42
+ data: Record<string, unknown>,
43
+ ): Effect.Effect<Record<string, unknown>, unknown, Requirements>;
44
+ update(
45
+ data: Record<string, unknown>,
46
+ ): Effect.Effect<Record<string, unknown> | null, unknown, Requirements>;
47
+ updateAll(
48
+ data: Record<string, unknown>,
49
+ ): Effect.Effect<
50
+ ReadonlyArray<Record<string, unknown>>,
51
+ unknown,
52
+ Requirements
53
+ >;
54
+ delete(): Effect.Effect<
55
+ Record<string, unknown> | null,
56
+ unknown,
57
+ Requirements
58
+ >;
59
+ deleteAll(): Effect.Effect<
60
+ ReadonlyArray<Record<string, unknown>>,
61
+ unknown,
62
+ Requirements
63
+ >;
64
+ }
65
+
66
+ const escapeLike = (value: string): string =>
67
+ value.replaceAll("\\", "\\\\").replaceAll("%", "\\%").replaceAll("_", "\\_");
68
+
69
+ const comparison = (
70
+ field: DynamicField,
71
+ where: CleanedWhere,
72
+ ): Expression | undefined => {
73
+ const value = where.value;
74
+ if (
75
+ where.mode === "insensitive" &&
76
+ typeof value === "string" &&
77
+ field.ilike !== undefined
78
+ ) {
79
+ if (where.operator === "eq") return field.ilike(escapeLike(value));
80
+ if (where.operator === "ne") return not(field.ilike(escapeLike(value)));
81
+ }
82
+ switch (where.operator) {
83
+ case "eq":
84
+ return value === null ? field.isNull() : field.eq(value);
85
+ case "ne":
86
+ return value === null ? field.isNotNull() : field.neq(value);
87
+ case "lt":
88
+ return field.lt(value);
89
+ case "lte":
90
+ return field.lte(value);
91
+ case "gt":
92
+ return field.gt(value);
93
+ case "gte":
94
+ return field.gte(value);
95
+ case "in": {
96
+ const values = (Array.isArray(value) ? value : [value]).filter(
97
+ (item) => item !== null,
98
+ );
99
+ const ilike = field.ilike;
100
+ return values.length === 0
101
+ ? and(field.isNull(), field.isNotNull())
102
+ : where.mode === "insensitive" &&
103
+ ilike !== undefined &&
104
+ values.every((item) => typeof item === "string")
105
+ ? or(...values.map((item) => ilike(escapeLike(String(item)))))
106
+ : field.in(values);
107
+ }
108
+ case "not_in": {
109
+ const values = (Array.isArray(value) ? value : [value]).filter(
110
+ (item) => item !== null,
111
+ );
112
+ if (values.length === 0) return undefined;
113
+ const ilike = field.ilike;
114
+ return where.mode === "insensitive" &&
115
+ ilike !== undefined &&
116
+ values.every((item) => typeof item === "string")
117
+ ? and(...values.map((item) => not(ilike(escapeLike(String(item))))))
118
+ : field.notIn(values);
119
+ }
120
+ case "contains":
121
+ case "starts_with":
122
+ case "ends_with": {
123
+ if (typeof value !== "string") {
124
+ throw new TypeError(`${where.operator} requires a string value`);
125
+ }
126
+ const escaped = escapeLike(value);
127
+ const pattern =
128
+ where.operator === "contains"
129
+ ? `%${escaped}%`
130
+ : where.operator === "starts_with"
131
+ ? `${escaped}%`
132
+ : `%${escaped}`;
133
+ return where.mode === "insensitive" && field.ilike !== undefined
134
+ ? field.ilike(pattern)
135
+ : field.like(pattern);
136
+ }
137
+ }
138
+ };
139
+
140
+ const whereExpression = (
141
+ fields: Record<string, DynamicField>,
142
+ where: ReadonlyArray<CleanedWhere>,
143
+ ): Expression | undefined => {
144
+ const conjunctions: Expression[] = [];
145
+ const disjunctions: Expression[] = [];
146
+
147
+ for (const condition of where) {
148
+ const field = fields[condition.field];
149
+ if (field === undefined) {
150
+ throw new TypeError(`Unknown database field: ${condition.field}`);
151
+ }
152
+ const expression = comparison(field, condition);
153
+ if (expression === undefined) continue;
154
+ (condition.connector === "OR" ? disjunctions : conjunctions).push(
155
+ expression,
156
+ );
157
+ }
158
+
159
+ if (disjunctions.length > 0) conjunctions.push(or(...disjunctions));
160
+ if (conjunctions.length === 0) return all();
161
+ return conjunctions.length === 1 ? conjunctions[0] : and(...conjunctions);
162
+ };
163
+
164
+ export const refineRelation = <Requirements>(
165
+ relation: DynamicRelation<Requirements>,
166
+ options: {
167
+ readonly where?: ReadonlyArray<CleanedWhere>;
168
+ readonly select?: ReadonlyArray<string>;
169
+ readonly limit?: number;
170
+ readonly offset?: number;
171
+ readonly sortBy?: {
172
+ readonly direction: "asc" | "desc";
173
+ readonly field: string;
174
+ };
175
+ },
176
+ ): DynamicRelation<Requirements> => {
177
+ let refined = relation;
178
+ if (options.where !== undefined) {
179
+ refined = refined.where((fields) => {
180
+ const expression = whereExpression(fields, options.where ?? []);
181
+ return expression ?? all();
182
+ });
183
+ }
184
+ if (options.sortBy !== undefined) {
185
+ refined = refined.orderBy((fields) => {
186
+ const field = fields[options.sortBy?.field ?? ""];
187
+ if (field === undefined) {
188
+ throw new TypeError(`Unknown database field: ${options.sortBy?.field}`);
189
+ }
190
+ return options.sortBy?.direction === "desc" ? field.desc() : field.asc();
191
+ });
192
+ }
193
+ if (options.offset !== undefined) refined = refined.skip(options.offset);
194
+ if (options.limit !== undefined) refined = refined.take(options.limit);
195
+ if (options.select !== undefined && options.select.length > 0) {
196
+ refined = refined.select(...options.select);
197
+ }
198
+ return refined;
199
+ };
@@ -0,0 +1,4 @@
1
+ export {
2
+ type EffectPrismaAdapterOptions,
3
+ effectPrismaAdapter,
4
+ } from "./better-auth/adapter.js";
@@ -0,0 +1,84 @@
1
+ import { AsyncLocalStorage } from "node:async_hooks";
2
+ import type { Layer } from "effect";
3
+ import { Cause, Context, Effect, Exit, ManagedRuntime } from "effect";
4
+ import type { PlatformRuntime, PlatformRuntimeOptions } from "./types.js";
5
+
6
+ const RuntimeCacheKey = Symbol.for("@shivaedev/platform/runtime-cache");
7
+ const globalRuntimeCache = globalThis as typeof globalThis & {
8
+ [RuntimeCacheKey]?: Map<string | symbol, PlatformRuntime<unknown, unknown>>;
9
+ };
10
+ const runtimeCache =
11
+ globalRuntimeCache[RuntimeCacheKey] ??
12
+ new Map<string | symbol, PlatformRuntime<unknown, unknown>>();
13
+ globalRuntimeCache[RuntimeCacheKey] = runtimeCache;
14
+
15
+ const make = <Services, BuildError>(
16
+ layer: Layer.Layer<Services, BuildError>,
17
+ ): PlatformRuntime<Services, BuildError> => {
18
+ const managed = ManagedRuntime.make(layer);
19
+ const services = new AsyncLocalStorage<Context.Context<never>>();
20
+
21
+ const currentServices = () => services.getStore();
22
+ const withAmbient = <A, E, Requirements extends Services>(
23
+ effect: Effect.Effect<A, E, Requirements>,
24
+ ): Effect.Effect<A, E | BuildError, Requirements> => {
25
+ const ambient = currentServices();
26
+ return ambient === undefined
27
+ ? effect
28
+ : Effect.flatMap(managed.contextEffect, (base) =>
29
+ Effect.provideContext(effect, Context.merge(base, ambient)),
30
+ );
31
+ };
32
+
33
+ const runPromiseExit: PlatformRuntime<
34
+ Services,
35
+ BuildError
36
+ >["runPromiseExit"] = (effect, options) =>
37
+ managed.runPromiseExit(withAmbient(effect), options);
38
+
39
+ const runtime: PlatformRuntime<Services, BuildError> = {
40
+ contextEffect: Effect.map(managed.contextEffect, (base) => {
41
+ const ambient = currentServices();
42
+ return ambient === undefined ? base : Context.merge(base, ambient);
43
+ }),
44
+ currentServices,
45
+ dispose: () => managed.dispose(),
46
+ runPromise: async (effect, options) => {
47
+ const exit = await runPromiseExit(effect, options);
48
+ if (Exit.isSuccess(exit)) return exit.value;
49
+ throw Cause.squash(exit.cause);
50
+ },
51
+ runPromiseExit,
52
+ runWithServices: (context, evaluate) =>
53
+ services.run(context as Context.Context<never>, evaluate),
54
+ };
55
+
56
+ return runtime;
57
+ };
58
+
59
+ export const makePlatformRuntime = <Services, BuildError>(
60
+ layer: Layer.Layer<Services, BuildError>,
61
+ options: PlatformRuntimeOptions = {},
62
+ ): PlatformRuntime<Services, BuildError> => {
63
+ const key =
64
+ process.env.NODE_ENV === "production"
65
+ ? undefined
66
+ : options.developmentCacheKey;
67
+ if (key === undefined) return make(layer);
68
+
69
+ const cached = runtimeCache.get(key);
70
+ if (cached !== undefined) {
71
+ return cached as PlatformRuntime<Services, BuildError>;
72
+ }
73
+
74
+ const runtime = make(layer);
75
+ const cachedRuntime: PlatformRuntime<Services, BuildError> = {
76
+ ...runtime,
77
+ dispose: async () => {
78
+ if (runtimeCache.get(key) === cachedRuntime) runtimeCache.delete(key);
79
+ await runtime.dispose();
80
+ },
81
+ };
82
+ runtimeCache.set(key, cachedRuntime as PlatformRuntime<unknown, unknown>);
83
+ return cachedRuntime;
84
+ };
@@ -0,0 +1,34 @@
1
+ import type { Context, Effect, Exit } from "effect";
2
+
3
+ export interface PlatformRuntimeOptions {
4
+ /** Reuse one runtime across development module reloads. */
5
+ readonly developmentCacheKey?: string | symbol;
6
+ }
7
+
8
+ export interface PlatformRuntime<Services, BuildError> {
9
+ readonly contextEffect: Effect.Effect<Context.Context<Services>, BuildError>;
10
+ readonly currentServices: () => Context.Context<never> | undefined;
11
+ readonly dispose: () => Promise<void>;
12
+ readonly runPromise: <A, E, Requirements extends Services>(
13
+ effect: Effect.Effect<A, E, Requirements>,
14
+ options?: { readonly signal?: AbortSignal },
15
+ ) => Promise<A>;
16
+ readonly runPromiseExit: <A, E, Requirements extends Services>(
17
+ effect: Effect.Effect<A, E, Requirements>,
18
+ options?: { readonly signal?: AbortSignal },
19
+ ) => Promise<Exit.Exit<A, E | BuildError>>;
20
+ readonly runWithServices: <Provided, Value>(
21
+ services: Context.Context<Provided>,
22
+ evaluate: () => Value,
23
+ ) => Value;
24
+ }
25
+
26
+ export type PlatformRuntimeServices<Runtime> =
27
+ Runtime extends PlatformRuntime<infer Services, infer _BuildError>
28
+ ? Services
29
+ : never;
30
+
31
+ export type PlatformRuntimeBuildError<Runtime> =
32
+ Runtime extends PlatformRuntime<infer _Services, infer BuildError>
33
+ ? BuildError
34
+ : never;
package/src/runtime.ts ADDED
@@ -0,0 +1,7 @@
1
+ export { makePlatformRuntime } from "./runtime/make.js";
2
+ export type {
3
+ PlatformRuntime,
4
+ PlatformRuntimeBuildError,
5
+ PlatformRuntimeOptions,
6
+ PlatformRuntimeServices,
7
+ } from "./runtime/types.js";
@@ -23,7 +23,12 @@ export type PlatformHarness<
23
23
  db: DatabaseService<Database>;
24
24
  trpc: EffectCallerFactory<Options, Caller>;
25
25
  }> &
26
- Omit<Extension, "db" | "trpc">;
26
+ Readonly<{
27
+ promise: <Value>(
28
+ evaluate: () => Promise<Value>,
29
+ ) => Effect.Effect<Value, unknown>;
30
+ }> &
31
+ Omit<Extension, "db" | "promise" | "trpc">;
27
32
 
28
33
  export type PlatformTest<Harness, Provided> = TrpcHarnessTest<
29
34
  Harness,
@@ -69,6 +69,7 @@ export const makePlatformIt =
69
69
  context,
70
70
  ): Effect.Effect<Harness, unknown, Services> =>
71
71
  Effect.gen(function* () {
72
+ const services = yield* Effect.context<Services>();
72
73
  const databaseEffect = database as unknown as Effect.Effect<
73
74
  DatabaseService<Database>,
74
75
  never,
@@ -78,7 +79,11 @@ export const makePlatformIt =
78
79
  const extension = options.extend
79
80
  ? yield* options.extend({ db, trpc }, context)
80
81
  : ({} as Extension);
81
- return { ...extension, db, trpc } as Harness;
82
+ const promise = <Value>(evaluate: () => Promise<Value>) =>
83
+ Effect.tryPromise(() =>
84
+ options.adapter.runWithServices(services, evaluate),
85
+ );
86
+ return { ...extension, db, promise, trpc } as Harness;
82
87
  }) as Effect.Effect<Harness, unknown, Services>,
83
88
  });
84
89