@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 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`. Both `transact` and `invalidateOnCommit` refuse a native outer `sql.withTransaction` they do not own, because they cannot observe its commit. Outside a transaction, `invalidateOnCommit` publishes immediately.
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,EAA6B,KAAK,EAAE,MAAM,QAAQ,CAAC;AAU5E,eAAO,MAAM,uBAAuB;;wIA0DlC,CAAC"}
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":"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"}
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,EAAE,MAAM,EAAU,MAAM,EAAE,MAAM,QAAQ,CAAC;AAChD,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAEvC,eAAO,MAAM,cAAc;;;;8BA8BzB,CAAC"}
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"}
@@ -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 > clock_timestamp() - ${staleMillis} * interval '1 millisecond'`;
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}, clock_timestamp())
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 = clock_timestamp() where process_id = ${processId}`.pipe(Effect.asVoid),
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;AAChD,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAEvC,MAAM,CAAC,MAAM,cAAc,GAAG,MAAM,CAAC,EAAE,CAAC,0BAA0B,CAAC,CAAC,QAAQ,CAAC,EAC5E,OAAe,EACf,WAAmB,EACnB,KAAK,GAAG,uBAAuB;IAE/B,MAAM,GAAG,GAAG,KAAK,CAAC,CAAC,SAAS,CAAC,SAAS,CAAC;IACvC,MAAM,SAAS,GAAG,GAAG,KAAK,CAAC,CAAC,MAAM,CAAC,OAAO,IAAI,KAAK,CAAC,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;IACtE,MAAM,OAAO,GAAG,GAAG,KAAK,UAAU,CAAC;IACnC,OAAO;QACN,KAAK,EAAE,GAAG,CAAC,eAAe,CACzB,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;YACnB,MAAM,CAAC,KAAK,CAAC,GAAG,KAAK,CAAC,CAAC,GAAG,CAAA,uBAAuB,GAAG,CAAC,GAAG,KAAK,UAAU,CAAC,6BAA6B,CAAC,gBAAgB,CAAC,IAAI,CAC1H,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,mBAAmB,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,CAAC,CACnG,CAAC;YACF,IAAI,KAAK,EAAE,OAAO,KAAK,OAAO,EAAE,CAAC;gBAChC,OAAO,KAAK,CAAC;YACd,CAAC;YACD,MAAM,MAAM,GAAG,KAAK,CAAC,CAAC,GAAG,CAAA,0BAA0B,GAAG,CAAC,OAAO,CAAC,qBAAqB,OAAO;0CACrD,WAAW,6BAA6B,CAAC;YAC/E,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBACvB,OAAO,KAAK,CAAC;YACd,CAAC;YACD,KAAK,CAAC,CAAC,GAAG,CAAA,eAAe,GAAG,CAAC,OAAO,CAAC,6CAA6C,SAAS,KAAK,OAAO;0EACjC,CAAC;YACvE,OAAO,IAAI,CAAC;QACb,CAAC,CAAC,CACF;QACD,SAAS,EAAE,GAAG,CAAA,UAAU,GAAG,CAAC,OAAO,CAAC,yDAAyD,SAAS,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC;QAC5H,MAAM,EAAE,GAAG,CAAA,eAAe,GAAG,CAAC,OAAO,CAAC,uBAAuB,SAAS,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC;KAC5F,CAAC;AACH,CAAC,CAAC,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,4 @@
1
+ import { Context } from "effect";
2
+ export class WorkerReleaseTesting extends Context.Service()("@shivaedev/sql/testing/WorkerReleaseTesting") {
3
+ }
4
+ //# sourceMappingURL=WorkerRelease.js.map
@@ -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.35.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.1"
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.33.0",
54
- "@shivaedev/jobs": "0.32.0",
55
- "@shivaedev/quality": "0.57.1",
56
- "@shivaedev/test-kits": "0.1.0",
57
- "@shivaedev/test-sql": "0.18.0",
58
- "@shivaedev/test-story": "0.36.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
  });
@@ -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 > clock_timestamp() - ${staleMillis} * interval '1 millisecond'`;
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}, clock_timestamp())
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: sql`update ${sql(workers)} set heartbeat = clock_timestamp() where process_id = ${processId}`.pipe(Effect.asVoid),
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") {}