@shivaedev/sql 0.32.0 → 0.34.0

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 (120) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/README.md +153 -23
  3. package/dist/MigrationStartedAt.d.ts.map +1 -0
  4. package/dist/{internal/MigrationStartedAt.js → MigrationStartedAt.js} +1 -1
  5. package/dist/MigrationStartedAt.js.map +1 -0
  6. package/dist/batchWrites.d.ts +1 -1
  7. package/dist/batchWrites.d.ts.map +1 -1
  8. package/dist/batchWrites.js.map +1 -1
  9. package/dist/batchedDelete.d.ts +9 -2
  10. package/dist/batchedDelete.d.ts.map +1 -1
  11. package/dist/batchedDelete.js +34 -35
  12. package/dist/batchedDelete.js.map +1 -1
  13. package/dist/changes.d.ts +8 -0
  14. package/dist/changes.d.ts.map +1 -0
  15. package/dist/changes.js +7 -0
  16. package/dist/changes.js.map +1 -0
  17. package/dist/conditionalUpsert.d.ts +4 -3
  18. package/dist/conditionalUpsert.d.ts.map +1 -1
  19. package/dist/conditionalUpsert.js +1 -1
  20. package/dist/conditionalUpsert.js.map +1 -1
  21. package/dist/conflictWrites.d.ts +6 -5
  22. package/dist/conflictWrites.d.ts.map +1 -1
  23. package/dist/conflictWrites.js +1 -1
  24. package/dist/conflictWrites.js.map +1 -1
  25. package/dist/deleteGroupsBatched.d.ts +13 -0
  26. package/dist/deleteGroupsBatched.d.ts.map +1 -0
  27. package/dist/deleteGroupsBatched.js +23 -0
  28. package/dist/deleteGroupsBatched.js.map +1 -0
  29. package/dist/inValues.d.ts +5 -0
  30. package/dist/inValues.d.ts.map +1 -0
  31. package/dist/inValues.js +10 -0
  32. package/dist/inValues.js.map +1 -0
  33. package/dist/insertFromSelect.d.ts +1 -1
  34. package/dist/insertFromSelect.d.ts.map +1 -1
  35. package/dist/insertFromSelect.js.map +1 -1
  36. package/dist/int4Array.d.ts +5 -0
  37. package/dist/int4Array.d.ts.map +1 -0
  38. package/dist/int4Array.js +10 -0
  39. package/dist/int4Array.js.map +1 -0
  40. package/dist/int8.d.ts +5 -0
  41. package/dist/int8.d.ts.map +1 -0
  42. package/dist/int8.js +9 -0
  43. package/dist/int8.js.map +1 -0
  44. package/dist/invalidationKeys.d.ts +8 -0
  45. package/dist/invalidationKeys.d.ts.map +1 -0
  46. package/dist/invalidationKeys.js +15 -0
  47. package/dist/invalidationKeys.js.map +1 -0
  48. package/dist/keysetPage.d.ts +26 -0
  49. package/dist/keysetPage.d.ts.map +1 -0
  50. package/dist/keysetPage.js +23 -0
  51. package/dist/keysetPage.js.map +1 -0
  52. package/dist/listenForChanges.js +2 -2
  53. package/dist/listenForChanges.js.map +1 -1
  54. package/dist/makeConflictClause.d.ts +2 -1
  55. package/dist/makeConflictClause.d.ts.map +1 -1
  56. package/dist/makeConflictClause.js +36 -6
  57. package/dist/makeConflictClause.js.map +1 -1
  58. package/dist/migrationLock.d.ts.map +1 -0
  59. package/dist/migrationLock.js.map +1 -0
  60. package/dist/migrations.d.ts +1 -1
  61. package/dist/migrations.d.ts.map +1 -1
  62. package/dist/migrations.js +18 -9
  63. package/dist/migrations.js.map +1 -1
  64. package/dist/postgresChanges.d.ts.map +1 -1
  65. package/dist/postgresChanges.js +8 -4
  66. package/dist/postgresChanges.js.map +1 -1
  67. package/dist/postgresLayer.d.ts +2 -1
  68. package/dist/postgresLayer.d.ts.map +1 -1
  69. package/dist/postgresLayer.js +1 -1
  70. package/dist/postgresLayer.js.map +1 -1
  71. package/dist/postgresRelease.js +1 -1
  72. package/dist/postgresRelease.js.map +1 -1
  73. package/dist/repositoryChanges.d.ts.map +1 -1
  74. package/dist/repositoryChanges.js +4 -3
  75. package/dist/repositoryChanges.js.map +1 -1
  76. package/dist/transact.d.ts +13 -5
  77. package/dist/transact.d.ts.map +1 -1
  78. package/dist/transact.js +45 -19
  79. package/dist/transact.js.map +1 -1
  80. package/dist/upsertColumns.d.ts +16 -0
  81. package/dist/upsertColumns.d.ts.map +1 -0
  82. package/dist/upsertColumns.js +2 -0
  83. package/dist/upsertColumns.js.map +1 -0
  84. package/dist/versionedStatements.d.ts +1 -1
  85. package/package.json +7 -6
  86. package/src/{internal/MigrationStartedAt.ts → MigrationStartedAt.ts} +1 -1
  87. package/src/batchWrites.ts +2 -1
  88. package/src/batchedDelete.ts +53 -40
  89. package/src/changes.ts +12 -0
  90. package/src/conditionalUpsert.ts +4 -3
  91. package/src/conflictWrites.ts +11 -7
  92. package/src/deleteGroupsBatched.ts +41 -0
  93. package/src/inValues.ts +11 -0
  94. package/src/insertFromSelect.ts +1 -1
  95. package/src/int4Array.ts +14 -0
  96. package/src/int8.ts +14 -0
  97. package/src/invalidationKeys.ts +24 -0
  98. package/src/keysetPage.ts +54 -0
  99. package/src/listenForChanges.ts +2 -2
  100. package/src/makeConflictClause.ts +53 -8
  101. package/src/migrations.ts +21 -10
  102. package/src/postgresChanges.ts +13 -4
  103. package/src/postgresLayer.ts +1 -1
  104. package/src/postgresRelease.ts +1 -1
  105. package/src/repositoryChanges.ts +4 -3
  106. package/src/transact.ts +61 -24
  107. package/src/upsertColumns.ts +16 -0
  108. package/dist/internal/CommandRecovery.d.ts +0 -3
  109. package/dist/internal/CommandRecovery.d.ts.map +0 -1
  110. package/dist/internal/CommandRecovery.js +0 -5
  111. package/dist/internal/CommandRecovery.js.map +0 -1
  112. package/dist/internal/MigrationStartedAt.d.ts.map +0 -1
  113. package/dist/internal/MigrationStartedAt.js.map +0 -1
  114. package/dist/internal/migrationLock.d.ts.map +0 -1
  115. package/dist/internal/migrationLock.js.map +0 -1
  116. package/src/internal/CommandRecovery.ts +0 -5
  117. /package/dist/{internal/MigrationStartedAt.d.ts → MigrationStartedAt.d.ts} +0 -0
  118. /package/dist/{internal/migrationLock.d.ts → migrationLock.d.ts} +0 -0
  119. /package/dist/{internal/migrationLock.js → migrationLock.js} +0 -0
  120. /package/src/{internal/migrationLock.ts → migrationLock.ts} +0 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,39 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.34.0 - 2026-11-03
