@shivaedev/test-sql 0.0.0-stage → 0.9.3

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 (64) hide show
  1. package/CHANGELOG.md +225 -0
  2. package/LICENSE +21 -0
  3. package/README.md +257 -2
  4. package/dist/server/assertModelColumns.d.ts +13 -0
  5. package/dist/server/assertModelColumns.d.ts.map +1 -0
  6. package/dist/server/assertModelColumns.js +11 -0
  7. package/dist/server/assertModelColumns.js.map +1 -0
  8. package/dist/server/database.d.ts +18 -0
  9. package/dist/server/database.d.ts.map +1 -0
  10. package/dist/server/database.js +4 -0
  11. package/dist/server/database.js.map +1 -0
  12. package/dist/server/internal/connectionLimitHint.d.ts +3 -0
  13. package/dist/server/internal/connectionLimitHint.d.ts.map +1 -0
  14. package/dist/server/internal/connectionLimitHint.js +15 -0
  15. package/dist/server/internal/connectionLimitHint.js.map +1 -0
  16. package/dist/server/internal/databaseCleanup.d.ts +7 -0
  17. package/dist/server/internal/databaseCleanup.d.ts.map +1 -0
  18. package/dist/server/internal/databaseCleanup.js +34 -0
  19. package/dist/server/internal/databaseCleanup.js.map +1 -0
  20. package/dist/server/internal/databaseOwner.d.ts +3 -0
  21. package/dist/server/internal/databaseOwner.d.ts.map +1 -0
  22. package/dist/server/internal/databaseOwner.js +24 -0
  23. package/dist/server/internal/databaseOwner.js.map +1 -0
  24. package/dist/server/internal/databases.d.ts +13 -0
  25. package/dist/server/internal/databases.d.ts.map +1 -0
  26. package/dist/server/internal/databases.js +26 -0
  27. package/dist/server/internal/databases.js.map +1 -0
  28. package/dist/server/internal/modelColumnProblems.d.ts +8 -0
  29. package/dist/server/internal/modelColumnProblems.d.ts.map +1 -0
  30. package/dist/server/internal/modelColumnProblems.js +35 -0
  31. package/dist/server/internal/modelColumnProblems.js.map +1 -0
  32. package/dist/server/migrationSteps.d.ts +8 -0
  33. package/dist/server/migrationSteps.d.ts.map +1 -0
  34. package/dist/server/migrationSteps.js +51 -0
  35. package/dist/server/migrationSteps.js.map +1 -0
  36. package/dist/server/migrationUpgrade.d.ts +14 -0
  37. package/dist/server/migrationUpgrade.d.ts.map +1 -0
  38. package/dist/server/migrationUpgrade.js +34 -0
  39. package/dist/server/migrationUpgrade.js.map +1 -0
  40. package/dist/server/postgres.d.ts +22 -0
  41. package/dist/server/postgres.d.ts.map +1 -0
  42. package/dist/server/postgres.js +43 -0
  43. package/dist/server/postgres.js.map +1 -0
  44. package/dist/server/withIsolatedDatabase.d.ts +6 -0
  45. package/dist/server/withIsolatedDatabase.d.ts.map +1 -0
  46. package/dist/server/withIsolatedDatabase.js +11 -0
  47. package/dist/server/withIsolatedDatabase.js.map +1 -0
  48. package/dist/server/withTestTransaction.d.ts +4 -0
  49. package/dist/server/withTestTransaction.d.ts.map +1 -0
  50. package/dist/server/withTestTransaction.js +10 -0
  51. package/dist/server/withTestTransaction.js.map +1 -0
  52. package/package.json +75 -3
  53. package/src/server/assertModelColumns.ts +19 -0
  54. package/src/server/database.ts +21 -0
  55. package/src/server/internal/connectionLimitHint.ts +16 -0
  56. package/src/server/internal/databaseCleanup.ts +40 -0
  57. package/src/server/internal/databaseOwner.ts +25 -0
  58. package/src/server/internal/databases.ts +47 -0
  59. package/src/server/internal/modelColumnProblems.ts +45 -0
  60. package/src/server/migrationSteps.ts +54 -0
  61. package/src/server/migrationUpgrade.ts +45 -0
  62. package/src/server/postgres.ts +72 -0
  63. package/src/server/withIsolatedDatabase.ts +13 -0
  64. package/src/server/withTestTransaction.ts +17 -0
