@shivaedev/test-sql 0.0.0 → 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.
- package/CHANGELOG.md +225 -0
- package/LICENSE +21 -0
- package/README.md +256 -1
- package/dist/server/assertModelColumns.d.ts +13 -0
- package/dist/server/assertModelColumns.d.ts.map +1 -0
- package/dist/server/assertModelColumns.js +11 -0
- package/dist/server/assertModelColumns.js.map +1 -0
- package/dist/server/database.d.ts +18 -0
- package/dist/server/database.d.ts.map +1 -0
- package/dist/server/database.js +4 -0
- package/dist/server/database.js.map +1 -0
- package/dist/server/internal/connectionLimitHint.d.ts +3 -0
- package/dist/server/internal/connectionLimitHint.d.ts.map +1 -0
- package/dist/server/internal/connectionLimitHint.js +15 -0
- package/dist/server/internal/connectionLimitHint.js.map +1 -0
- package/dist/server/internal/databaseCleanup.d.ts +7 -0
- package/dist/server/internal/databaseCleanup.d.ts.map +1 -0
- package/dist/server/internal/databaseCleanup.js +34 -0
- package/dist/server/internal/databaseCleanup.js.map +1 -0
- package/dist/server/internal/databaseOwner.d.ts +3 -0
- package/dist/server/internal/databaseOwner.d.ts.map +1 -0
- package/dist/server/internal/databaseOwner.js +24 -0
- package/dist/server/internal/databaseOwner.js.map +1 -0
- package/dist/server/internal/databases.d.ts +13 -0
- package/dist/server/internal/databases.d.ts.map +1 -0
- package/dist/server/internal/databases.js +26 -0
- package/dist/server/internal/databases.js.map +1 -0
- package/dist/server/internal/modelColumnProblems.d.ts +8 -0
- package/dist/server/internal/modelColumnProblems.d.ts.map +1 -0
- package/dist/server/internal/modelColumnProblems.js +35 -0
- package/dist/server/internal/modelColumnProblems.js.map +1 -0
- package/dist/server/migrationSteps.d.ts +8 -0
- package/dist/server/migrationSteps.d.ts.map +1 -0
- package/dist/server/migrationSteps.js +51 -0
- package/dist/server/migrationSteps.js.map +1 -0
- package/dist/server/migrationUpgrade.d.ts +14 -0
- package/dist/server/migrationUpgrade.d.ts.map +1 -0
- package/dist/server/migrationUpgrade.js +34 -0
- package/dist/server/migrationUpgrade.js.map +1 -0
- package/dist/server/postgres.d.ts +22 -0
- package/dist/server/postgres.d.ts.map +1 -0
- package/dist/server/postgres.js +43 -0
- package/dist/server/postgres.js.map +1 -0
- package/dist/server/withIsolatedDatabase.d.ts +6 -0
- package/dist/server/withIsolatedDatabase.d.ts.map +1 -0
- package/dist/server/withIsolatedDatabase.js +11 -0
- package/dist/server/withIsolatedDatabase.js.map +1 -0
- package/dist/server/withTestTransaction.d.ts +4 -0
- package/dist/server/withTestTransaction.d.ts.map +1 -0
- package/dist/server/withTestTransaction.js +10 -0
- package/dist/server/withTestTransaction.js.map +1 -0
- package/package.json +72 -4
- package/src/server/assertModelColumns.ts +19 -0
- package/src/server/database.ts +21 -0
- package/src/server/internal/connectionLimitHint.ts +16 -0
- package/src/server/internal/databaseCleanup.ts +40 -0
- package/src/server/internal/databaseOwner.ts +25 -0
- package/src/server/internal/databases.ts +47 -0
- package/src/server/internal/modelColumnProblems.ts +45 -0
- package/src/server/migrationSteps.ts +54 -0
- package/src/server/migrationUpgrade.ts +45 -0
- package/src/server/postgres.ts +72 -0
- package/src/server/withIsolatedDatabase.ts +13 -0
- 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 @@
|
|
|
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,10 +1,78 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shivaedev/test-sql",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
5
|
-
"
|
|
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
|
+
},
|
|
6
12
|
"repository": {
|
|
7
13
|
"type": "git",
|
|
8
|
-
"url": "git+https://github.com/ShivaeDev/platform.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"
|
|
9
77
|
}
|
|
10
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
|
+
}
|