4
+
5
+ ### Added
6
+
7
+ - The `postgresChanges` binding implements `CommandPublication` with PostgreSQL publication and recovery through local resynchronization and a Resync broadcast.
8
+ - `keysetPage` supplies native SQL seek and ordering fragments, validates positive integer page sizes, and returns rows with an optional continuation from one row of lookahead. Callers supply a unique tie-breaker, predicates, result decoding and cursor values, including exact PostgreSQL timestamp text for microsecond continuation.
9
+ - `sql`'s `postgresLayer` provides its own Reactivity service alongside `PgPool`, `PgClient` and `SqlClient`.
10
+ - Declare raw SQL writers' table, record and bucket keys once with `changes(covers)` from `@shivaedev/sql/changes.ts`. Record the declaration with `record()` and pass it directly to `withChangeCoverage` to check announcements against PostgreSQL writes.
11
+ - Keep a recording local with the typed `{ publication: "local" }` option on `record()` or `invalidateOnCommit`. Local readers refresh after commit without cross-process publication; rollback discards the recording. Shared recordings in the same transaction still publish.
12
+ - `deleteGroupsBatched` selects an ordered prefix of whole groups within per-batch and total row budgets, then deletes each batch in an independent transaction. Group selection and deletion use native Effects and SQL; repository deletes publish item and bucket refreshes after commit. A failed batch rolls back its deletes and announcements while preserving earlier batches.
13
+
14
+ ### Fixed
15
+
16
+ - Omit database causes from PostgreSQL change listener outage warnings and reconnect debug logs while preserving reconnection and live-stream resynchronization.
17
+ - Reject a migration ID recorded under a different name with a typed `BadState` migration error before running new migrations, schema components or release publication, matching readiness's ID and name checks.
18
+ - Match partial unique indexes in conflict writes under PostgreSQL generic plans as well as the default plan mode. Encoded conflict-target predicates use PostgreSQL-quoted constants, with one preliminary quoting query per write; full-index targets and bound write values are unchanged.
19
+ - PostgreSQL retention purges refresh item and authorized bucket query results after each committed batch without requiring collection-wide read permission. Batches remain bounded and commit independently, with constant-size refresh hints.
20
+ - Repository writes from workers refresh matching session-scoped queries over the application's change channel. Every subscriber rechecks its own read policy before receiving hints or results, including rerun-and-compare queries. Client caches retain their registry isolation.
21
+ - Refresh matching subscribers when `invalidateOnCommit` receives typed collection keys. List, item and bucket keys use the shared invalidation encoding before commit buffering; arrays may mix typed and raw Reactivity keys, and record-form invalidations retain their behavior.
22
+
23
+ ### Breaking
24
+
25
+ - Replace `CommandRecovery` from `@shivaedev/sql/internal/CommandRecovery.ts` with `CommandPublication` from `@shivaedev/changes/commandPublication.ts`: yield the port and run its `recover` Effect. Custom providers now supply `{ publish, recover }`; both operations fail with `CommandPublicationError`, so consumers must handle recovery failures. `postgresChanges` provides both operations.
26
+ - Import `MigrationStartedAt` from `@shivaedev/sql/MigrationStartedAt.ts` instead of `@shivaedev/sql/internal/MigrationStartedAt.ts`. Its service identifier is now `@shivaedev/sql/MigrationStartedAt`; provide the exported reference.
27
+ - Import `migrationLock` from `@shivaedev/sql/migrationLock.ts` instead of `@shivaedev/sql/internal/migrationLock.ts`. The SQL `internal` import paths are removed without aliases.
28
+
29
+ ## 0.33.0 - 2026-11-02
30
+
31
+ ### Added
32
+
33
+ - Repository `upsert` and `upsertMany` accept per-field `add` and `keepOnNull` modes in `updateColumns`, including conditional upserts. Counter additions run atomically in SQL, while null incoming values can preserve stored values; record and bucket changes still publish after commit.
34
+ - `inValues(column, values)` supplies a native SQL predicate that matches nothing for an empty list, including qualified columns in `SqlSchema` queries.
35
+ - `int8(field)` and `int4Array(field)` preserve field schemas while encoding native PostgreSQL storage parameters for bigint values and integer arrays, including empty arrays.
36
+
3
37
  ## 0.32.0 - 2026-11-02
4
38
 
5
39
  ### Added
package/README.md CHANGED
@@ -54,7 +54,7 @@ Pair this preset with server's [cache trim and connection lifetime](https://gith
54
54
 
55
55
  ### One PostgreSQL pool per application
56
56
 
57
- `postgresLayer(url, options)` from `@shivaedev/sql/postgresLayer.ts` provides the native `PgPool` service from `@effect/sql-pg/PgPool`, `PgClient` and `SqlClient` together. `options` accepts `applicationName`, `maxConnections`, `minConnections`, `connectionTTL`, `idleTimeout`, `transformQueryNames` and `transformResultNames`. Queries, transactions and raw connections reserved with `yield* pool.reserve` share the same `maxConnections` budget. A reservation holds its connection until its scope closes; release it before waiting for work that needs that slot.
57
+ `postgresLayer(url, options)` from `@shivaedev/sql/postgresLayer.ts` provides the native `PgPool` service from `@effect/sql-pg/PgPool`, `PgClient`, `SqlClient` and a database-local Reactivity service together. `options` accepts `applicationName`, `maxConnections`, `minConnections`, `connectionTTL`, `idleTimeout`, `transformQueryNames` and `transformResultNames`. Queries, transactions and raw connections reserved with `yield* pool.reserve` share the same `maxConnections` budget. A reservation holds its connection until its scope closes; release it before waiting for work that needs that slot.
58
58
 
59
59
  ```ts
60
60
  import * as PgPool from "@effect/sql-pg/PgPool";
@@ -129,27 +129,24 @@ const createdAt = Model.Field({
129
129
 
130
130
  This field is absent from insert and update. A PostgreSQL `timestamptz` default supplies a `Date` on the database boundary, while the JSON codec encodes a string. Choose each field's schema for the values its driver or transport actually returns.
131
131
 
132
- For a PostgreSQL `int4[]` column, give the native driver an explicit element OID so an empty array has a type too:
132
+ For PostgreSQL `int8` and one-dimensional `int4[]` columns, wrap the field's value schema with a storage codec:
133
133
 
134
134
  ```ts
135
- import * as PgTypes from "@effect/sql-pg/PgTypes";
136
- import { Result, SchemaGetter, SchemaParser } from "effect";
135
+ import { int8 } from "@shivaedev/sql/int8.ts";
136
+ import { int4Array } from "@shivaedev/sql/int4Array.ts";
137
137
 
138
138
  const Integers = Schema.Array(Schema.Int);
139
- const StoredIntegers = Schema.Unknown.pipe(
140
- Schema.decodeTo(Integers, {
141
- decode: SchemaGetter.transformEffect((value) => SchemaParser.decodeUnknownEffect(Integers)(PgTypes.isParameter(value) ? value.value : value)),
142
- encode: SchemaGetter.transform((values) => Result.getOrThrow(PgTypes.array(values, PgTypes.OID.int4))),
143
- }),
144
- );
139
+ const StoredIntegers = int4Array(Integers);
145
140
 
146
141
  class Sample extends Model.Class<Sample>("Sample")({
147
- id: Schema.Int,
142
+ id: int8(Schema.BigInt),
148
143
  values: Model.Field({ insert: StoredIntegers, select: StoredIntegers, update: StoredIntegers, json: Integers }),
149
144
  }) {}
