@shivaedev/sql 0.35.0 → 0.36.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 +12 -0
- package/README.md +1 -1
- package/dist/coordinateWorkerRelease.d.ts.map +1 -1
- package/dist/coordinateWorkerRelease.js +7 -1
- package/dist/coordinateWorkerRelease.js.map +1 -1
- package/dist/releaseWorkers.d.ts.map +1 -1
- package/dist/releaseWorkers.js +10 -4
- package/dist/releaseWorkers.js.map +1 -1
- package/dist/testing/WorkerRelease.d.ts +13 -0
- package/dist/testing/WorkerRelease.d.ts.map +1 -0
- package/dist/testing/WorkerRelease.js +4 -0
- package/dist/testing/WorkerRelease.js.map +1 -0
- package/package.json +8 -8
- package/src/coordinateWorkerRelease.ts +7 -1
- package/src/releaseWorkers.ts +16 -4
- package/src/testing/WorkerRelease.ts +13 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.36.1 - 2026-11-04
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- Document the Quality check and the supported `transact` repair for repository writes inside native transactions.
|
|
8
|
+
|
|
9
|
+
## 0.36.0 - 2026-11-03
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- Add the scoped `WorkerReleaseTesting` hook for host stories to control lease time and await release checks.
|
|
14
|
+
|
|
3
15
|
## 0.35.0 - 2026-11-03
|
|
4
16
|
|
|
5
17
|
### Breaking
|
package/README.md
CHANGED
|
@@ -698,7 +698,7 @@ The transaction rules follow the database owner:
|
|
|
698
698
|
- Keys belong to the `SqlClient` in the recording Effect's context. Entering another client's transaction does not move an existing client's keys. Re-entering a still-open transaction on the original client joins its own savepoint.
|
|
699
699
|
- Recording from a fiber after its transaction finishes dies. Record every changed key before the body returns.
|
|
700
700
|
|
|
701
|
-
Begin the outermost changed transaction with `transact`.
|
|
701
|
+
Begin the outermost changed transaction with `transact`. Quality's `effect/sql-transact` rule reports native `sql.withTransaction` calls around repository writes, `transact` or `invalidateOnCommit`, and names `transact` in the repair. It follows direct calls, pipes, local aliases and source helpers; native read-only and raw SQL transactions remain supported. Both `transact` and `invalidateOnCommit` also refuse an unowned native outer transaction at runtime. Outside a transaction, `invalidateOnCommit` publishes immediately.
|
|
702
702
|
|
|
703
703
|
PostgreSQL commit visibility decides publication even when a caller is interrupted while `COMMIT` is in flight: if the database commits, keys publish and the caller remains interrupted. If invalidation itself fails after commit, the failure is logged and the committed result stands. Do not treat a notification failure as evidence that the write can be retried safely.
|
|
704
704
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"coordinateWorkerRelease.d.ts","sourceRoot":"","sources":["../src/coordinateWorkerRelease.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"coordinateWorkerRelease.d.ts","sourceRoot":"","sources":["../src/coordinateWorkerRelease.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAqC,KAAK,EAAE,MAAM,QAAQ,CAAC;AAWpF,eAAO,MAAM,uBAAuB;;wIA+DlC,CAAC"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { Duration, Effect, Exit, Predicate, Schedule, Scope } from "effect";
|
|
1
|
+
import { Duration, Effect, Exit, Option, Predicate, Schedule, Scope } from "effect";
|
|
2
2
|
import { isSqlError } from "effect/sql/SqlError";
|
|
3
|
+
import { WorkerReleaseTesting } from "#testing/WorkerRelease.ts";
|
|
3
4
|
import { readPostgresRelease } from "./postgresRelease.js";
|
|
4
5
|
import { releaseWorkers } from "./releaseWorkers.js";
|
|
5
6
|
import { makeWorkerRelease } from "./WorkerRelease.js";
|
|
@@ -41,6 +42,11 @@ export const coordinateWorkerRelease = Effect.fn("EffectSql.coordinateWorkerRele
|
|
|
41
42
|
yield* loop("heartbeat", rows.heartbeat);
|
|
42
43
|
yield* tick("poll", poll).pipe(Scope.provide(scope));
|
|
43
44
|
yield* loop("poll", poll);
|
|
45
|
+
const testing = yield* Effect.serviceOption(WorkerReleaseTesting);
|
|
46
|
+
if (Option.isSome(testing)) {
|
|
47
|
+
const context = yield* Effect.context();
|
|
48
|
+
yield* testing.value.register({ heartbeat: rows.heartbeat, poll: poll.pipe(Effect.provideContext(context)) }).pipe(Scope.provide(scope));
|
|
49
|
+
}
|
|
44
50
|
return gate.service;
|
|
45
51
|
});
|
|
46
52
|
//# sourceMappingURL=coordinateWorkerRelease.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"coordinateWorkerRelease.js","sourceRoot":"","sources":["../src/coordinateWorkerRelease.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAC;
|
|
1
|
+
{"version":3,"file":"coordinateWorkerRelease.js","sourceRoot":"","sources":["../src/coordinateWorkerRelease.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"releaseWorkers.d.ts","sourceRoot":"","sources":["../src/releaseWorkers.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"releaseWorkers.d.ts","sourceRoot":"","sources":["../src/releaseWorkers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAS,MAAM,EAAkB,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC/D,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAGvC,eAAO,MAAM,cAAc;;;;8BAyCzB,CAAC"}
|
package/dist/releaseWorkers.js
CHANGED
|
@@ -1,25 +1,31 @@
|
|
|
1
|
-
import { Effect, Random, Schema } from "effect";
|
|
1
|
+
import { Clock, Effect, Option, Random, Schema } from "effect";
|
|
2
2
|
import { SqlClient } from "effect/sql";
|
|
3
|
+
import { WorkerReleaseTesting } from "#testing/WorkerRelease.ts";
|
|
3
4
|
export const releaseWorkers = Effect.fn("EffectSql.releaseWorkers")(function* (release, staleMillis, table = "effect_sql_migrations") {
|
|
4
5
|
const sql = yield* SqlClient.SqlClient;
|
|
5
6
|
const processId = `${yield* Random.nextInt}:${yield* Random.nextInt}`;
|
|
6
7
|
const workers = `${table}_workers`;
|
|
8
|
+
const testing = yield* Effect.serviceOption(WorkerReleaseTesting);
|
|
9
|
+
const now = Option.isSome(testing)
|
|
10
|
+
? Clock.currentTimeMillis.pipe(Effect.provideService(Clock.Clock, testing.value.clock), Effect.map((millis) => sql `${new Date(millis).toISOString()}::timestamptz`))
|
|
11
|
+
: Effect.succeed(sql `clock_timestamp()`);
|
|
7
12
|
return {
|
|
8
13
|
claim: sql.withTransaction(Effect.gen(function* () {
|
|
14
|
+
const timestamp = yield* now;
|
|
9
15
|
const [owner] = yield* sql `select release from ${sql(`${table}_release`)} where id = true for update`.withoutTransform.pipe(Effect.flatMap(Schema.decodeUnknownEffect(Schema.Array(Schema.Struct({ release: Schema.String })))));
|
|
10
16
|
if (owner?.release !== release) {
|
|
11
17
|
return false;
|
|
12
18
|
}
|
|
13
19
|
const others = yield* sql `select process_id from ${sql(workers)} where release <> ${release}
|
|
14
|
-
and heartbeat >
|
|
20
|
+
and heartbeat > ${timestamp} - ${staleMillis} * interval '1 millisecond'`;
|
|
15
21
|
if (others.length > 0) {
|
|
16
22
|
return false;
|
|
17
23
|
}
|
|
18
|
-
yield* sql `insert into ${sql(workers)} (process_id, release, heartbeat) values (${processId}, ${release},
|
|
24
|
+
yield* sql `insert into ${sql(workers)} (process_id, release, heartbeat) values (${processId}, ${release}, ${timestamp})
|
|
19
25
|
on conflict (process_id) do update set heartbeat = excluded.heartbeat`;
|
|
20
26
|
return true;
|
|
21
27
|
})),
|
|
22
|
-
heartbeat: sql `update ${sql(workers)} set heartbeat =
|
|
28
|
+
heartbeat: now.pipe(Effect.flatMap((timestamp) => sql `update ${sql(workers)} set heartbeat = ${timestamp} where process_id = ${processId}`), Effect.asVoid),
|
|
23
29
|
remove: sql `delete from ${sql(workers)} where process_id = ${processId}`.pipe(Effect.asVoid),
|
|
24
30
|
};
|
|
25
31
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"releaseWorkers.js","sourceRoot":"","sources":["../src/releaseWorkers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;
|
|
1
|
+
{"version":3,"file":"releaseWorkers.js","sourceRoot":"","sources":["../src/releaseWorkers.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type Clock, Context, type Effect, type Schema, type Scope } from "effect";
|
|
2
|
+
import type { SqlError } from "effect/sql/SqlError";
|
|
3
|
+
declare const WorkerReleaseTesting_base: Context.ServiceClass<WorkerReleaseTesting, "@shivaedev/sql/testing/WorkerReleaseTesting", {
|
|
4
|
+
readonly clock: Clock.Clock;
|
|
5
|
+
readonly register: (tick: {
|
|
6
|
+
readonly heartbeat: Effect.Effect<void, SqlError | Schema.SchemaError>;
|
|
7
|
+
readonly poll: Effect.Effect<void, SqlError | Schema.SchemaError>;
|
|
8
|
+
}) => Effect.Effect<void, never, Scope.Scope>;
|
|
9
|
+
}>;
|
|
10
|
+
export declare class WorkerReleaseTesting extends WorkerReleaseTesting_base {
|
|
11
|
+
}
|
|
12
|
+
export {};
|
|
13
|
+
//# sourceMappingURL=WorkerRelease.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"WorkerRelease.d.ts","sourceRoot":"","sources":["../../src/testing/WorkerRelease.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,KAAK,EAAE,OAAO,EAAE,KAAK,MAAM,EAAE,KAAK,MAAM,EAAE,KAAK,KAAK,EAAE,MAAM,QAAQ,CAAC;AACnF,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;;oBAKlC,KAAK,CAAC,KAAK;uBACR,CAAC,IAAI,EAAE;QACzB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,QAAQ,GAAG,MAAM,CAAC,WAAW,CAAC,CAAC;QACvE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,QAAQ,GAAG,MAAM,CAAC,WAAW,CAAC,CAAC;KAClE,KAAK,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC;;AAP/C,qBAAa,oBAAqB,SAAQ,yBASQ;CAAG"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"WorkerRelease.js","sourceRoot":"","sources":["../../src/testing/WorkerRelease.ts"],"names":[],"mappings":"AAAA,OAAO,EAAc,OAAO,EAAwC,MAAM,QAAQ,CAAC;AAGnF,MAAM,OAAO,oBAAqB,SAAQ,OAAO,CAAC,OAAO,EAStD,CAAC,6CAA6C,CAAC;CAAG"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shivaedev/sql",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.36.1",
|
|
4
4
|
"description": "Schema-derived repositories over native Effect SQL",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"effect"
|
|
@@ -44,18 +44,18 @@
|
|
|
44
44
|
],
|
|
45
45
|
"dependencies": {
|
|
46
46
|
"@shivaedev/changes": "0.9.0",
|
|
47
|
-
"@shivaedev/service": "0.6.
|
|
47
|
+
"@shivaedev/service": "0.6.2"
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
50
50
|
"@effect/platform-node": "4.0.6",
|
|
51
51
|
"@effect/sql-pg": "4.0.6",
|
|
52
52
|
"@effect/sql-sqlite-node": "4.0.6",
|
|
53
|
-
"@shivaedev/contract": "0.
|
|
54
|
-
"@shivaedev/jobs": "0.
|
|
55
|
-
"@shivaedev/quality": "0.
|
|
56
|
-
"@shivaedev/test-kits": "0.1
|
|
57
|
-
"@shivaedev/test-sql": "0.18.
|
|
58
|
-
"@shivaedev/test-story": "0.
|
|
53
|
+
"@shivaedev/contract": "0.34.0",
|
|
54
|
+
"@shivaedev/jobs": "0.34.0",
|
|
55
|
+
"@shivaedev/quality": "0.58.0",
|
|
56
|
+
"@shivaedev/test-kits": "0.2.1",
|
|
57
|
+
"@shivaedev/test-sql": "0.18.2",
|
|
58
|
+
"@shivaedev/test-story": "0.37.0",
|
|
59
59
|
"@types/node": "24.19.2",
|
|
60
60
|
"effect": "4.0.6",
|
|
61
61
|
"typescript": "7.0.2",
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { Duration, Effect, Exit, Predicate, Schedule, Scope } from "effect";
|
|
1
|
+
import { Duration, Effect, Exit, Option, Predicate, Schedule, Scope } from "effect";
|
|
2
2
|
import { isSqlError } from "effect/sql/SqlError";
|
|
3
|
+
import { WorkerReleaseTesting } from "#testing/WorkerRelease.ts";
|
|
3
4
|
import { readPostgresRelease } from "./postgresRelease.ts";
|
|
4
5
|
import { releaseWorkers } from "./releaseWorkers.ts";
|
|
5
6
|
import { makeWorkerRelease } from "./WorkerRelease.ts";
|
|
@@ -65,5 +66,10 @@ export const coordinateWorkerRelease = Effect.fn("EffectSql.coordinateWorkerRele
|
|
|
65
66
|
yield* loop("heartbeat", rows.heartbeat);
|
|
66
67
|
yield* tick("poll", poll).pipe(Scope.provide(scope));
|
|
67
68
|
yield* loop("poll", poll);
|
|
69
|
+
const testing = yield* Effect.serviceOption(WorkerReleaseTesting);
|
|
70
|
+
if (Option.isSome(testing)) {
|
|
71
|
+
const context = yield* Effect.context<Effect.Services<typeof poll>>();
|
|
72
|
+
yield* testing.value.register({ heartbeat: rows.heartbeat, poll: poll.pipe(Effect.provideContext(context)) }).pipe(Scope.provide(scope));
|
|
73
|
+
}
|
|
68
74
|
return gate.service;
|
|
69
75
|
});
|
package/src/releaseWorkers.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { Effect, Random, Schema } from "effect";
|
|
1
|
+
import { Clock, Effect, Option, Random, Schema } from "effect";
|
|
2
2
|
import { SqlClient } from "effect/sql";
|
|
3
|
+
import { WorkerReleaseTesting } from "#testing/WorkerRelease.ts";
|
|
3
4
|
|
|
4
5
|
export const releaseWorkers = Effect.fn("EffectSql.releaseWorkers")(function* (
|
|
5
6
|
release: string,
|
|
@@ -9,9 +10,17 @@ export const releaseWorkers = Effect.fn("EffectSql.releaseWorkers")(function* (
|
|
|
9
10
|
const sql = yield* SqlClient.SqlClient;
|
|
10
11
|
const processId = `${yield* Random.nextInt}:${yield* Random.nextInt}`;
|
|
11
12
|
const workers = `${table}_workers`;
|
|
13
|
+
const testing = yield* Effect.serviceOption(WorkerReleaseTesting);
|
|
14
|
+
const now = Option.isSome(testing)
|
|
15
|
+
? Clock.currentTimeMillis.pipe(
|
|
16
|
+
Effect.provideService(Clock.Clock, testing.value.clock),
|
|
17
|
+
Effect.map((millis) => sql`${new Date(millis).toISOString()}::timestamptz`),
|
|
18
|
+
)
|
|
19
|
+
: Effect.succeed(sql`clock_timestamp()`);
|
|
12
20
|
return {
|
|
13
21
|
claim: sql.withTransaction(
|
|
14
22
|
Effect.gen(function* () {
|
|
23
|
+
const timestamp = yield* now;
|
|
15
24
|
const [owner] = yield* sql`select release from ${sql(`${table}_release`)} where id = true for update`.withoutTransform.pipe(
|
|
16
25
|
Effect.flatMap(Schema.decodeUnknownEffect(Schema.Array(Schema.Struct({ release: Schema.String })))),
|
|
17
26
|
);
|
|
@@ -19,16 +28,19 @@ export const releaseWorkers = Effect.fn("EffectSql.releaseWorkers")(function* (
|
|
|
19
28
|
return false;
|
|
20
29
|
}
|
|
21
30
|
const others = yield* sql`select process_id from ${sql(workers)} where release <> ${release}
|
|
22
|
-
and heartbeat >
|
|
31
|
+
and heartbeat > ${timestamp} - ${staleMillis} * interval '1 millisecond'`;
|
|
23
32
|
if (others.length > 0) {
|
|
24
33
|
return false;
|
|
25
34
|
}
|
|
26
|
-
yield* sql`insert into ${sql(workers)} (process_id, release, heartbeat) values (${processId}, ${release},
|
|
35
|
+
yield* sql`insert into ${sql(workers)} (process_id, release, heartbeat) values (${processId}, ${release}, ${timestamp})
|
|
27
36
|
on conflict (process_id) do update set heartbeat = excluded.heartbeat`;
|
|
28
37
|
return true;
|
|
29
38
|
}),
|
|
30
39
|
),
|
|
31
|
-
heartbeat:
|
|
40
|
+
heartbeat: now.pipe(
|
|
41
|
+
Effect.flatMap((timestamp) => sql`update ${sql(workers)} set heartbeat = ${timestamp} where process_id = ${processId}`),
|
|
42
|
+
Effect.asVoid,
|
|
43
|
+
),
|
|
32
44
|
remove: sql`delete from ${sql(workers)} where process_id = ${processId}`.pipe(Effect.asVoid),
|
|
33
45
|
};
|
|
34
46
|
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type Clock, Context, type Effect, type Schema, type Scope } from "effect";
|
|
2
|
+
import type { SqlError } from "effect/sql/SqlError";
|
|
3
|
+
|
|
4
|
+
export class WorkerReleaseTesting extends Context.Service<
|
|
5
|
+
WorkerReleaseTesting,
|
|
6
|
+
{
|
|
7
|
+
readonly clock: Clock.Clock;
|
|
8
|
+
readonly register: (tick: {
|
|
9
|
+
readonly heartbeat: Effect.Effect<void, SqlError | Schema.SchemaError>;
|
|
10
|
+
readonly poll: Effect.Effect<void, SqlError | Schema.SchemaError>;
|
|
11
|
+
}) => Effect.Effect<void, never, Scope.Scope>;
|
|
12
|
+
}
|
|
13
|
+
>()("@shivaedev/sql/testing/WorkerReleaseTesting") {}
|