@shivaedev/sql 0.25.0 → 0.26.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +33 -0
- package/README.md +29 -8
- package/dist/PostgresListenerError.d.ts +10 -0
- package/dist/PostgresListenerError.d.ts.map +1 -0
- package/dist/PostgresListenerError.js +7 -0
- package/dist/PostgresListenerError.js.map +1 -0
- package/dist/SchemaComponent.d.ts +5 -5
- package/dist/SchemaComponent.d.ts.map +1 -1
- package/dist/SchemaComponent.js +1 -2
- package/dist/SchemaComponent.js.map +1 -1
- package/dist/WorkerRelease.d.ts +7 -8
- package/dist/WorkerRelease.d.ts.map +1 -1
- package/dist/WorkerRelease.js +10 -4
- package/dist/WorkerRelease.js.map +1 -1
- package/dist/adapters/makePostgresClient.d.ts +14 -0
- package/dist/adapters/makePostgresClient.d.ts.map +1 -0
- package/dist/adapters/makePostgresClient.js +63 -0
- package/dist/adapters/makePostgresClient.js.map +1 -0
- package/dist/adapters/postgresStream.d.ts +4 -0
- package/dist/adapters/postgresStream.d.ts.map +1 -0
- package/dist/adapters/postgresStream.js +20 -0
- package/dist/adapters/postgresStream.js.map +1 -0
- package/dist/assertPostgresMigrations.d.ts +1 -1
- package/dist/assertPostgresMigrations.d.ts.map +1 -1
- package/dist/assertPostgresMigrations.js.map +1 -1
- package/dist/coordinateWorkerRelease.d.ts +3 -3
- package/dist/coordinateWorkerRelease.d.ts.map +1 -1
- package/dist/coordinateWorkerRelease.js +14 -5
- package/dist/coordinateWorkerRelease.js.map +1 -1
- package/dist/databaseAddress.d.ts +4 -0
- package/dist/databaseAddress.d.ts.map +1 -0
- package/dist/databaseAddress.js +25 -0
- package/dist/databaseAddress.js.map +1 -0
- package/dist/internal/CommandRecovery.d.ts +3 -0
- package/dist/internal/CommandRecovery.d.ts.map +1 -0
- package/dist/internal/CommandRecovery.js +5 -0
- package/dist/internal/CommandRecovery.js.map +1 -0
- package/dist/listenForChanges.d.ts +3 -1
- package/dist/listenForChanges.d.ts.map +1 -1
- package/dist/listenForChanges.js +31 -34
- package/dist/listenForChanges.js.map +1 -1
- package/dist/mapSqlError.js +17 -3
- package/dist/mapSqlError.js.map +1 -1
- package/dist/migrations.d.ts +5 -4
- package/dist/migrations.d.ts.map +1 -1
- package/dist/migrations.js +7 -3
- package/dist/migrations.js.map +1 -1
- package/dist/notifyPayload.d.ts +1 -0
- package/dist/notifyPayload.d.ts.map +1 -1
- package/dist/notifyPayload.js +2 -0
- package/dist/notifyPayload.js.map +1 -1
- package/dist/pendingPostgresMigrations.d.ts +4 -3
- package/dist/pendingPostgresMigrations.d.ts.map +1 -1
- package/dist/pendingPostgresMigrations.js +8 -4
- package/dist/pendingPostgresMigrations.js.map +1 -1
- package/dist/postgresChanges.d.ts +3 -1
- package/dist/postgresChanges.d.ts.map +1 -1
- package/dist/postgresChanges.js +4 -2
- package/dist/postgresChanges.js.map +1 -1
- package/dist/postgresLayer.d.ts +11 -0
- package/dist/postgresLayer.d.ts.map +1 -0
- package/dist/postgresLayer.js +19 -0
- package/dist/postgresLayer.js.map +1 -0
- package/dist/postgresRelease.d.ts +9 -6
- package/dist/postgresRelease.d.ts.map +1 -1
- package/dist/postgresRelease.js +3 -3
- package/dist/postgresRelease.js.map +1 -1
- package/dist/releaseWorkers.d.ts +2 -2
- package/dist/releaseWorkers.d.ts.map +1 -1
- package/dist/releaseWorkers.js +3 -3
- package/dist/releaseWorkers.js.map +1 -1
- package/dist/testing.d.ts +2 -2
- package/dist/testing.d.ts.map +1 -1
- package/dist/testing.js +2 -2
- package/dist/testing.js.map +1 -1
- package/package.json +12 -7
- package/src/PostgresListenerError.ts +9 -0
- package/src/SchemaComponent.ts +6 -6
- package/src/WorkerRelease.ts +11 -9
- package/src/adapters/makePostgresClient.ts +72 -0
- package/src/adapters/postgresStream.ts +26 -0
- package/src/assertPostgresMigrations.ts +2 -2
- package/src/coordinateWorkerRelease.ts +31 -5
- package/src/databaseAddress.ts +25 -0
- package/src/internal/CommandRecovery.ts +5 -0
- package/src/listenForChanges.ts +41 -39
- package/src/mapSqlError.ts +19 -3
- package/src/migrations.ts +14 -7
- package/src/notifyPayload.ts +2 -0
- package/src/pendingPostgresMigrations.ts +16 -7
- package/src/postgresChanges.ts +10 -2
- package/src/postgresLayer.ts +27 -0
- package/src/postgresRelease.ts +9 -5
- package/src/releaseWorkers.ts +5 -3
- package/src/testing.ts +4 -8
package/src/postgresChanges.ts
CHANGED
|
@@ -2,8 +2,9 @@ import { Context, type Duration, Effect, Layer, type Queue, type Scope } from "e
|
|
|
2
2
|
import * as Reactivity from "effect/reactivity/Reactivity";
|
|
3
3
|
import { SqlClient } from "effect/sql";
|
|
4
4
|
import type { SqlError } from "effect/sql/SqlError";
|
|
5
|
+
import { CommandRecovery } from "#internal/CommandRecovery.ts";
|
|
5
6
|
import { listenForChanges } from "./listenForChanges.ts";
|
|
6
|
-
import { encodeKeys } from "./notifyPayload.ts";
|
|
7
|
+
import { encodeKeys, resyncPayload } from "./notifyPayload.ts";
|
|
7
8
|
import { BeforeCommit, CommittedSink } from "./transact.ts";
|
|
8
9
|
|
|
9
10
|
export interface PostgresNotification {
|
|
@@ -17,8 +18,10 @@ export interface PostgresChangeClient extends SqlClient.SqlClient {
|
|
|
17
18
|
export interface PostgresChangesOptions {
|
|
18
19
|
readonly channel: string;
|
|
19
20
|
readonly client: PostgresChangeClient;
|
|
21
|
+
readonly connectionSetting?: string;
|
|
20
22
|
readonly heartbeatInterval?: Duration.Input;
|
|
21
23
|
readonly heartbeatTimeout?: Duration.Input;
|
|
24
|
+
readonly listenerClient?: Pick<PostgresChangeClient, "listen">;
|
|
22
25
|
readonly resync: Effect.Effect<number>;
|
|
23
26
|
readonly retryDelay?: Duration.Input;
|
|
24
27
|
}
|
|
@@ -42,7 +45,12 @@ export function postgresChanges(options: PostgresChangesOptions) {
|
|
|
42
45
|
sql.transactionService === options.client.transactionService ? Effect.void : Reactivity.invalidate(keys),
|
|
43
46
|
);
|
|
44
47
|
}
|
|
45
|
-
|
|
48
|
+
const recover = options.resync.pipe(
|
|
49
|
+
Effect.andThen(options.client`select pg_notify(${options.channel}, ${resyncPayload})`),
|
|
50
|
+
Effect.catchCause((cause) => Effect.logError("Committed command Resync broadcast failed; other hosts may remain stale", cause)),
|
|
51
|
+
Effect.asVoid,
|
|
52
|
+
);
|
|
53
|
+
return Context.make(BeforeCommit, notify).pipe(Context.add(CommittedSink, committed), Context.add(CommandRecovery, recover));
|
|
46
54
|
}),
|
|
47
55
|
);
|
|
48
56
|
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import * as PgClient from "@effect/sql-pg/PgClient";
|
|
2
|
+
import * as PgPool from "@effect/sql-pg/PgPool";
|
|
3
|
+
import { Context, Effect, Layer, type Redacted } from "effect";
|
|
4
|
+
import * as Reactivity from "effect/reactivity/Reactivity";
|
|
5
|
+
import { SqlClient } from "effect/sql";
|
|
6
|
+
import { makePostgresClient } from "#adapters/makePostgresClient.ts";
|
|
7
|
+
import { postgresStream } from "#adapters/postgresStream.ts";
|
|
8
|
+
import type { SqlNameTransforms } from "#clientOptions.ts";
|
|
9
|
+
import { databaseAddress } from "#databaseAddress.ts";
|
|
10
|
+
|
|
11
|
+
export interface PostgresOptions extends SqlNameTransforms {
|
|
12
|
+
readonly applicationName?: string | undefined;
|
|
13
|
+
readonly maxConnections?: number | undefined;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function postgresLayer(url: Redacted.Redacted<string>, options?: PostgresOptions) {
|
|
17
|
+
return Layer.effectContext(
|
|
18
|
+
Effect.gen(function* () {
|
|
19
|
+
const address = databaseAddress(url);
|
|
20
|
+
const stream = yield* postgresStream(url, address);
|
|
21
|
+
const config = { ...options, ...address, stream, url };
|
|
22
|
+
const pool = yield* PgPool.make(config);
|
|
23
|
+
const client = yield* makePostgresClient(config).pipe(Effect.provideService(PgPool.PgPool, pool));
|
|
24
|
+
return Context.make(PgPool.PgPool, pool).pipe(Context.add(PgClient.PgClient, client), Context.add(SqlClient.SqlClient, client));
|
|
25
|
+
}),
|
|
26
|
+
).pipe(Layer.provide(Reactivity.layer));
|
|
27
|
+
}
|
package/src/postgresRelease.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Effect } from "effect";
|
|
1
|
+
import { Effect, Schema } from "effect";
|
|
2
2
|
import { SqlClient } from "effect/sql";
|
|
3
3
|
import type { SchemaComponent } from "./SchemaComponent.ts";
|
|
4
4
|
|
|
@@ -6,15 +6,19 @@ export const readPostgresRelease = Effect.fn("EffectSql.readPostgresRelease")(fu
|
|
|
6
6
|
const sql = yield* SqlClient.SqlClient;
|
|
7
7
|
const name = `${table}_release`;
|
|
8
8
|
const identifier = sql`${sql(name)}`.compile()[0];
|
|
9
|
-
const [exists] = yield* sql
|
|
9
|
+
const [exists] = yield* sql`select to_regclass(${identifier})::text as name`.withoutTransform.pipe(
|
|
10
|
+
Effect.flatMap(Schema.decodeUnknownEffect(Schema.Array(Schema.Struct({ name: Schema.NullOr(Schema.String) })))),
|
|
11
|
+
);
|
|
10
12
|
if (exists === undefined || exists.name === null) {
|
|
11
13
|
return undefined;
|
|
12
14
|
}
|
|
13
|
-
const [row] = yield* sql
|
|
15
|
+
const [row] = yield* sql`select release, recorded_at as "recordedAt" from ${sql(name)}`.withoutTransform.pipe(
|
|
16
|
+
Effect.flatMap(Schema.decodeUnknownEffect(Schema.Array(Schema.Struct({ recordedAt: Schema.Date, release: Schema.String })))),
|
|
17
|
+
);
|
|
14
18
|
return row;
|
|
15
19
|
});
|
|
16
20
|
|
|
17
|
-
export function postgresRelease(release: string, table = "effect_sql_migrations")
|
|
21
|
+
export function postgresRelease(release: string, table = "effect_sql_migrations") {
|
|
18
22
|
return {
|
|
19
23
|
apply: Effect.gen(function* () {
|
|
20
24
|
const sql = yield* SqlClient.SqlClient;
|
|
@@ -37,5 +41,5 @@ export function postgresRelease(release: string, table = "effect_sql_migrations"
|
|
|
37
41
|
const current = yield* readPostgresRelease(table);
|
|
38
42
|
return current?.release === release ? undefined : `Expected release ${release}; recorded ${current?.release ?? "none"}`;
|
|
39
43
|
}),
|
|
40
|
-
};
|
|
44
|
+
} satisfies SchemaComponent;
|
|
41
45
|
}
|
package/src/releaseWorkers.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Effect } from "effect";
|
|
1
|
+
import { Effect, Random, Schema } from "effect";
|
|
2
2
|
import { SqlClient } from "effect/sql";
|
|
3
3
|
|
|
4
4
|
export const releaseWorkers = Effect.fn("EffectSql.releaseWorkers")(function* (
|
|
@@ -7,12 +7,14 @@ export const releaseWorkers = Effect.fn("EffectSql.releaseWorkers")(function* (
|
|
|
7
7
|
table = "effect_sql_migrations",
|
|
8
8
|
) {
|
|
9
9
|
const sql = yield* SqlClient.SqlClient;
|
|
10
|
-
const processId =
|
|
10
|
+
const processId = `${yield* Random.nextInt}:${yield* Random.nextInt}`;
|
|
11
11
|
const workers = `${table}_workers`;
|
|
12
12
|
return {
|
|
13
13
|
claim: sql.withTransaction(
|
|
14
14
|
Effect.gen(function* () {
|
|
15
|
-
const [owner] = yield* sql
|
|
15
|
+
const [owner] = yield* sql`select release from ${sql(`${table}_release`)} where id = true for update`.withoutTransform.pipe(
|
|
16
|
+
Effect.flatMap(Schema.decodeUnknownEffect(Schema.Array(Schema.Struct({ release: Schema.String })))),
|
|
17
|
+
);
|
|
16
18
|
if (owner?.release !== release) {
|
|
17
19
|
return false;
|
|
18
20
|
}
|
package/src/testing.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Data, Effect } from "effect";
|
|
1
|
+
import { Data, Effect, Schema } from "effect";
|
|
2
2
|
import { SqlClient } from "effect/sql";
|
|
3
3
|
import { type BucketKey, type ItemKey, invalidationKeys } from "@shivaedev/changes/keys.ts";
|
|
4
4
|
import { ChangeDeclarations } from "#ChangeDeclarations.ts";
|
|
@@ -10,12 +10,6 @@ export interface ChangeCoverage {
|
|
|
10
10
|
readonly covers: Readonly<Record<string, readonly (ItemKey | BucketKey)[]>>;
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
interface TableWrites {
|
|
14
|
-
readonly relid: number;
|
|
15
|
-
readonly relname: string;
|
|
16
|
-
readonly writes: number;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
13
|
export const withChangeCoverage = Effect.fn("Changes.withChangeCoverage")(function* <A, E, R>(
|
|
20
14
|
body: Effect.Effect<A, E, R>,
|
|
21
15
|
options?: ChangeCoverage,
|
|
@@ -25,7 +19,9 @@ export const withChangeCoverage = Effect.fn("Changes.withChangeCoverage")(functi
|
|
|
25
19
|
const named = new Set<unknown>();
|
|
26
20
|
const declarations = new Map<string, { keys: Set<string>; rows: number }>();
|
|
27
21
|
const outer = yield* ChangeDeclarations;
|
|
28
|
-
const written = sql
|
|
22
|
+
const written = sql`select relid, relname, (n_tup_ins + n_tup_upd + n_tup_del)::int as writes from pg_stat_xact_user_tables`.pipe(
|
|
23
|
+
Effect.flatMap(Schema.decodeUnknownEffect(Schema.Array(Schema.Struct({ relid: Schema.Int, relname: Schema.String, writes: Schema.Int })))),
|
|
24
|
+
);
|
|
29
25
|
const checked = Effect.gen(function* () {
|
|
30
26
|
const before = new Map((yield* written).map((row) => [row.relid, row.writes]));
|
|
31
27
|
const result = yield* body;
|