150
145
  ```
151
146
 
152
- The decoded value is an integer array in every variant. The database variants encode a native typed `int4[]` parameter; JSON retains the ordinary array. The same storage codec on `select` lets equality filters encode empty arrays too. `PgTypes.array` resolves the built-in `int4` array OID; `getOrThrow` unwraps that known constructor result. PostgreSQL still enforces its integer range on writes.
147
+ `int8(field)` keeps bigint values exact beyond `Number.MAX_SAFE_INTEGER` and encodes native `PgTypes.int8` parameters. The native driver returns `bigint`; no conversion through a JS number occurs. `int4Array(field)` encodes a native array parameter with the `int4` element OID, including for an empty array. Both take the field schema, preserve its validation, and unwrap their native parameters when repository writes decode returned rows. PostgreSQL still enforces its integer ranges.
148
+
149
+ Use the array storage codec on select, insert and update so equality filters encode empty arrays too. Keep an ordinary array schema for JSON. A bigint field transported as JSON likewise needs an explicit transport codec in its JSON variant; these helpers describe database storage.
153
150
 
154
151
  For a PostgreSQL `jsonb` column, wrap the value schema with `jsonb`:
155
152
 
@@ -346,6 +343,62 @@ Here `archive` is another unversioned `InvoiceLine` repository with its own tabl
346
343
 
347
344
  A repository configured with `conflictColumns` also exposes `insertFromSelectOrIgnore(source, columns)`. It uses the same conflict columns and optional `conflictWhere` predicate as `insertOrIgnore`, skips existing keys, and returns only inserted rows. An empty source returns `[]`. Both writes join `transact` and roll back with it. Source filter encoding services and target result decoding services remain in the write's Effect environment.
348
345
 
346
+ ### Native SQL list predicates
347
+
348
+ `inValues(column, values)` from `@shivaedev/sql/inValues.ts` returns an Effect requiring the native `SqlClient`. It builds a native SQL fragment: an empty list matches nothing, and a non-empty list binds each value as a parameter. Pass a column name or a native fragment for a qualified column. Keep request encoding, result decoding and the SQL itself in native `SqlSchema`:
349
+
350
+ ```ts
351
+ import { SqlClient, SqlSchema } from "effect/sql";
352
+ import { inValues } from "@shivaedev/sql/inValues.ts";
353
+
354
+ const sql = yield* SqlClient.SqlClient;
355
+ const paymentsBySequence = SqlSchema.findAll({
356
+ Request: Schema.Array(Payment.fields.sequence),
357
+ Result: Schema.Struct({ sequence: Payment.fields.sequence }),
358
+ execute: (sequences) => Effect.gen(function* () {
359
+ const predicate = yield* inValues(sql`p.${sql("sequence")}`, sequences);
360
+ return yield* sql`select p.sequence from payments p where ${predicate}`;
361
+ }),
362
+ });
363
+
364
+ const rows = yield* paymentsBySequence([]);
365
+ ```
366
+
367
+ `rows` is empty even when the table contains payments. The helper takes its column explicitly and leaves native `SqlSchema` visible. Use native SQL with composed schemas for joins; this helper adds no query layer or typed join.
368
+
369
+ ### Keyset pages over native SQL
370
+
371
+ `keysetPage` builds seek and ordering fragments and asks your `read` callback for one extra row. It returns `{ rows, next }`, where `next` is an `Option` containing the last returned row's cursor only when another row exists. Your query keeps its predicates, joins and result schema:
372
+
373
+ ```ts
374
+ import { Effect, Schema } from "effect";
375
+ import { SqlClient } from "effect/sql";
376
+ import { keysetPage } from "@shivaedev/sql/keysetPage.ts";
377
+
378
+ const Cursor = Schema.Struct({ at: Schema.String, id: Schema.Number });
379
+ const PageRow = Schema.Struct({ id: Schema.Number, cursorAt: Schema.String, updatedAt: Schema.Date });
380
+
381
+ const listNotes = Effect.fnUntraced(function* (owner: string, after?: typeof Cursor.Type) {
382
+ const sql = yield* SqlClient.SqlClient;
383
+ return yield* keysetPage({
384
+ limit: 20,
385
+ after,
386
+ orderBy: [{ column: sql`n.updated_at`, direction: "desc", value: (cursor: typeof Cursor.Type) => cursor.at }],
387
+ tieBreaker: { column: sql`n.id`, direction: "desc", value: (cursor) => cursor.id },
388
+ cursorFor: (row: typeof PageRow.Type) => ({ at: row.cursorAt, id: row.id }),
389
+ read: ({ seek, orderBy, limit }) =>
390
+ sql`select n.id, n.updated_at as "updatedAt", to_json(n.updated_at) #>> '{}' as "cursorAt"
391
+ from notes n where n.owner = ${owner} and ${seek} order by ${orderBy} limit ${limit}`.pipe(
392
+ Effect.flatMap(Schema.decodeUnknownEffect(Schema.Array(PageRow))),
393
+ ),
394
+ });
395
+ });
396
+ ```
397
+
398
+ All ordering expressions must be non-null. The required `tieBreaker` must be unique across the query's rows, normally a primary key; the caller owns that database constraint. It is appended to `orderBy`, which can be empty when the unique key alone orders the list. Each column can be a name or a native fragment, and each direction can be `asc` or `desc`, including mixed directions. `read` must apply the supplied seek predicate, ordering and lookahead limit. Page sizes must be positive safe integers smaller than `Number.MAX_SAFE_INTEGER`; invalid sizes fail with `SchemaError` before `read` runs.
399
+
400
+ Keep timestamp continuation values separate from decoded dates: the example projects the PostgreSQL `timestamptz` value as exact text with `to_json`, retaining microseconds while the display field uses `Schema.Date`. Order and compare the original timestamp column, and return the text unchanged through `cursorFor`. Cursor values accept strings, numbers, bigints and booleans, excluding `Date` and null. Cursor encoding and request validation stay with the caller. Reuse the same predicates and ordering on subsequent requests. This is a page read, with no snapshot or live infinite-list behavior.
401
+
349
402
  ### Composite keys and idempotent writes
350
403
 
351
404
  A generated app can ingest the same reading more than once. Declare the table's primary key with an `idColumn` tuple and its unique conflict key with `conflictColumns`:
@@ -384,7 +437,7 @@ The application creates `readings` with `PRIMARY KEY (source, sequence)`; `seque
384
437
  | `insertManyOrIgnore` | Readonly array of model insert inputs | Decoded inserted models; conflicting rows are omitted. |
385
438
  | `upsertMany` | Readonly array of model insert inputs and `{ updateColumns: [...] }` | An array of `{ row, inserted }` results. |
386
439
 
387
- All four methods encode with the model's insert schema and retain its codec services. `upsert` and `upsertMany` require a nonempty list of inserted, updateable fields; primary-key, conflict-key and generated fields are excluded. Only those columns take their incoming encoded values on conflict. Other stored values stay intact. A selected optional field must be present in every encoded input. Unknown, empty, protected or absent update columns fail with `SchemaError`. A violation of another unique constraint remains a native `SqlError`.
440
+ All four methods encode with the model's insert schema and retain its codec services. `upsert` and `upsertMany` require a nonempty list of inserted, updateable fields; primary-key, conflict-key and generated fields are excluded. String entries replace those columns with their incoming encoded values on conflict. Other stored values stay intact. A selected optional field must be present in every encoded input. Unknown, empty, protected or absent update columns fail with `SchemaError`. A violation of another unique constraint remains a native `SqlError`.
388
441
 
389
442
  For a partial unique index such as `CREATE UNIQUE INDEX ON entries (a, b) WHERE kind = 'x'`, add `conflictWhere` beside `conflictColumns`:
390
443
 