@@ -0,0 +1,34 @@
1
+ import { Clock, Effect, Fiber, Predicate, Schedule } from "effect";
2
+ import { abandonedDatabase } from "./databaseOwner.js";
3
+ import { administer } from "./databases.js";
4
+ export const databaseCleanup = Effect.fn("PostgresTest.databaseCleanup")(function* (url) {
5
+ const pending = [];
6
+ function drop(name) {
7
+ return administer(url, (admin) => admin `drop database if exists ${admin(name)} with (force)`).pipe(Effect.retry({
8
+ schedule: Schedule.spaced("100 millis"),
9
+ times: 300,
10
+ while: (error) => Predicate.hasProperty(error.reason.cause, "code") && error.reason.cause.code === "53300",
11
+ }), Effect.provideService(Clock.Clock, Clock.Clock.defaultValue()));
12
+ }
13
+ const awaitCleanup = Effect.suspend(() => Effect.gen(function* () {
14
+ const exits = yield* Effect.forEach([...pending], Fiber.join);
15
+ yield* Effect.forEach(exits, (exit) => exit).pipe(Effect.orDie);
16
+ }));
17
+ yield* Effect.addFinalizer(() => awaitCleanup);
18
+ const leftovers = yield* administer(url, (admin) => admin `select datname from pg_database`);
19
+ for (const { datname } of leftovers) {
20
+ if (abandonedDatabase(datname)) {
21
+ yield* drop(datname);
22
+ }
23
+ }
24
+ function release(name) {
25
+ return Effect.gen(function* () {
26
+ const previous = pending.at(-1);
27
+ const ready = previous === undefined ? Effect.void : Fiber.join(previous);
28
+ const fiber = yield* Effect.andThen(ready, Effect.exit(drop(name))).pipe(Effect.uninterruptible, Effect.forkDetach);
29
+ pending.push(fiber);
30
+ });
31
+ }
32
+ return { awaitCleanup, release };
33
+ });
34
+ //# sourceMappingURL=databaseCleanup.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"databaseCleanup.js","sourceRoot":"","sources":["../../../src/server/internal/databaseCleanup.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ export declare function databaseName(): string;
2
+ export declare function abandonedDatabase(name: string): boolean;
3
+ //# sourceMappingURL=databaseOwner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"databaseOwner.d.ts","sourceRoot":"","sources":["../../../src/server/internal/databaseOwner.ts"],"names":[],"mappings":"AAMA,wBAAgB,YAAY,WAE3B;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,WAc7C"}
@@ -0,0 +1,24 @@
1
+ import { createHash } from "node:crypto";
2
+ import { hostname } from "node:os";
3
+ import process from "node:process";
4
+ const host = createHash("sha256").update(hostname()).digest("hex").slice(0, 8);
5
+ export function databaseName() {
6
+ return `effect_test_${host}_${process.pid}_${crypto.randomUUID().replaceAll("-", "")}`;
7
+ }
8
+ export function abandonedDatabase(name) {
9
+ if (/^effect_test_[a-f0-9]{32}$/u.test(name)) {
10
+ return true;
11
+ }
12
+ const owner = /^effect_test_(?<host>[a-f0-9]{8})_(?<pid>[0-9]+)_[a-f0-9]{32}$/u.exec(name);
13
+ if (owner === null || owner.groups?.host !== host) {
14
+ return false;
15
+ }
16
+ try {
17
+ process.kill(Number(owner.groups.pid), 0);
18
+ return false;
19
+ }
20
+ catch (error) {
21
+ return error instanceof Error && "code" in error && error.code === "ESRCH";
22
+ }
23
+ }
24
+ //# sourceMappingURL=databaseOwner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"databaseOwner.js","sourceRoot":"","sources":["../../../src/server/internal/databaseOwner.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACnC,OAAO,OAAO,MAAM,cAAc,CAAC;AAEnC,MAAM,IAAI,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;AAE/E,MAAM,UAAU,YAAY;IAC3B,OAAO,eAAe,IAAI,IAAI,OAAO,CAAC,GAAG,IAAI,MAAM,CAAC,UAAU,EAAE,CAAC,UAAU,CAAC,GAAG,EAAE,EAAE,CAAC,EAAE,CAAC;AACxF,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,IAAY;IAC7C,IAAI,6BAA6B,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QAC9C,OAAO,IAAI,CAAC;IACb,CAAC;IACD,MAAM,KAAK,GAAG,iEAAiE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC3F,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,CAAC,MAAM,EAAE,IAAI,KAAK,IAAI,EAAE,CAAC;QACnD,OAAO,KAAK,CAAC;IACd,CAAC;IACD,IAAI,CAAC;QACJ,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;QAC1C,OAAO,KAAK,CAAC;IACd,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QAChB,OAAO,KAAK,YAAY,KAAK,IAAI,MAAM,IAAI,KAAK,IAAI,KAAK,CAAC,IAAI,KAAK,OAAO,CAAC;IAC5E,CAAC;AACF,CAAC"}
@@ -0,0 +1,13 @@
1
+ import * as PgClient from "@effect/sql-pg/PgClient";
2
+ import { Effect, Layer, Redacted } from "effect";
3
+ import * as Reactivity from "effect/reactivity/Reactivity";
4
+ import { SqlClient } from "effect/sql";
5
+ import type { SqlError } from "effect/sql/SqlError";
6
+ import type { SqlNameTransforms } from "@shivaedev/sql/clientOptions.ts";
7
+ export declare function connection(url: Redacted.Redacted<string>, maxConnections: number, transforms: SqlNameTransforms): Layer.Layer<PgClient.PgClient | Reactivity.Reactivity | SqlClient.SqlClient, SqlError, never>;
8
+ export declare const administer: <A>(url: Redacted.Redacted<string>, operation: (admin: PgClient.PgClient) => Effect.Effect<A, SqlError>) => Effect.Effect<A, SqlError, never>;
9
+ export declare const database: (url: Redacted.Redacted<string>, release: (name: string) => Effect.Effect<unknown>, template?: any) => Effect.Effect<{
10
+ name: string;
11
+ url: Redacted.Redacted<string>;
12
+ }, SqlError, import("effect/Scope").Scope>;
13
+ //# sourceMappingURL=databases.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"databases.d.ts","sourceRoot":"","sources":["../../../src/server/internal/databases.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,QAAQ,MAAM,yBAAyB,CAAC;AACpD,OAAO,EAAW,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAC;AAC1D,OAAO,KAAK,UAAU,MAAM,8BAA8B,CAAC;AAC3D,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AACvC,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iCAAiC,CAAC;AAIzE,wBAAgB,UAAU,CAAC,GAAG,EAAE,QAAQ,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,cAAc,EAAE,MAAM,EAAE,UAAU,EAAE,iBAAiB,iGAY/G;AAED,eAAO,MAAM,UAAU,GAAmD,CAAC,qDAEvD,QAAQ,CAAC,QAAQ,KAAK,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,QAAQ,CAAC,sCAKlE,CAAC;AAEH,eAAO,MAAM,QAAQ,mDAEJ,MAAM,KAAK,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC;;;0CAYhD,CAAC"}
@@ -0,0 +1,26 @@
1
+ import * as PgClient from "@effect/sql-pg/PgClient";
2
+ import { Context, Effect, Layer, Redacted } from "effect";
3
+ import * as Reactivity from "effect/reactivity/Reactivity";
4
+ import { SqlClient } from "effect/sql";
5
+ import { connectionLimitHint } from "./connectionLimitHint.js";
6
+ import { databaseName } from "./databaseOwner.js";
7
+ export function connection(url, maxConnections, transforms) {
8
+ return Layer.effectContext(Effect.map(PgClient.make({
9
+ maxConnections,
10
+ transformQueryNames: transforms.transformQueryNames,
11
+ transformResultNames: transforms.transformResultNames,
12
+ url,
13
+ }), (client) => Context.make(SqlClient.SqlClient, client).pipe(Context.add(PgClient.PgClient, client)))).pipe(Layer.provideMerge(Layer.fresh(Reactivity.layer)));
14
+ }
15
+ export const administer = Effect.fn("PostgresTest.administer")(function* (url, operation) {
16
+ return yield* Effect.scoped(Effect.flatMap(PgClient.makeClient({ url }), operation).pipe(Effect.mapError(connectionLimitHint))).pipe(Effect.provide(Reactivity.layer));
17
+ });
18
+ export const database = Effect.fn("PostgresTest.database")(function* (url, release, template = "template0") {
19
+ const name = databaseName();
20
+ const target = new URL(Redacted.value(url));
21
+ target.pathname = `/${name}`;
22
+ target.searchParams.delete("database");
23
+ yield* Effect.acquireRelease(administer(url, (admin) => admin `create database ${admin(name)} template ${admin(template)}`), () => release(name));
24
+ return { name, url: Redacted.make(target.toString()) };
25
+ });
26
+ //# sourceMappingURL=databases.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"databases.js","sourceRoot":"","sources":["../../../src/server/internal/databases.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,QAAQ,MAAM,yBAAyB,CAAC;AACpD,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAC;AAC1D,OAAO,KAAK,UAAU,MAAM,8BAA8B,CAAC;AAC3D,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAGvC,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAC/D,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAElD,MAAM,UAAU,UAAU,CAAC,GAA8B,EAAE,cAAsB,EAAE,UAA6B;IAC/G,OAAO,KAAK,CAAC,aAAa,CACzB,MAAM,CAAC,GAAG,CACT,QAAQ,CAAC,IAAI,CAAC;QACb,cAAc;QACd,mBAAmB,EAAE,UAAU,CAAC,mBAAmB;QACnD,oBAAoB,EAAE,UAAU,CAAC,oBAAoB;QACrD,GAAG;KACH,CAAC,EACF,CAAC,MAAM,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,CAClG,CACD,CAAC,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,KAAK,CAAC,KAAK,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AAC3D,CAAC;AAED,MAAM,CAAC,MAAM,UAAU,GAAG,MAAM,CAAC,EAAE,CAAC,yBAAyB,CAAC,CAAC,QAAQ,CAAC,EACvE,GAA8B,EAC9B,SAAmE;IAEnE,OAAO,KAAK,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,SAAS,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,mBAAmB,CAAC,CAAC,CAAC,CAAC,IAAI,CACnI,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,KAAK,CAAC,CAChC,CAAC;AACH,CAAC,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,QAAQ,GAAG,MAAM,CAAC,EAAE,CAAC,uBAAuB,CAAC,CAAC,QAAQ,CAAC,EACnE,GAA8B,EAC9B,OAAiD,EACjD,QAAQ,GAAG,WAAW;IAEtB,MAAM,IAAI,GAAG,YAAY,EAAE,CAAC;IAC5B,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC;IAC5C,MAAM,CAAC,QAAQ,GAAG,IAAI,IAAI,EAAE,CAAC;IAC7B,MAAM,CAAC,YAAY,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;IACvC,KAAK,CAAC,CAAC,MAAM,CAAC,cAAc,CAC3B,UAAU,CAAC,GAAG,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAA,mBAAmB,KAAK,CAAC,IAAI,CAAC,aAAa,KAAK,CAAC,QAAQ,CAAC,EAAE,CAAC,EAC7F,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CACnB,CAAC;IACF,OAAO,EAAE,IAAI,EAAE,GAAG,EAAE,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC,EAAE,CAAC;AACxD,CAAC,CAAC,CAAC"}
@@ -0,0 +1,8 @@
1
+ import type { PostgresColumn } from "#migrationUpgrade.ts";
2
+ interface NameTransforms {
3
+ readonly transformQueryNames?: ((name: string) => string) | undefined;
4
+ readonly transformResultNames?: ((name: string) => string) | undefined;
5
+ }
6
+ export declare function modelColumnProblems(columns: readonly PostgresColumn[], fields: object, expectedTypes: Readonly<Record<string, string>>, transforms: NameTransforms | undefined): readonly string[];
7
+ export {};
8
+ //# sourceMappingURL=modelColumnProblems.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"modelColumnProblems.d.ts","sourceRoot":"","sources":["../../../src/server/internal/modelColumnProblems.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAE3D,UAAU,cAAc;IACvB,QAAQ,CAAC,mBAAmB,CAAC,EAAE,CAAC,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC,GAAG,SAAS,CAAC;IACtE,QAAQ,CAAC,oBAAoB,CAAC,EAAE,CAAC,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC,GAAG,SAAS,CAAC;CACvE;AAED,wBAAgB,mBAAmB,CAClC,OAAO,EAAE,SAAS,cAAc,EAAE,EAClC,MAAM,EAAE,MAAM,EACd,aAAa,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,EAC/C,UAAU,EAAE,cAAc,GAAG,SAAS,GACpC,SAAS,MAAM,EAAE,CAgCnB"}
@@ -0,0 +1,35 @@
1
+ export function modelColumnProblems(columns, fields, expectedTypes, transforms) {
2
+ const queryFields = new Map(Object.keys(fields).map((name) => [transforms?.transformQueryNames?.(name) ?? name, name]));
3
+ function fieldName(name) {
4
+ return queryFields.get(name) ?? transforms?.transformResultNames?.(name) ?? name;
5
+ }
6
+ function columnKey(name) {
7
+ return transforms?.transformQueryNames === undefined ? fieldName(name) : name;
8
+ }
9
+ const actual = new Map(columns.map(({ name, type }) => [columnKey(name), type]));
10
+ const problems = [];
11
+ for (const name of Object.keys(fields)) {
12
+ const type = actual.get(transforms?.transformQueryNames?.(name) ?? name);
13
+ if (type === undefined) {
14
+ problems.push(`Missing column: ${name}`);
15
+ }
16
+ else if (type !== expectedTypes[name]) {
17
+ problems.push(`Wrong type for ${name}: expected ${expectedTypes[name]}, received ${type}`);
18
+ }
19
+ if (!Object.hasOwn(expectedTypes, name)) {
20
+ problems.push(`Missing expected PostgreSQL type: ${name}`);
21
+ }
22
+ }
23
+ for (const column of columns) {
24
+ if (!queryFields.has(columnKey(column.name))) {
25
+ problems.push(`Extra column: ${fieldName(column.name)}`);
26
+ }
27
+ }
28
+ for (const name of Object.keys(expectedTypes)) {
29
+ if (!Object.hasOwn(fields, name)) {
30
+ problems.push(`Extra expected PostgreSQL type: ${name}`);
31
+ }
32
+ }
33
+ return problems;
34
+ }
35
+ //# sourceMappingURL=modelColumnProblems.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"modelColumnProblems.js","sourceRoot":"","sources":["../../../src/server/internal/modelColumnProblems.ts"],"names":[],"mappings":"AAOA,MAAM,UAAU,mBAAmB,CAClC,OAAkC,EAClC,MAAc,EACd,aAA+C,EAC/C,UAAsC;IAEtC,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,UAAU,EAAE,mBAAmB,EAAE,CAAC,IAAI,CAAC,IAAI,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC;IACxH,SAAS,SAAS,CAAC,IAAY;QAC9B,OAAO,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,UAAU,EAAE,oBAAoB,EAAE,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC;IAClF,CAAC;IACD,SAAS,SAAS,CAAC,IAAY;QAC9B,OAAO,UAAU,EAAE,mBAAmB,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IAC/E,CAAC;IACD,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC;IACjF,MAAM,QAAQ,GAAa,EAAE,CAAC;IAC9B,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;QACxC,MAAM,IAAI,GAAG,MAAM,CAAC,GAAG,CAAC,UAAU,EAAE,mBAAmB,EAAE,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,CAAC;QACzE,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;YACxB,QAAQ,CAAC,IAAI,CAAC,mBAAmB,IAAI,EAAE,CAAC,CAAC;QAC1C,CAAC;aAAM,IAAI,IAAI,KAAK,aAAa,CAAC,IAAI,CAAC,EAAE,CAAC;YACzC,QAAQ,CAAC,IAAI,CAAC,kBAAkB,IAAI,cAAc,aAAa,CAAC,IAAI,CAAC,cAAc,IAAI,EAAE,CAAC,CAAC;QAC5F,CAAC;QACD,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,aAAa,EAAE,IAAI,CAAC,EAAE,CAAC;YACzC,QAAQ,CAAC,IAAI,CAAC,qCAAqC,IAAI,EAAE,CAAC,CAAC;QAC5D,CAAC;IACF,CAAC;IACD,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;QAC9B,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC;YAC9C,QAAQ,CAAC,IAAI,CAAC,iBAAiB,SAAS,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAC1D,CAAC;IACF,CAAC;IACD,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,IAAI,CAAC,aAAa,CAAC,EAAE,CAAC;QAC/C,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,EAAE,CAAC;YAClC,QAAQ,CAAC,IAAI,CAAC,mCAAmC,IAAI,EAAE,CAAC,CAAC;QAC1D,CAAC;IACF,CAAC;IACD,OAAO,QAAQ,CAAC;AACjB,CAAC"}
@@ -0,0 +1,8 @@
1
+ import { Effect } from "effect";
2
+ import { Migrator, SqlClient } from "effect/sql";
3
+ import { type PostgresMigrationOptions } from "@shivaedev/sql/migrations.ts";
4
+ export declare const migrationSteps: <R>(migrations: PostgresMigrationOptions<R>) => Effect.Effect<{
5
+ migrateTo: (name: string | number) => Effect.Effect<readonly (readonly [id: number, name: string])[], Migrator.MigrationError | import("effect/sql/SqlError").SqlError, Exclude<R, R | SqlClient.SqlClient>>;
6
+ migrateToLatest: Effect.Effect<readonly (readonly [id: number, name: string])[], Migrator.MigrationError | import("effect/sql/SqlError").SqlError, Exclude<R, R | SqlClient.SqlClient>>;
7
+ }, Migrator.MigrationError, R | SqlClient.SqlClient>;
8
+ //# sourceMappingURL=migrationSteps.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"migrationSteps.d.ts","sourceRoot":"","sources":["../../src/server/migrationSteps.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAU,MAAM,QAAQ,CAAC;AACxC,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AACjD,OAAO,EAAmB,KAAK,wBAAwB,EAAE,MAAM,8BAA8B,CAAC;AAY9F,eAAO,MAAM,cAAc,GAAuD,CAAC;sBAwB/D,MAAM,GAAG,MAAM;;oDAejC,CAAC"}
@@ -0,0 +1,51 @@
1
+ import { Effect, Option } from "effect";
2
+ import { Migrator, SqlClient } from "effect/sql";
3
+ import { migratePostgres } from "@shivaedev/sql/migrations.ts";
4
+ function invalid(message) {
5
+ return Effect.fail(new Migrator.MigrationError({ kind: "BadState", message }));
6
+ }
7
+ const outsideTransaction = Effect.fnUntraced(function* (sql) {
8
+ if (Option.isSome(yield* Effect.serviceOption(sql.transactionService))) {
9
+ return yield* invalid("Migration steps must run outside a wrapping transaction.");
10
+ }
11
+ });
12
+ export const migrationSteps = Effect.fn("PostgresTest.migrationSteps")(function* (migrations) {
13
+ const sql = yield* SqlClient.SqlClient;
14
+ const context = yield* Effect.context();
15
+ yield* sql.onDialectOrElse({ orElse: () => invalid("Migration steps require PostgreSQL."), pg: () => Effect.void });
16
+ yield* outsideTransaction(sql);
17
+ const resolved = yield* migrations.loader;
18
+ const ledger = migrations.table ?? "effect_sql_migrations";
19
+ const identifier = sql `${sql(ledger)}`.compile()[0];
20
+ function run(loader, target) {
21
+ return Effect.gen(function* () {
22
+ yield* outsideTransaction(sql);
23
+ if (target !== undefined) {
24
+ const [existing] = yield* sql `select to_regclass(${identifier})::text as name`.withoutTransform;
25
+ if (existing !== undefined && existing.name !== null) {
26
+ const later = yield* sql `select migration_id from ${sql(ledger)} where migration_id > ${target} limit 1`;
27
+ if (later.length > 0) {
28
+ return yield* invalid(`Cannot migrate to ${target}: later migrations are already applied.`);
29
+ }
30
+ }
31
+ }
32
+ return yield* migratePostgres({ ...migrations, loader }).pipe(Effect.provideContext(context));
33
+ });
34
+ }
35
+ return {
36
+ migrateTo: (name) => {
37
+ const match = /^(?<id>\d+)(?:_(?<label>.+))?$/u.exec(String(name))?.groups;
38
+ const matches = match === undefined ? [] : resolved.filter(([id, label]) => id === Number(match.id) && (match.label === undefined || label === match.label));
39
+ const [target] = matches;
40
+ if (target === undefined) {
41
+ return invalid(`Migration "${name}" is not in the loader.`);
42
+ }
43
+ if (matches.length > 1) {
44
+ return invalid(`Migration "${name}" is ambiguous in the loader.`);
45
+ }
46
+ return run(Effect.succeed(resolved.filter(([id]) => id <= target[0])), target[0]);
47
+ },
48
+ migrateToLatest: run(Effect.succeed(resolved)),
49
+ };
50
+ });
51
+ //# sourceMappingURL=migrationSteps.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"migrationSteps.js","sourceRoot":"","sources":["../../src/server/migrationSteps.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ import { Effect } from "effect";
2
+ import { Migrator, SqlClient } from "effect/sql";
3
+ import { type PostgresMigrationOptions } from "@shivaedev/sql/migrations.ts";
4
+ export interface PostgresColumn {
5
+ readonly name: string;
6
+ readonly type: string;
7
+ }
8
+ export interface MigrationUpgradeOptions<R = never> {
9
+ readonly migrations: PostgresMigrationOptions<R>;
10
+ readonly table: string;
11
+ readonly upTo: number;
12
+ }
13
+ export declare const migrationUpgrade: <R>(options: MigrationUpgradeOptions<R>) => Effect.Effect<readonly PostgresColumn[], Migrator.MigrationError | import("effect/sql/SqlError").SqlError, R | SqlClient.SqlClient>;
14
+ //# sourceMappingURL=migrationUpgrade.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"migrationUpgrade.d.ts","sourceRoot":"","sources":["../../src/server/migrationUpgrade.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAU,MAAM,QAAQ,CAAC;AACxC,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AACjD,OAAO,EAAmB,KAAK,wBAAwB,EAAE,MAAM,8BAA8B,CAAC;AAE9F,MAAM,WAAW,cAAc;IAC9B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,uBAAuB,CAAC,CAAC,GAAG,KAAK;IACjD,QAAQ,CAAC,UAAU,EAAE,wBAAwB,CAAC,CAAC,CAAC,CAAC;IACjD,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACtB;AAED,eAAO,MAAM,gBAAgB,GAAyD,CAAC,6KA6BrF,CAAC"}
@@ -0,0 +1,34 @@
1
+ import { Effect, Option } from "effect";
2
+ import { Migrator, SqlClient } from "effect/sql";
3
+ import { migratePostgres } from "@shivaedev/sql/migrations.ts";
4
+ export const migrationUpgrade = Effect.fn("PostgresTest.migrationUpgrade")(function* (options) {
5
+ const sql = yield* SqlClient.SqlClient;
6
+ function invalid(message) {
7
+ return Effect.fail(new Migrator.MigrationError({ kind: "BadState", message }));
8
+ }
9
+ yield* sql.onDialectOrElse({ orElse: () => invalid("Migration upgrade checks require PostgreSQL."), pg: () => Effect.void });
10
+ if (Option.isSome(yield* Effect.serviceOption(sql.transactionService))) {
11
+ return yield* invalid("Migration upgrade checks must run outside a wrapping transaction.");
12
+ }
13
+ const ledger = options.migrations.table ?? "effect_sql_migrations";
14
+ const identifier = sql `${sql(ledger)}`.compile()[0];
15
+ const existing = yield* sql `select to_regclass(${identifier})::text as name`.withoutTransform;
16
+ if (existing[0]?.name !== null) {
17
+ const applied = yield* sql `select migration_id from ${sql(ledger)} limit 1`;
18
+ if (applied.length > 0) {
19
+ return yield* invalid("Migration upgrade checks require an empty migration ledger.");
20
+ }
21
+ }
22
+ const resolved = yield* options.migrations.loader;
23
+ if (!resolved.some(([id]) => id === options.upTo)) {
24
+ return yield* invalid(`Migration ${options.upTo} is not in the loader.`);
25
+ }
26
+ yield* migratePostgres({ ...options.migrations, loader: Effect.succeed(resolved.filter(([id]) => id <= options.upTo)) });
27
+ yield* migratePostgres({ ...options.migrations, loader: Effect.succeed(resolved) });
28
+ return yield* sql `
29
+ select attname as name, format_type(atttypid, atttypmod) as type
30
+ from pg_attribute
31
+ where attrelid = ${options.table}::regclass and attnum > 0 and not attisdropped
32
+ order by attname`.withoutTransform;
33
+ });
34
+ //# sourceMappingURL=migrationUpgrade.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"migrationUpgrade.js","sourceRoot":"","sources":["../../src/server/migrationUpgrade.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AACxC,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AACjD,OAAO,EAAE,eAAe,EAAiC,MAAM,8BAA8B,CAAC;AAa9F,MAAM,CAAC,MAAM,gBAAgB,GAAG,MAAM,CAAC,EAAE,CAAC,+BAA+B,CAAC,CAAC,QAAQ,CAAC,EAAK,OAAmC;IAC3H,MAAM,GAAG,GAAG,KAAK,CAAC,CAAC,SAAS,CAAC,SAAS,CAAC;IACvC,SAAS,OAAO,CAAC,OAAe;QAC/B,OAAO,MAAM,CAAC,IAAI,CAAC,IAAI,QAAQ,CAAC,cAAc,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC;IAChF,CAAC;IACD,KAAK,CAAC,CAAC,GAAG,CAAC,eAAe,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,8CAA8C,CAAC,EAAE,EAAE,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC;IAC7H,IAAI,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,aAAa,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC,EAAE,CAAC;QACxE,OAAO,KAAK,CAAC,CAAC,OAAO,CAAC,mEAAmE,CAAC,CAAC;IAC5F,CAAC;IACD,MAAM,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,KAAK,IAAI,uBAAuB,CAAC;IACnE,MAAM,UAAU,GAAG,GAAG,CAAA,GAAG,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC;IACpD,MAAM,QAAQ,GAAG,KAAK,CAAC,CAAC,GAAG,CAAkC,sBAAsB,UAAU,iBAAiB,CAAC,gBAAgB,CAAC;IAChI,IAAI,QAAQ,CAAC,CAAC,CAAC,EAAE,IAAI,KAAK,IAAI,EAAE,CAAC;QAChC,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,GAAG,CAAA,4BAA4B,GAAG,CAAC,MAAM,CAAC,UAAU,CAAC;QAC5E,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACxB,OAAO,KAAK,CAAC,CAAC,OAAO,CAAC,6DAA6D,CAAC,CAAC;QACtF,CAAC;IACF,CAAC;IACD,MAAM,QAAQ,GAAG,KAAK,CAAC,CAAC,OAAO,CAAC,UAAU,CAAC,MAAM,CAAC;IAClD,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;QACnD,OAAO,KAAK,CAAC,CAAC,OAAO,CAAC,aAAa,OAAO,CAAC,IAAI,wBAAwB,CAAC,CAAC;IAC1E,CAAC;IACD,KAAK,CAAC,CAAC,eAAe,CAAC,EAAE,GAAG,OAAO,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,EAAE,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC;IACzH,KAAK,CAAC,CAAC,eAAe,CAAC,EAAE,GAAG,OAAO,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;IACpF,OAAO,KAAK,CAAC,CAAC,GAAG,CAAgB;;;qBAGb,OAAO,CAAC,KAAK;mBACf,CAAC,gBAAgB,CAAC;AACrC,CAAC,CAAC,CAAC"}
@@ -0,0 +1,22 @@
1
+ import { Effect, Layer, type Redacted, type Scope } from "effect";
2
+ import * as Reactivity from "effect/reactivity/Reactivity";
3
+ import type { SqlNameTransforms } from "@shivaedev/sql/clientOptions.ts";
4
+ import { type PostgresMigrationOptions } from "@shivaedev/sql/migrations.ts";
5
+ import { PostgresTestDatabase, type PostgresTestServices } from "#database.ts";
6
+ export type PostgresTestLoader<E = never, R = never> = (url: Redacted.Redacted<string>) => Effect.Effect<unknown, E, R | PostgresTestServices | Scope.Scope>;
7
+ interface PostgresTestCommonOptions<E, TFixtures> extends SqlNameTransforms {
8
+ readonly fixtures?: Effect.Effect<unknown, E, TFixtures | PostgresTestServices | Scope.Scope> | PostgresTestLoader<E, TFixtures>;
9
+ readonly maxConnections?: number;
10
+ readonly prepare?: boolean;
11
+ readonly url: Redacted.Redacted<string>;
12
+ }
13
+ export type PostgresTestOptions<E = never, R = never, TFixtures = never> = PostgresTestCommonOptions<E, TFixtures> & ({
14
+ readonly migrations: PostgresMigrationOptions<R>;
15
+ readonly initialize?: never;
16
+ } | {
17
+ readonly initialize: Effect.Effect<unknown, E, R | PostgresTestServices | Scope.Scope> | PostgresTestLoader<E, R>;
18
+ readonly migrations?: never;
19
+ });
20
+ export declare function makePostgresTestLayer<E = never, R = never, TFixtures = never>(options: PostgresTestOptions<E, R, TFixtures>): Layer.Layer<import("@effect/sql-pg/PgClient").PgClient | PostgresTestDatabase | Reactivity.Reactivity | import("effect/sql/SqlClient").SqlClient, E | import("effect/sql/Migrator").MigrationError | import("effect/sql/SqlError").SqlError, Exclude<Exclude<Exclude<Exclude<R, import("@effect/sql-pg/PgClient").PgClient | Reactivity.Reactivity | import("effect/sql/SqlClient").SqlClient>, Scope.Scope>, Scope.Scope>, Reactivity.Reactivity> | Exclude<Exclude<Exclude<Exclude<TFixtures, import("@effect/sql-pg/PgClient").PgClient | Reactivity.Reactivity | import("effect/sql/SqlClient").SqlClient>, Scope.Scope>, Scope.Scope>, Reactivity.Reactivity>>;
21
+ export {};
22
+ //# sourceMappingURL=postgres.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"postgres.d.ts","sourceRoot":"","sources":["../../src/server/postgres.ts"],"names":[],"mappings":"AAAA,OAAO,EAAW,MAAM,EAAE,KAAK,EAAE,KAAK,QAAQ,EAAE,KAAK,KAAK,EAAE,MAAM,QAAQ,CAAC;AAC3E,OAAO,KAAK,UAAU,MAAM,8BAA8B,CAAC;AAC3D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iCAAiC,CAAC;AACzE,OAAO,EAAmB,KAAK,wBAAwB,EAAE,MAAM,8BAA8B,CAAC;AAC9F,OAAO,EAAE,oBAAoB,EAAoC,KAAK,oBAAoB,EAAE,MAAM,cAAc,CAAC;AAKjH,MAAM,MAAM,kBAAkB,CAAC,CAAC,GAAG,KAAK,EAAE,CAAC,GAAG,KAAK,IAAI,CACtD,GAAG,EAAE,QAAQ,CAAC,QAAQ,CAAC,MAAM,CAAC,KAC1B,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC,EAAE,CAAC,GAAG,oBAAoB,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC;AAEvE,UAAU,yBAAyB,CAAC,CAAC,EAAE,SAAS,CAAE,SAAQ,iBAAiB;IAC1E,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC,EAAE,SAAS,GAAG,oBAAoB,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,kBAAkB,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC;IACjI,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,GAAG,EAAE,QAAQ,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;CACxC;AAED,MAAM,MAAM,mBAAmB,CAAC,CAAC,GAAG,KAAK,EAAE,CAAC,GAAG,KAAK,EAAE,SAAS,GAAG,KAAK,IAAI,yBAAyB,CAAC,CAAC,EAAE,SAAS,CAAC,GACjH,CACG;IAAE,QAAQ,CAAC,UAAU,EAAE,wBAAwB,CAAC,CAAC,CAAC,CAAC;IAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,KAAK,CAAA;CAAE,GACjF;IACA,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC,EAAE,CAAC,GAAG,oBAAoB,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,kBAAkB,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAClH,QAAQ,CAAC,UAAU,CAAC,EAAE,KAAK,CAAC;CAC3B,CACH,CAAC;AASH,wBAAgB,qBAAqB,CAAC,CAAC,GAAG,KAAK,EAAE,CAAC,GAAG,KAAK,EAAE,SAAS,GAAG,KAAK,EAAE,OAAO,EAAE,mBAAmB,CAAC,CAAC,EAAE,CAAC,EAAE,SAAS,CAAC,uoBAmC3H"}
@@ -0,0 +1,43 @@
1
+ import { Context, Effect, Layer } from "effect";
2
+ import * as Reactivity from "effect/reactivity/Reactivity";
3
+ import { migratePostgres } from "@shivaedev/sql/migrations.ts";
4
+ import { PostgresTestDatabase } from "#database.ts";
5
+ import { connectionLimitHint } from "#internal/connectionLimitHint.ts";
6
+ import { databaseCleanup } from "#internal/databaseCleanup.ts";
7
+ import { connection, database } from "#internal/databases.ts";
8
+ function runLoader(loader, url) {
9
+ return typeof loader === "function" ? loader(url) : loader;
10
+ }
11
+ export function makePostgresTestLayer(options) {
12
+ return Layer.effectContext(Effect.gen(function* () {
13
+ const maxConnections = options.maxConnections ?? 2;
14
+ const { awaitCleanup, release } = yield* databaseCleanup(options.url);
15
+ const empty = yield* database(options.url, release);
16
+ const template = options.prepare === false ? empty : yield* database(options.url, release, empty.name);
17
+ if (options.prepare !== false) {
18
+ yield* Effect.scoped(Effect.gen(function* () {
19
+ const services = yield* Layer.build(connection(template.url, maxConnections, options));
20
+ const initialize = options.initialize;
21
+ if (initialize === undefined) {
22
+ yield* migratePostgres(options.migrations).pipe(Effect.provideContext(services));
23
+ }
24
+ else {
25
+ yield* runLoader(initialize, template.url).pipe(Effect.provideContext(services));
26
+ }
27
+ if (options.fixtures !== undefined) {
28
+ yield* runLoader(options.fixtures, template.url).pipe(Effect.provideContext(services));
29
+ }
30
+ }));
31
+ }
32
+ const shared = yield* database(options.url, release, template.name);
33
+ const services = yield* Layer.build(connection(shared.url, maxConnections, options));
34
+ const clone = (source) => Effect.gen(function* () {
35
+ const target = yield* database(options.url, release, source === "empty" ? empty.name : template.name);
36
+ const context = yield* Layer.build(connection(target.url, maxConnections, options));
37
+ return Context.add(context, PostgresTestDatabase, { awaitCleanup, clone, isolated, url: target.url });
38
+ });
39
+ const isolated = clone("migrated");
40
+ return Context.add(services, PostgresTestDatabase, { awaitCleanup, clone, isolated, url: shared.url });
41
+ }).pipe(Effect.mapError(connectionLimitHint))).pipe(Layer.provide(Reactivity.layer));
42
+ }
43
+ //# sourceMappingURL=postgres.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"postgres.js","sourceRoot":"","sources":["../../src/server/postgres.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ import { Effect } from "effect";
2
+ import { PostgresTestDatabase, type PostgresTestTemplate } from "./database.ts";
3
+ export declare function withIsolatedDatabase<A, E, R>(effect: Effect.Effect<A, E, R>, options?: {
4
+ readonly template?: PostgresTestTemplate;
5
+ }): Effect.Effect<A, E | import("effect/sql/SqlError").SqlError, PostgresTestDatabase | Exclude<Exclude<Exclude<R, PostgresTestDatabase | import("./database.ts").PostgresTestServices>, import("effect/Scope").Scope>, import("effect/Scope").Scope>>;
6
+ //# sourceMappingURL=withIsolatedDatabase.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"withIsolatedDatabase.d.ts","sourceRoot":"","sources":["../../src/server/withIsolatedDatabase.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAEhC,OAAO,EAAE,oBAAoB,EAAE,KAAK,oBAAoB,EAAE,MAAM,eAAe,CAAC;AAEhF,wBAAgB,oBAAoB,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE,OAAO,CAAC,EAAE;IAAE,QAAQ,CAAC,QAAQ,CAAC,EAAE,oBAAoB,CAAA;CAAE,sPAQnI"}
@@ -0,0 +1,11 @@
1
+ import { Effect } from "effect";
2
+ import { connectionLimitHint } from "#internal/connectionLimitHint.ts";
3
+ import { PostgresTestDatabase } from "./database.js";
4
+ export function withIsolatedDatabase(effect, options) {
5
+ return Effect.scoped(Effect.gen(function* () {
6
+ const database = yield* PostgresTestDatabase;
7
+ const services = yield* options?.template === undefined ? database.isolated : database.clone(options.template);
8
+ return yield* Effect.scoped(effect.pipe(Effect.provideContext(services)));
9
+ })).pipe(Effect.mapError(connectionLimitHint));
10
+ }
11
+ //# sourceMappingURL=withIsolatedDatabase.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"withIsolatedDatabase.js","sourceRoot":"","sources":["../../src/server/withIsolatedDatabase.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAChC,OAAO,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAC;AACvE,OAAO,EAAE,oBAAoB,EAA6B,MAAM,eAAe,CAAC;AAEhF,MAAM,UAAU,oBAAoB,CAAU,MAA8B,EAAE,OAAsD;IACnI,OAAO,MAAM,CAAC,MAAM,CACnB,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;QACnB,MAAM,QAAQ,GAAG,KAAK,CAAC,CAAC,oBAAoB,CAAC;QAC7C,MAAM,QAAQ,GAAG,KAAK,CAAC,CAAC,OAAO,EAAE,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;QAC/G,OAAO,KAAK,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,cAAc,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;IAC3E,CAAC,CAAC,CACF,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,mBAAmB,CAAC,CAAC,CAAC;AAC9C,CAAC"}
@@ -0,0 +1,4 @@
1
+ import { Effect } from "effect";
2
+ import type { SqlError } from "effect/sql/SqlError";
3
+ export declare function withTestTransaction<A, E, R>(effect: Effect.Effect<A, E, R>): Effect.Effect<A, E | SqlError, import("effect/reactivity/Reactivity").Reactivity | import("effect/sql/SqlClient").SqlClient | Exclude<R, import("effect/Scope").Scope>>;
4
+ //# sourceMappingURL=withTestTransaction.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"withTestTransaction.d.ts","sourceRoot":"","sources":["../../src/server/withTestTransaction.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAQ,MAAM,QAAQ,CAAC;AACtC,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAQpD,wBAAgB,mBAAmB,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,2KAO1E"}
@@ -0,0 +1,10 @@
1
+ import { Effect, Exit } from "effect";
2
+ import { transact } from "@shivaedev/sql/transact.ts";
3
+ import { connectionLimitHint } from "#internal/connectionLimitHint.ts";
4
+ function isTestExit(value) {
5
+ return Exit.isExit(value);
6
+ }
7
+ export function withTestTransaction(effect) {
8
+ return Effect.uninterruptibleMask((restore) => transact(Effect.flatMap(Effect.exit(Effect.scoped(restore(effect))), Effect.fail), { onSqlError: (error) => error }).pipe(Effect.catchIf(isTestExit, (exit) => exit), Effect.mapError(connectionLimitHint)));
9
+ }
10
+ //# sourceMappingURL=withTestTransaction.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"withTestTransaction.js","sourceRoot":"","sources":["../../src/server/withTestTransaction.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAEtC,OAAO,EAAE,QAAQ,EAAE,MAAM,4BAA4B,CAAC;AACtD,OAAO,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAC;AAEvE,SAAS,UAAU,CAAO,KAAiC;IAC1D,OAAO,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAC3B,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAU,MAA8B;IAC1E,OAAO,MAAM,CAAC,mBAAmB,CAAC,CAAC,OAAO,EAAE,EAAE,CAC7C,QAAQ,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,IAAI,CAAC,EAAE,EAAE,UAAU,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,EAAE,CAAC,CAAC,IAAI,CACxH,MAAM,CAAC,OAAO,CAAC,UAAU,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,EAC1C,MAAM,CAAC,QAAQ,CAAC,mBAAmB,CAAC,CACpC,CACD,CAAC;AACH,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,78 @@
1
1
  {
2
2
  "name": "@shivaedev/test-sql",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.9.3",
4
+ "description": "PostgreSQL rollback and real-commit test isolation for Effect",
5
+ "keywords": [
6
+ "effect"
7
+ ],
8
+ "homepage": "https://github.com/ShivaeDev/platform/tree/main/packages/test-sql#readme",
9
+ "bugs": {
10
+ "url": "https://github.com/ShivaeDev/platform/issues"
11
+ },
12
+ "repository": {
13
+ "type": "git",
14
+ "url": "git+https://github.com/ShivaeDev/platform.git",
15
+ "directory": "packages/test-sql"
16
+ },
17
+ "license": "MIT",
18
+ "sideEffects": false,
19
+ "type": "module",
20
+ "imports": {
21
+ "#test/*": "./src/test-support/*",
22
+ "#*.ts": {
23
+ "source": "./src/server/*.ts",
24
+ "types": "./dist/server/*.d.ts",
25
+ "default": "./dist/server/*.js"
26
+ }
27
+ },
28
+ "exports": {
29
+ "./*.ts": {
30
+ "source": "./src/*.ts",
31
+ "types": "./dist/*.d.ts",
32
+ "default": "./dist/*.js"
33
+ },
34
+ "./package.json": "./package.json",
35
+ "./server/internal/*": null
36
+ },
37
+ "files": [
38
+ "dist",
39
+ "src",
40
+ "!src/**/*.test.ts",
41
+ "!src/**/*.spec.ts",
42
+ "!src/**/test-support",
43
+ "CHANGELOG.md",
44
+ "README.md"
45
+ ],
46
+ "dependencies": {
47
+ "@shivaedev/sql": "0.21.0"
48
+ },
49
+ "devDependencies": {
50
+ "@effect/sql-pg": "4.0.5",
51
+ "@effect/vitest": "4.0.5",
52
+ "@shivaedev/quality": "0.41.1",
53
+ "@shivaedev/test": "0.9.0",
54
+ "@shivaedev/test-story": "0.20.1",
55
+ "@types/node": "24.19.2",
56
+ "effect": "4.0.5",
57
+ "pg-boss": "12.38.0",
58
+ "typescript": "7.0.2",
59
+ "vitest": "5.0.5"
60
+ },
61
+ "peerDependencies": {
62
+ "@effect/sql-pg": "4.0.5",
63
+ "effect": "4.0.5"
64
+ },
65
+ "engines": {
66
+ "node": ">=24"
67
+ },
68
+ "publishConfig": {
69
+ "access": "public",
70
+ "provenance": true
71
+ },
72
+ "scripts": {
73
+ "build": "node --eval \"import('node:fs').then(({ rmSync }) => rmSync('dist', { force: true, recursive: true }))\" && tsc --project tsconfig.build.json",
74
+ "ready": "pnpm typecheck && pnpm test && pnpm build",
75
+ "test": "vitest run",
76
+ "typecheck": "tsc --noEmit"
77
+ }
6
78
  }
@@ -0,0 +1,19 @@
1
+ import * as PgClient from "@effect/sql-pg/PgClient";
2
+ import { Data, Effect, Option } from "effect";
3
+ import type { Model } from "effect/schema";
4
+ import { modelColumnProblems } from "#internal/modelColumnProblems.ts";
5
+ import type { PostgresColumn } from "./migrationUpgrade.ts";
6
+
7
+ export class ModelColumnMismatch extends Data.TaggedError("ModelColumnMismatch")<{
8
+ readonly problems: readonly string[];
9
+ }> {}
10
+
11
+ export const assertModelColumns = Effect.fn("PostgresTest.assertModelColumns")(function* <TModel extends Model.Any>(
12
+ columns: readonly PostgresColumn[],
13
+ model: TModel,
14
+ types: { readonly [TField in keyof NoInfer<TModel>["fields"]]: string },
15
+ ) {
16
+ const client = yield* Effect.serviceOption(PgClient.PgClient);
17
+ const problems = modelColumnProblems(columns, model.fields, types, Option.getOrUndefined(client)?.config);
18
+ return yield* problems.length === 0 ? Effect.void : Effect.fail(new ModelColumnMismatch({ problems }));
19
+ });
@@ -0,0 +1,21 @@
1
+ import type * as PgClient from "@effect/sql-pg/PgClient";
2
+ import { Context, type Effect, type Redacted, type Scope } from "effect";
3
+ import type * as Reactivity from "effect/reactivity/Reactivity";
4
+ import type { SqlClient } from "effect/sql";
5
+ import type { SqlError } from "effect/sql/SqlError";
6
+
7
+ export type PostgresTestServices = PgClient.PgClient | SqlClient.SqlClient | Reactivity.Reactivity;
8
+ export type PostgresTestTemplate = "empty" | "migrated";
9
+
10
+ export interface PostgresTestDatabaseService {
11
+ readonly awaitCleanup: Effect.Effect<void>;
12
+ readonly clone: (
13
+ template: PostgresTestTemplate,
14
+ ) => Effect.Effect<Context.Context<PostgresTestServices | PostgresTestDatabase>, SqlError, Scope.Scope>;
15
+ readonly isolated: Effect.Effect<Context.Context<PostgresTestServices | PostgresTestDatabase>, SqlError, Scope.Scope>;
16
+ readonly url: Redacted.Redacted<string>;
17
+ }
18
+
19
+ export class PostgresTestDatabase extends Context.Service<PostgresTestDatabase, PostgresTestDatabaseService>()(
20
+ "@shivaedev/test-sql/server/PostgresTestDatabase",
21
+ ) {}
@@ -0,0 +1,16 @@
1
+ import { Predicate } from "effect";
2
+ import { ConnectionError, isSqlError, SqlError } from "effect/sql/SqlError";
3
+
4
+ export function connectionLimitHint<E>(error: E): E | SqlError {
5
+ if (!(isSqlError(error) && Predicate.hasProperty(error.reason.cause, "code")) || error.reason.cause.code !== "53300") {
6
+ return error;
7
+ }
8
+ return new SqlError({
9
+ reason: new ConnectionError({
10
+ cause: error.reason.cause,
11
+ message:
12
+ "PostgreSQL connection limit reached (SQLSTATE 53300): check max_connections and role connection limits. Try vitest run --maxWorkers=4, reduce the suite's maxConnections, or increase the server's connection budget.",
13
+ operation: error.reason.operation,
14
+ }),
15
+ });
16
+ }