@@ -399,7 +452,21 @@ const entries = yield* makeRepository(Entry, {
399
452
  const inserted = yield* entries.insertManyOrIgnore(inputs);
400
453
  ```
401
454
 
402
- All four conflict methods use this target, producing `ON CONFLICT (a, b) WHERE kind = $n` before `DO NOTHING` or `DO UPDATE SET ...`, followed by `RETURNING`. `conflictWhere` uses typed equality entries like `findMany.where`: decoded field values pass through their field codecs and remain bound parameters. Rows outside the index predicate can insert with the same key. A target that matches no unique index fails with native `SqlError` in the error channel. Use native `SqlClient` and `SqlSchema` for SQL expressions or other conflict policies.
455
+ All four conflict methods use this target, producing `ON CONFLICT (a, b) WHERE kind = 'x'` before `DO NOTHING` or `DO UPDATE SET ...`, followed by `RETURNING`. `conflictWhere` uses typed equality entries like `findMany.where`: decoded field values pass through their field codecs. PostgreSQL quotes the encoded target values in one preliminary query so the conflict target contains constants and matches the partial index under both default and generic plans. Insert values and `updateWhere` operands remain bound parameters. Rows outside the index predicate can insert with the same key. A target that matches no unique index fails with native `SqlError` in the error channel. Use native `SqlClient` and `SqlSchema` for SQL expressions or other conflict policies.
456
+
457
+ Declare conflict behavior per field in the same `updateColumns` list:
458
+
459
+ ```ts
460
+ const saved = yield* counters.upsert(incoming, {
461
+ updateColumns: [
462
+ { column: "count", mode: "add" },
463
+ { column: "label", mode: "keepOnNull" },
464
+ "note",
465
+ ],
466
+ });
467
+ ```
468
+
469
+ `add` computes the stored column plus its incoming encoded value inside the conflict update, so concurrent increments from separate PostgreSQL connections all count. Its model field must decode to a number, bigint or `BigDecimal`; choose a storage codec and SQL column that support native addition. `keepOnNull` uses the incoming value when it is non-null and keeps the stored value when it is SQL `NULL`. A string entry still replaces the field. Inserts use the incoming values directly. These entries also work in `upsertMany` and with `updateWhere`, and publish the same record and old/new bucket keys as other repository writes.
403
470
 
404
471
  On PostgreSQL, pass `updateWhere` to conditionally apply the update branch:
405
472
 
@@ -442,7 +509,7 @@ Each batch call starts `transact`, so a later chunk failure rolls back earlier c
442
509
 
443
510
  On PostgreSQL, `deleteBatched({ where, batchSize, maxRows, pause })` removes matching rows in independently committed transactions and returns the number removed. `where` uses the same encoded filters as `findMany`, including comparisons. Each batch deletes at most `batchSize` rows; the last batch shrinks to respect `maxRows`. `batchSize` is a positive safe integer and `maxRows` is a nonnegative safe integer. A zero cap or no matching rows returns zero without announcing changes.
444
511
 
445
- Each nonempty batch publishes one table-level change hint after commit, so queries watching the table refresh once per batch. It does not enumerate record or bucket keys. Use table subscriptions for data subject to retention purges. A later failure rolls back only its batch and its hint; earlier batches remain committed. SQL failures remain typed `SqlError` and invalid limits fail with `SchemaError`.
512
+ Each nonempty batch publishes one collection-wide change hint after commit, so table, item and bucket subscribers refresh after each batch. The hint has a constant size and does not enumerate deleted records or bucket values. Viewers keep their existing query permissions; a refresh does not require access to the whole table. A later failure rolls back only its batch and its hint; earlier batches remain committed. SQL failures remain typed `SqlError` and invalid limits fail with `SchemaError`.
446
513
 
447
514
  `pause` is an Effect duration between full batches while the cap permits more work. The method holds a PostgreSQL session advisory lock for the run; another purge of that table skips and returns zero, even when called through another repository instance. The lock is released on completion, failure or interruption. The run reserves one connection for the lock and uses another for batch transactions: allow at least two pool connections, plus capacity for other work. Use a direct PostgreSQL connection or session pooling, since transaction pooling cannot preserve session locks. Rows locked by other transactions are skipped and can be removed by a later run. Purges have no ordering guarantee.
448
515
 
@@ -480,6 +547,41 @@ export const PurgeAudit = defineSchedule({
480
547
 
481
548
  Here the application's `AuditEntry` model has a `createdAt` field decoded as `Date`. Register `PurgeAudit` in the jobs Layer's `jobs` list and provide the database and change-feed Layers to the worker.
482
549
 
550
+ #### Whole groups across tables
551
+
552
+ Use `deleteGroupsBatched` from `@shivaedev/sql/deleteGroupsBatched.ts` when a parent and its children must fit together. Keep `repository.deleteBatched` for single-table purges with constant-size collection hints. The grouped helper takes the same `batchSize`, `maxRows` and `pause`, plus a parent `tableName`, `select(limit)` and `delete(value)` callbacks. It shares the parent table's purge lock with single-table runs and requires PostgreSQL, at least two pool connections and a call outside transactions.
553
+
554
+ `select` runs inside each batch transaction and returns an ordered array of `{ value, rowCount }`. Return at most `limit` candidates, with a positive safe-integer count including every parent and child row that `delete` will remove. Lock candidate parents with native SQL, and have concurrent group writers take the same parent lock before changing children, so counts remain accurate through deletion. Count rows in a separate statement after acquiring the parent locks, as below, to include changes committed before lock acquisition. The helper selects the longest prefix that fits the smaller of the batch budget and the remaining total budget; it never splits a group or skips past an oversized first group. If no group fits, the run returns the count already removed. A partially filled batch can be followed by another batch after `pause`.
555
+
556
+ ```ts
557
+ import { Effect } from "effect";
558
+ import { deleteGroupsBatched } from "@shivaedev/sql/deleteGroupsBatched.ts";
559
+ import { inValues } from "@shivaedev/sql/inValues.ts";
560
+
561
+ const removed = yield* deleteGroupsBatched({
562
+ tableName: "runs",
563
+ batchSize: 1_000,
564
+ maxRows: 50_000,
565
+ pause: "100 millis",
566
+ select: (limit) => Effect.gen(function* () {
567
+ const locked = yield* sql<{ id: number }>`
568
+ select id from runs where finished_at < ${cutoff}
569
+ order by id limit ${limit} for update skip locked`;
570
+ const selected = yield* inValues(sql`r.id`, locked.map(({ id }) => id));
571
+ return yield* sql<{ value: number; rowCount: number }>`
572
+ select r.id as value,
573
+ (1 + (select count(*) from run_steps s where s.run_id = r.id))::int as "rowCount"
574
+ from runs r where ${selected} order by r.id`;
575
+ }),
576
+ delete: (runId) => Effect.gen(function* () {
577
+ yield* steps.delete({ runId });
578
+ yield* runs.delete(runId);
579
+ }),
580
+ });
581
+ ```
582
+
583
+ Here `sql` is the native client and `steps` and `runs` are repositories. The callbacks own eligibility, group membership, locking and accurate row counts; the helper owns budget selection and the independent commit boundary. Use repository deletes for each table, including children, so deleted items and declared owner buckets refresh after each committed batch. Unaffected owner readers receive no deleted identities. Raw SQL deletes instead need explicit `invalidateOnCommit` keys. A failure anywhere in a batch discards all of that batch's deletes and announcements, including earlier groups in the same batch; previous committed batches and refreshes remain. Callback failures and required services remain in the returned Effect, alongside SQL, schema and purge-availability errors.
584
+
483
585
  ### Optimistic concurrency
484
586
 
485
587
  Declare a numeric version field in the model's select and update variants, leave it out of insert, and select it with `versionColumn`:
@@ -542,6 +644,25 @@ const saveInvoiceLine = (input: typeof InvoiceLine.update.Type) =>
542
644
 
543
645
  `transact` wraps native `sql.withTransaction`. It maps typed `SqlError` failures through `onSqlError` and keeps other typed failures and service requirements. `invalidateOnCommit` records derived reader keys that a repository or command declaration does not already name; after the owning outermost commit, they are deduplicated and passed to native `Reactivity.invalidate` once. The record `{ invoiceLines: [1] }` marks `"invoiceLines"` and `"invoiceLines:1"`. An array such as `["invoiceLines:1"]` marks only those keys.
544
646
 
647
+ Arrays also accept typed keys from `collection`: a list key refreshes list subscribers, an item key refreshes that item and its list, and a bucket key refreshes that bucket. Subscribers register these keys through `readKeys` from `@shivaedev/changes/keys.ts`. Typed keys may share an array with raw Reactivity keys; raw keys keep their native identity.
648
+
649
+ For a raw SQL writer, declare affected records and buckets once with `changes` from `@shivaedev/sql/changes.ts`, grouped by their SQL table name. The returned declaration's `record()` action uses the same transaction boundary as repository writes:
650
+
651
+ ```ts
652
+ import { changes } from "@shivaedev/sql/changes.ts";
653
+
654
+ const moved = changes({
655
+ notes: [notes.item(id), notes.bucket.listId(oldList), notes.bucket.listId(newList), notes.bucket.owner(owner)],
656
+ });
657
+
658
+ yield* Effect.gen(function* () {
659
+ yield* sql`update notes set "listId" = ${newList} where id = ${id}`;
660
+ yield* moved.record();
661
+ }).pipe(transact({ onSqlError: (error) => error }));
662
+ ```
663
+
664
+ Use `moved.record({ publication: "local" })` to refresh local readers after commit without cross-process publication. `invalidateOnCommit(keys, { publication: "local" })` accepts the same typed option. The default is `"shared"`. Local and shared keys can coexist in one transaction; a key recorded both ways publishes once. Rollback discards both kinds. The option applies to that recording, so other writers in the transaction can still publish shared keys.
665
+
545
666
  Mark bucket fields on the model with `bucket` from `@shivaedev/changes/buckets.ts`:
546
667
 
547
668
  ```ts
@@ -638,7 +759,7 @@ Use sequential numbers, not timestamps. Effect's Migrator skips any migration wh
638
759
 
639
760
  `migrationConvention.ts` exports `MIGRATIONS_DIRECTORY` and `MIGRATION_FILE_PATTERN` so template packaging and repository quality configuration share the folder and filename convention.
640
761
 
641
- `migratePostgres` rejects loaded migration IDs at or below the latest applied ID that are missing from the ledger with a typed native `MigrationError` (`kind: "BadState"`) naming those IDs.
762
+ `migratePostgres` rejects loaded migration IDs at or below the latest applied ID that are missing from the ledger with a typed native `MigrationError` (`kind: "BadState"`) naming those IDs. It also rejects a loaded ID recorded under a different name, matching readiness's ID and name checks. Both checks run before any new migration body, schema component or release publication. Later ledger entries absent from the loader are allowed for older releases.
642
763
 
643
764
  `migratePostgres` discovers migrations, then takes an advisory lock before creating the ledger and running native Migrator. Runs with only numbered migrations use a transaction lock and work with a one-connection pool. Runs with schema components or a `release` use a session lock that stays held through the ledger transaction, all schema components and release publication, and releases on completion, failure or interruption. Both lock modes coordinate on the same ledger key. Concurrent runners on a fresh or existing ledger wait, then see the committed ledger and avoid repeating migrations; their schema components never overlap. Every runner of that ledger must use this helper and the same ledger name. The session lock reserves one pool connection while migration and component SQL use other connections: allow at least two connections for an active runner with components or a release, plus room for waiting runners and other work. Those runs require a direct PostgreSQL connection or session pooling. The required `lockTimeout` bounds acquisition of the advisory lock and lock waits in the ledger transaction; `Duration.infinity` disables those lock timeouts. Choose deployment and execution timeouts in the application.
644
765
 
@@ -668,7 +789,9 @@ const delivery = postgresChanges({
668
789
 
669
790
  `pg` is the application's `@effect/sql-pg` client; the binding accepts its native SQL and `listen` methods without creating another pool. Optional `listenerClient` supplies a different native client for LISTEN; the application client still owns write notifications and heartbeat publication. Both clients must reach the same database. LISTEN requires a direct or session-mode connection. With one shared client, reserve one session connection for LISTEN and another for writes and heartbeats; use a pool client with at least two connections. With separate clients, the listener needs one connection of its own. Compose `delivery` into the Layer that provides feature services using `transact`, alongside the existing SQL and Reactivity services. The [reference server](https://github.com/ShivaeDev/platform/blob/main/packages/react/src/test-support/postgres-example/server.ts) shows the complete composition. The binding sends `select pg_notify(...)` through the writing transaction's `SqlClient`, so a rollback wakes no process. Its transaction sink leaves delivery to the listener, including in the writing process. Unframed `invalidateOnCommit` calls retain local invalidation; cross-process writes must use `transact`.
670
791
 
671
- `PostgresChangesOptions` takes `channel`, `client`, `resync`, optional `listenerClient` and `connectionSetting` (a diagnostic label, default `"client"`), and optional `heartbeatInterval`, `heartbeatTimeout` and `retryDelay` Effect durations. The defaults are 5 seconds, 10 seconds and 1 second. Acquisition waits for LISTEN and then a heartbeat received through it, each within `heartbeatTimeout`. A deadline without a heartbeat fails startup with `PostgresListenerError` from `@shivaedev/sql/PostgresListenerError.ts`, whose `connectionSetting` and message identify the connection to fix. Native SQL failures still fail startup with `SqlError`. Established listeners reconnect after a loss. Loss and reconnect failures log their causes. A missing heartbeat or a stream failure resyncs streams before listener cleanup; a successful reconnection resyncs again. Listener cleanup runs in a parallel sub-scope bounded by `heartbeatTimeout`, so stalled listener cleanup cannot delay native connection release, a fresh subscription or scope shutdown. The heartbeat detects a listener that stops delivering without failing. `resync` is an infallible `Effect.Effect<number>` returning the number of open live streams it marked for reconciliation. `makeLiveServer.resync` provides this count and stops counting a stream when its scope closes. Direct bindings with another stream owner must return that owner's count. Provide the Layer for the process lifetime so scope release closes the listener.
792
+ The Layer also provides `CommandPublication` from `@shivaedev/changes/commandPublication.ts`. Call `publish(keys)` after a command's write commits to announce its declared keys through the bound client. An enclosing `transact` delays these keys until its outer commit and discards them on rollback. Publication failures use the typed `CommandPublicationError`; `recover` resynchronizes local streams and broadcasts Resync through PostgreSQL. Recovery failures use the same typed error, so the command boundary can log a delivery gap while preserving the committed result.
793
+
794
+ `PostgresChangesOptions` takes `channel`, `client`, `resync`, optional `listenerClient` and `connectionSetting` (a diagnostic label, default `"client"`), and optional `heartbeatInterval`, `heartbeatTimeout` and `retryDelay` Effect durations. The defaults are 5 seconds, 10 seconds and 1 second. Acquisition waits for LISTEN and then a heartbeat received through it, each within `heartbeatTimeout`. A deadline without a heartbeat fails startup with `PostgresListenerError` from `@shivaedev/sql/PostgresListenerError.ts`, whose `connectionSetting` and message identify the connection to fix. Native SQL failures still fail startup with `SqlError`. Established listeners reconnect after a loss. Loss warnings and reconnect debug logs omit database causes. A missing heartbeat or a stream failure resyncs streams before listener cleanup; a successful reconnection resyncs again. Listener cleanup runs in a parallel sub-scope bounded by `heartbeatTimeout`, so stalled listener cleanup cannot delay native connection release, a fresh subscription or scope shutdown. The heartbeat detects a listener that stops delivering without failing. `resync` is an infallible `Effect.Effect<number>` returning the number of open live streams it marked for reconciliation. `makeLiveServer.resync` provides this count and stops counting a stream when its scope closes. Direct bindings with another stream owner must return that owner's count. Provide the Layer for the process lifetime so scope release closes the listener.
672
795
 
673
796
  NOTIFY is a wake-up signal, not a durable log. Transaction payloads contain only string or finite-number invalidation keys, with no row values. PostgreSQL requires each payload to be smaller than 8,000 bytes in the default configuration. The binding splits JSON arrays by their UTF-8 byte length; one key that cannot fit, or a nonserializable key type, fails with `SqlError` before commit and rolls back the write. Heartbeats use a separate control payload on the same channel. Applications choose a channel shared only by their cooperating processes. LISTEN requires a session connection; PgBouncer transaction pooling needs another delivery adapter. Missed notifications are reconciled by querying again on resync, with no replay guarantee.
674
797
 
@@ -676,6 +799,10 @@ NOTIFY is a wake-up signal, not a durable log. Transaction payloads contain only
676
799
 
677
800
  Import the module that defines the name; there is no root entry.
678
801
  - `@shivaedev/sql/clientOptions.ts`: `clientOptions(options, { snakeCase? })`, `SqlNameTransforms` — Add default native name transforms to driver or app host options; opt out with `snakeCase: false`.
802
+ - `@shivaedev/sql/inValues.ts`: `inValues(column, values)` — Native list predicate with match-nothing semantics for an empty list; requires `SqlClient`.
803
+ - `@shivaedev/sql/keysetPage.ts`: `keysetPage(options)`, `KeysetOrder`, `KeysetPageQuery`, `KeysetPage` — Native seek and order fragments with a required unique tie-breaker, validated page size and one-row lookahead; predicates, result schemas and cursor formats stay with the caller.
804
+ - `@shivaedev/sql/int8.ts`: `int8(field)` — PostgreSQL bigint storage codec using the supplied field schema.
805
+ - `@shivaedev/sql/int4Array.ts`: `int4Array(field)` — PostgreSQL integer-array storage codec, including typed empty arrays.
679
806
  - `@shivaedev/sql/repository.ts`: `makeRepository(model, options)` — `tableName`, scalar or composite `idColumn`, `spanPrefix`, optional `versionColumn`, `conflictColumns` and `conflictWhere`, and `collection` required for bucket models; requires native `SqlClient`.
680
807
  - `@shivaedev/sql/repository.ts`: `OptimisticConcurrencyError` — Typed stale or missing-row failure for versioned writes, with `tableName`, `id`, `expectedVersion`, `reason: "missing" | "stale"`.
681
808
  - `@shivaedev/sql/tupleIn.ts`: `tupleIn(model, fields, tuples, types)` — Filter entry matching two or more fields together against a list of tuples, for `where` in reads and unversioned deletes.
@@ -683,7 +810,7 @@ Import the module that defines the name; there is no root entry.
683
810
  - `@shivaedev/sql/conditionalUpsert.ts`: `UpsertFilter`, `UpsertResult` — The `updateWhere` filter shape and the tagged `Inserted`, `Updated` or `Unchanged` result of a conditional upsert.
684
811
  - `@shivaedev/sql/mapSqlError.ts`: `mapSqlError({ constraints, fallback })` — Mapper from named constraint violations to application errors, with a fallback for every other `SqlError`.
685
812
  - `@shivaedev/sql/transact.ts`: `transact(effect, options)` or `effect.pipe(transact(options))` — Required `onSqlError` mapper; requires `SqlClient` and `Reactivity`.
686
- - `@shivaedev/sql/transact.ts`: `invalidateOnCommit(keys)` — Records keys for its client, or invalidates immediately outside a transaction.
813
+ - `@shivaedev/sql/transact.ts`: `invalidateOnCommit(keys, options?)` — Records keys for its client, or invalidates immediately outside a transaction.
687
814
  - `@shivaedev/sql/advisoryXactLock.ts`: `advisoryXactLock(key)`, `AdvisoryLockOutsideTransaction` — PostgreSQL transaction lock on a bigint or string hashed with `hashtextextended(key, 0)`; requires an active transaction on the current `SqlClient`.
688
815
  - `@shivaedev/sql/migrationConvention.ts`: `MIGRATIONS_DIRECTORY`, `MIGRATION_FILE_PATTERN` — Application folder and four-digit sequential snake_case TypeScript filename pattern.
689
816
  - `@shivaedev/sql/migrationsFromDirectory.ts`: `migrationsFromDirectory(root)` — Discover migrations under the application root with native `FileSystem` and `Path`.
@@ -693,6 +820,7 @@ Import the module that defines the name; there is no root entry.
693
820
  - `@shivaedev/sql/postgresRelease.ts`: `postgresRelease(release, table?)`, `readPostgresRelease(table?)` — Release marker schema component and its reader.
694
821
  - `@shivaedev/sql/postgresChanges.ts`: `postgresChanges(options)` — Scoped PostgreSQL LISTEN/NOTIFY Layer for commit-bound invalidation.
695
822
  - `@shivaedev/sql/repositoryCollection.ts`: `BucketRegistrationError` — Registration failure with table, collection and bucket field names.
823
+ - `@shivaedev/sql/changes.ts`: `changes(covers)`, `ChangeDeclaration` — Declares a raw writer's table, record and bucket keys once, with a `record(options?)` action.
696
824
  - `@shivaedev/sql/testing.ts`: `withChangeCoverage(body, { covers })`, `expectPublished(keys)`, `UnannouncedWrites`, `PublishedKeysMismatch` — Test assertions that every write announced its keys and that the expected keys were published.
697
825
 
698
826
  ### Install and database setup
@@ -747,7 +875,7 @@ The mapper handles named unique violations and PostgreSQL foreign-key and check
747
875
 
748
876
  Recover outside `transact` so earlier writes roll back before handling a mapped domain error. A losing unique-key insert can then catch `NameTaken` and read the winner's row without a private transaction error. See the [constraint-failures guide](https://github.com/ShivaeDev/platform/blob/main/docs/framework/constraint-errors.md).
749
877
 
750
- For the native PostgreSQL driver's default representations, use precise numeric strings with `Schema.BigDecimalFromString`, `int8` values with `Schema.BigInt`, `date` values as `YYYY-MM-DD` strings with `Schema.String`, instants with `Schema.Date`, nullable text and generated values. A persisted value that violates its schema fails decoding; a write and read inside the enclosing transaction roll back together when that failure escapes.
878
+ For the native PostgreSQL driver's default representations, use precise numeric strings with `Schema.BigDecimalFromString`, `int8` values with `int8(Schema.BigInt)`, `date` values as `YYYY-MM-DD` strings with `Schema.String`, instants with `Schema.Date`, nullable text and generated values. A persisted value that violates its schema fails decoding; a write and read inside the enclosing transaction roll back together when that failure escapes.
751
879
 
752
880
  JSON and JSONB columns are not supported. A repository write of a plain object to one fails with `SqlError` and writes nothing. Give structured data its own columns, or its own table when it repeats, so PostgreSQL constraints and the model both describe it.
753
881
 
@@ -755,17 +883,19 @@ JSON and JSONB columns are not supported. A repository write of a plain object t
755
883
 
756
884
  The package's PostgreSQL tests use a disposable database through `PLATFORM_SQL_TEST_DATABASE_URL`. Without the URL they skip locally; under `CI`, collection fails at the shared gate.
757
885
 
758
- The sql story kit exposes `withChangeCoverage` and `expectPublished`, also exported from `@shivaedev/sql/testing.ts`. `withChangeCoverage(body, { covers })` checks PostgreSQL transaction write counters before commit and requires every declared record and bucket key to be recorded. Repository writes supply these declarations automatically. Raw writes must declare their affected keys through `covers` and record them through the same after-commit path; a table key alone covers nothing. Missing announcements fail with `UnannouncedWrites` and roll back the transaction.
886
+ The sql story kit exposes `withChangeCoverage` and `expectPublished`, also exported from `@shivaedev/sql/testing.ts`. `withChangeCoverage(body, { covers })` checks PostgreSQL transaction write counters before commit and requires every declared record and bucket key to be recorded. Repository writes supply these declarations automatically. Raw writers can pass the declaration returned by `changes` directly to `withChangeCoverage` and record it through `record()`; a table key alone covers nothing. Local recordings satisfy coverage too. Missing announcements fail with `UnannouncedWrites` and roll back the transaction.
759
887
 
760
888
  ```ts
761
- const changed = [notes.item(id), notes.bucket.listId(oldList), notes.bucket.listId(newList), notes.bucket.owner(owner)];
889
+ const changed = changes({
890
+ notes: [notes.item(id), notes.bucket.listId(oldList), notes.bucket.listId(newList), notes.bucket.owner(owner)],
891
+ });
762
892
 
763
893
  yield* withChangeCoverage(
764
894
  Effect.gen(function* () {
765
895
  yield* sql`update notes set "listId" = ${newList} where id = ${id}`;
766
- yield* invalidateOnCommit(invalidationKeys(changed));
896
+ yield* changed.record();
767
897
  }),
768
- { covers: { notes: changed } },
898
+ changed,
769
899
  );
770
900
  ```
771
901
 
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MigrationStartedAt.d.ts","sourceRoot":"","sources":["../src/MigrationStartedAt.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AAEjC,eAAO,MAAM,kBAAkB,uCAE7B,CAAC"}
@@ -1,5 +1,5 @@
1
1
  import { Context } from "effect";
2
- export const MigrationStartedAt = Context.Reference("@shivaedev/sql/internal/MigrationStartedAt", {
2
+ export const MigrationStartedAt = Context.Reference("@shivaedev/sql/MigrationStartedAt", {
3
3
  defaultValue: () => undefined,
4
4
  });
5
5
  //# sourceMappingURL=MigrationStartedAt.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MigrationStartedAt.js","sourceRoot":"","sources":["../src/MigrationStartedAt.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AAEjC,MAAM,CAAC,MAAM,kBAAkB,GAAG,OAAO,CAAC,SAAS,CAAqB,mCAAmC,EAAE;IAC5G,YAAY,EAAE,GAAG,EAAE,CAAC,SAAS;CAC7B,CAAC,CAAC"}
@@ -12,5 +12,5 @@ export declare function makeInsertMany<TServices = never>(model: Model.Any, sql:
12
12
  readonly tableName: string;
13
13
  readonly spanPrefix: string;
14
14
  readonly changes: RepositoryChanges;
15
- }, operation: string, prepare?: (row: Readonly<Record<string, unknown>>) => Effect.Effect<Fragment, Schema.SchemaError, TServices>, upsert?: boolean): (inputs: readonly unknown[]) => Effect.Effect<unknown[], Schema.SchemaError | SqlError, unknown>;
15
+ }, operation: string, prepare?: (row: Readonly<Record<string, unknown>>) => Effect.Effect<Fragment, SqlError | Schema.SchemaError, TServices>, upsert?: boolean): (inputs: readonly unknown[]) => Effect.Effect<unknown[], Schema.SchemaError | SqlError, unknown>;
16
16
  //# sourceMappingURL=batchWrites.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"batchWrites.d.ts","sourceRoot":"","sources":["../src/batchWrites.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AACxC,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AAC3C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAC5C,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAGhE,MAAM,MAAM,UAAU,CAAC,MAAM,SAAS,KAAK,CAAC,GAAG,EAAE,SAAS,GAAG,KAAK,IAAI,CACrE,MAAM,EAAE,SAAS,MAAM,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,EAAE,KACvC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,EAAE,QAAQ,GAAG,MAAM,CAAC,WAAW,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC,kBAAkB,CAAC,GAAG,MAAM,CAAC,kBAAkB,CAAC,GAAG,SAAS,CAAC,CAAC;AAEnJ,MAAM,WAAW,WAAW,CAAC,MAAM,SAAS,KAAK,CAAC,GAAG;IACpD,QAAQ,CAAC,UAAU,EAAE,UAAU,CAAC,MAAM,CAAC,CAAC;CACxC;AAED,wBAAgB,cAAc,CAAC,SAAS,GAAG,KAAK,EAC/C,KAAK,EAAE,KAAK,CAAC,GAAG,EAChB,GAAG,EAAE,SAAS,CAAC,SAAS,EACxB,OAAO,EAAE;IAAE,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,iBAAiB,CAAA;CAAE,EACzG,SAAS,EAAE,MAAM,EACjB,OAAO,GAAE,CAAC,GAAG,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,KAAK,MAAM,CAAC,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,WAAW,EAAE,SAAS,CAA+B,EACzI,MAAM,UAAQ,YAOE,SAAS,OAAO,EAAE,sEA2ClC"}
1
+ {"version":3,"file":"batchWrites.d.ts","sourceRoot":"","sources":["../src/batchWrites.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AACxC,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AAC3C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAC5C,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAGhE,MAAM,MAAM,UAAU,CAAC,MAAM,SAAS,KAAK,CAAC,GAAG,EAAE,SAAS,GAAG,KAAK,IAAI,CACrE,MAAM,EAAE,SAAS,MAAM,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,EAAE,KACvC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,EAAE,QAAQ,GAAG,MAAM,CAAC,WAAW,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC,kBAAkB,CAAC,GAAG,MAAM,CAAC,kBAAkB,CAAC,GAAG,SAAS,CAAC,CAAC;AAEnJ,MAAM,WAAW,WAAW,CAAC,MAAM,SAAS,KAAK,CAAC,GAAG;IACpD,QAAQ,CAAC,UAAU,EAAE,UAAU,CAAC,MAAM,CAAC,CAAC;CACxC;AAED,wBAAgB,cAAc,CAAC,SAAS,GAAG,KAAK,EAC/C,KAAK,EAAE,KAAK,CAAC,GAAG,EAChB,GAAG,EAAE,SAAS,CAAC,SAAS,EACxB,OAAO,EAAE;IAAE,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,iBAAiB,CAAA;CAAE,EACzG,SAAS,EAAE,MAAM,EACjB,OAAO,GAAE,CAAC,GAAG,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,KAAK,MAAM,CAAC,MAAM,CAAC,QAAQ,EAAE,QAAQ,GAAG,MAAM,CAAC,WAAW,EAAE,SAAS,CAC/F,EACtB,MAAM,UAAQ,YAOE,SAAS,OAAO,EAAE,sEA2ClC"}
@@ -1 +1 @@
1
- {"version":3,"file":"batchWrites.js","sourceRoot":"","sources":["../src/batchWrites.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"batchWrites.js","sourceRoot":"","sources":["../src/batchWrites.ts"],"names":[],"mappings":"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"}
@@ -4,11 +4,10 @@ import { SqlClient } from "effect/sql";
4
4
  import type { SqlError } from "effect/sql/SqlError";
5
5
  import type { Fragment } from "effect/sql/Statement";
6
6
  import { type FindManyFilter } from "#repositoryFilter.ts";
7
- export interface BatchedDeleteOptions<TModel extends Model.Any> {
7
+ export interface PurgeLimits {
8
8
  readonly batchSize: number;
9
9
  readonly maxRows: number;
10
10
  readonly pause: Duration.Input;
11
- readonly where: FindManyFilter<TModel>;
12
11
  }
13
12
  declare const BatchedDeleteUnavailable_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
14
13
  readonly _tag: "BatchedDeleteUnavailable";
@@ -17,6 +16,14 @@ export declare class BatchedDeleteUnavailable extends BatchedDeleteUnavailable_b
17
16
  readonly reason: "transaction" | "dialect";
18
17
  }> {
19
18
  }
19
+ export declare const PositiveRowCount: Schema.Number;
20
+ export declare const runPurgeBatches: <E, R>(tableName: string, options: PurgeLimits, batch: (limit: number) => Effect.Effect<{
21
+ readonly count: number;
22
+ readonly done: boolean;
23
+ }, E, R>, prepare?: Effect.Effect<unknown, E, R> | undefined) => Effect.Effect<number, E | BatchedDeleteUnavailable | Schema.SchemaError | SqlError, R | import("effect/Scope").Scope | SqlClient.SqlClient>;
24
+ export interface BatchedDeleteOptions<TModel extends Model.Any> extends PurgeLimits {
25
+ readonly where: FindManyFilter<TModel>;
26
+ }
20
27
  export interface BatchedDelete<TModel extends Model.Any> {
21
28
  readonly deleteBatched: (options: BatchedDeleteOptions<TModel>) => Effect.Effect<number, SqlError | Schema.SchemaError | BatchedDeleteUnavailable, TModel["fields"][keyof TModel["fields"]]["EncodingServices"]>;
22
29
  }
@@ -1 +1 @@
1
- {"version":3,"file":"batchedDelete.d.ts","sourceRoot":"","sources":["../src/batchedDelete.ts"],"names":[],"mappings":"AAAA,OAAO,EAAQ,KAAK,QAAQ,EAAE,MAAM,EAAU,MAAM,EAAE,MAAM,QAAQ,CAAC;AACrE,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AAC3C,OAAO,EAAE,SAAS,EAAa,MAAM,YAAY,CAAC;AAClD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAErD,OAAO,EAAE,KAAK,cAAc,EAAc,MAAM,sBAAsB,CAAC;AAGvE,MAAM,WAAW,oBAAoB,CAAC,MAAM,SAAS,KAAK,CAAC,GAAG;IAC7D,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAC,KAAK,CAAC;IAC/B,QAAQ,CAAC,KAAK,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;CACvC;;;;AAED,qBAAa,wBAAyB,SAAQ,8BAA6C;IAC1F,QAAQ,CAAC,MAAM,EAAE,aAAa,GAAG,SAAS,CAAC;CAC3C,CAAC;CAAG;AAEL,MAAM,WAAW,aAAa,CAAC,MAAM,SAAS,KAAK,CAAC,GAAG;IACtD,QAAQ,CAAC,aAAa,EAAE,CACvB,OAAO,EAAE,oBAAoB,CAAC,MAAM,CAAC,KACjC,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,QAAQ,GAAG,MAAM,CAAC,WAAW,GAAG,wBAAwB,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC,MAAM,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,kBAAkB,CAAC,CAAC,CAAC;CACnJ;AAMD,eAAO,MAAM,iBAAiB;wBAEoB,MAAM;yBAAuB,MAAM;;;;;;8BA+CnE,oBAAoB,CAAC,KAAK,CAAC,GAAG,CAAC,0HAa/C,CAAC"}
1
+ {"version":3,"file":"batchedDelete.d.ts","sourceRoot":"","sources":["../src/batchedDelete.ts"],"names":[],"mappings":"AAAA,OAAO,EAAQ,KAAK,QAAQ,EAAE,MAAM,EAAU,MAAM,EAAE,MAAM,QAAQ,CAAC;AACrE,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AAC3C,OAAO,EAAE,SAAS,EAAa,MAAM,YAAY,CAAC;AAClD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAErD,OAAO,EAAE,KAAK,cAAc,EAAc,MAAM,sBAAsB,CAAC;AAGvE,MAAM,WAAW,WAAW;IAC3B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAC,KAAK,CAAC;CAC/B;;;;AAED,qBAAa,wBAAyB,SAAQ,8BAA6C;IAC1F,QAAQ,CAAC,MAAM,EAAE,aAAa,GAAG,SAAS,CAAC;CAC3C,CAAC;CAAG;AAEL,eAAO,MAAM,gBAAgB,eAAoH,CAAC;AAIlJ,eAAO,MAAM,eAAe,GAAgC,CAAC,EAAE,CAAC,0DAGhD,MAAM,KAAK,MAAM,CAAC,MAAM,CAAC;IAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAA;CAAE,EAAE,CAAC,EAAE,CAAC,CAAC,oMA2BhG,CAAC;AAEH,MAAM,WAAW,oBAAoB,CAAC,MAAM,SAAS,KAAK,CAAC,GAAG,CAAE,SAAQ,WAAW;IAClF,QAAQ,CAAC,KAAK,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;CACvC;AAED,MAAM,WAAW,aAAa,CAAC,MAAM,SAAS,KAAK,CAAC,GAAG;IACtD,QAAQ,CAAC,aAAa,EAAE,CACvB,OAAO,EAAE,oBAAoB,CAAC,MAAM,CAAC,KACjC,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,QAAQ,GAAG,MAAM,CAAC,WAAW,GAAG,wBAAwB,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC,MAAM,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,kBAAkB,CAAC,CAAC,CAAC;CACnJ;AAED,eAAO,MAAM,iBAAiB;wBAEoB,MAAM;yBAAuB,MAAM;;;;;;8BAwBnE,oBAAoB,CAAC,KAAK,CAAC,GAAG,CAAC,0HAc/C,CAAC"}
@@ -4,20 +4,38 @@ import { makeFilter } from "#repositoryFilter.ts";
4
4
  import { tryPurgeLock } from "#tryPurgeLock.ts";
5
5
  export class BatchedDeleteUnavailable extends Data.TaggedError("BatchedDeleteUnavailable") {
6
6
  }
7
- const Positive = Schema.Number.check(Schema.isInt(), Schema.isGreaterThan(0), Schema.isLessThanOrEqualTo(Number.MAX_SAFE_INTEGER));
7
+ export const PositiveRowCount = Schema.Number.check(Schema.isInt(), Schema.isGreaterThan(0), Schema.isLessThanOrEqualTo(Number.MAX_SAFE_INTEGER));
8
8
  const Nonnegative = Schema.Number.check(Schema.isInt(), Schema.isGreaterThanOrEqualTo(0), Schema.isLessThanOrEqualTo(Number.MAX_SAFE_INTEGER));
9
- const Limits = Schema.Struct({ batchSize: Positive, maxRows: Nonnegative });
9
+ const Limits = Schema.Struct({ batchSize: PositiveRowCount, maxRows: Nonnegative });
10
+ export const runPurgeBatches = Effect.fnUntraced(function* (tableName, options, batch, prepare) {
11
+ const sql = yield* SqlClient.SqlClient;
12
+ if (sql.driver !== "pg") {
13
+ return yield* new BatchedDeleteUnavailable({ reason: "dialect" });
14
+ }
15
+ if (Option.isSome(yield* Effect.serviceOption(sql.transactionService))) {
16
+ return yield* new BatchedDeleteUnavailable({ reason: "transaction" });
17
+ }
18
+ const { batchSize, maxRows } = yield* Schema.decodeUnknownEffect(Limits)(options);
19
+ if (prepare !== undefined) {
20
+ yield* prepare;
21
+ }
22
+ if (maxRows === 0 || !(yield* tryPurgeLock(tableName))) {
23
+ return 0;
24
+ }
25
+ let removed = 0;
26
+ while (removed < maxRows) {
27
+ const { count, done } = yield* batch(Math.min(batchSize, maxRows - removed));
28
+ removed += count;
29
+ if (done || count === 0 || removed === maxRows) {
30
+ break;
31
+ }
32
+ yield* Effect.sleep(options.pause);
33
+ }
34
+ return removed;
35
+ });
10
36
  export const makeBatchedDelete = Effect.fnUntraced(function* (model, { tableName, spanPrefix }, changes) {
11
37
  const sql = yield* SqlClient.SqlClient;
12
38
  const { where } = makeFilter(model, sql);
13
- const available = Effect.gen(function* () {
14
- if (sql.driver !== "pg") {
15
- return yield* new BatchedDeleteUnavailable({ reason: "dialect" });
16
- }
17
- if (Option.isSome(yield* Effect.serviceOption(sql.transactionService))) {
18
- return yield* new BatchedDeleteUnavailable({ reason: "transaction" });
19
- }
20
- });
21
39
  function deleteBatch(predicate, limit) {
22
40
  const count = SqlSchema.findOne({
23
41
  execute: (batchLimit) => sql `with removed as (
@@ -34,31 +52,12 @@ export const makeBatchedDelete = Effect.fnUntraced(function* (model, { tableName
34
52
  return result.count;
35
53
  }));
36
54
  }
37
- const runBatches = Effect.fnUntraced(function* (options, predicate) {
38
- const { batchSize, maxRows } = options;
39
- let removed = 0;
40
- while (removed < maxRows) {
41
- const limit = Math.min(batchSize, maxRows - removed);
42
- const count = yield* deleteBatch(predicate, limit);
43
- removed += count;
44
- if (count < limit || removed === maxRows) {
45
- break;
46
- }
47
- yield* Effect.sleep(options.pause);
48
- }
49
- return removed;
50
- });
51
55
  return (options) => Effect.gen(function* () {
52
- yield* available;
53
- const { maxRows } = yield* Schema.decodeUnknownEffect(Limits)(options);
54
- const predicate = yield* where(options.where, true, true);
55
- if (maxRows === 0) {
56
- return 0;
57
- }
58
- if (!(yield* tryPurgeLock(tableName).pipe(Effect.provideService(SqlClient.SqlClient, sql)))) {
59
- return 0;
60
- }
61
- return yield* runBatches(options, predicate);
62
- }).pipe(Effect.scoped, Effect.withSpan(`${spanPrefix}.deleteBatched`));
56
+ const predicate = yield* Effect.cached(where(options.where, true, true));
57
+ return yield* runPurgeBatches(tableName, options, (limit) => Effect.gen(function* () {
58
+ const count = yield* deleteBatch(yield* predicate, limit);
59
+ return { count, done: count < limit };
60
+ }), predicate);
61
+ }).pipe(Effect.provideService(SqlClient.SqlClient, sql), Effect.scoped, Effect.withSpan(`${spanPrefix}.deleteBatched`));
63
62
  });
64
63
  //# sourceMappingURL=batchedDelete.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"batchedDelete.js","sourceRoot":"","sources":["../src/batchedDelete.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"batchedDelete.js","sourceRoot":"","sources":["../src/batchedDelete.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ import { type BucketKey, type ItemKey } from "@shivaedev/changes/keys.ts";
2
+ import { type AnnouncementOptions, invalidateOnCommit } from "#transact.ts";
3
+ export interface ChangeDeclaration {
4
+ readonly covers: Readonly<Record<string, readonly (ItemKey | BucketKey)[]>>;
5
+ readonly record: (options?: AnnouncementOptions) => ReturnType<typeof invalidateOnCommit>;
6
+ }
7
+ export declare function changes(covers: ChangeDeclaration["covers"]): ChangeDeclaration;
8
+ //# sourceMappingURL=changes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"changes.d.ts","sourceRoot":"","sources":["../src/changes.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,SAAS,EAAE,KAAK,OAAO,EAAoB,MAAM,4BAA4B,CAAC;AAC5F,OAAO,EAAE,KAAK,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAE5E,MAAM,WAAW,iBAAiB;IACjC,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,OAAO,GAAG,SAAS,CAAC,EAAE,CAAC,CAAC,CAAC;IAC5E,QAAQ,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,EAAE,mBAAmB,KAAK,UAAU,CAAC,OAAO,kBAAkB,CAAC,CAAC;CAC1F;AAED,wBAAgB,OAAO,CAAC,MAAM,EAAE,iBAAiB,CAAC,QAAQ,CAAC,GAAG,iBAAiB,CAG9E"}
@@ -0,0 +1,7 @@
1
+ import { invalidationKeys } from "@shivaedev/changes/keys.ts";
2
+ import { invalidateOnCommit } from "#transact.ts";
3
+ export function changes(covers) {
4
+ const keys = invalidationKeys(Object.values(covers).flat());
5
+ return { covers, record: (options) => invalidateOnCommit(keys, options) };
6
+ }
7
+ //# sourceMappingURL=changes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"changes.js","sourceRoot":"","sources":["../src/changes.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgC,gBAAgB,EAAE,MAAM,4BAA4B,CAAC;AAC5F,OAAO,EAA4B,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAO5E,MAAM,UAAU,OAAO,CAAC,MAAmC;IAC1D,MAAM,IAAI,GAAG,gBAAgB,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;IAC5D,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,kBAAkB,CAAC,IAAI,EAAE,OAAO,CAAC,EAAE,CAAC;AAC3E,CAAC"}