@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.
- package/CHANGELOG.md +34 -0
- package/README.md +153 -23
- package/dist/MigrationStartedAt.d.ts.map +1 -0
- package/dist/{internal/MigrationStartedAt.js → MigrationStartedAt.js} +1 -1
- package/dist/MigrationStartedAt.js.map +1 -0
- package/dist/batchWrites.d.ts +1 -1
- package/dist/batchWrites.d.ts.map +1 -1
- package/dist/batchWrites.js.map +1 -1
- package/dist/batchedDelete.d.ts +9 -2
- package/dist/batchedDelete.d.ts.map +1 -1
- package/dist/batchedDelete.js +34 -35
- package/dist/batchedDelete.js.map +1 -1
- package/dist/changes.d.ts +8 -0
- package/dist/changes.d.ts.map +1 -0
- package/dist/changes.js +7 -0
- package/dist/changes.js.map +1 -0
- package/dist/conditionalUpsert.d.ts +4 -3
- package/dist/conditionalUpsert.d.ts.map +1 -1
- package/dist/conditionalUpsert.js +1 -1
- package/dist/conditionalUpsert.js.map +1 -1
- package/dist/conflictWrites.d.ts +6 -5
- package/dist/conflictWrites.d.ts.map +1 -1
- package/dist/conflictWrites.js +1 -1
- package/dist/conflictWrites.js.map +1 -1
- package/dist/deleteGroupsBatched.d.ts +13 -0
- package/dist/deleteGroupsBatched.d.ts.map +1 -0
- package/dist/deleteGroupsBatched.js +23 -0
- package/dist/deleteGroupsBatched.js.map +1 -0
- package/dist/inValues.d.ts +5 -0
- package/dist/inValues.d.ts.map +1 -0
- package/dist/inValues.js +10 -0
- package/dist/inValues.js.map +1 -0
- package/dist/insertFromSelect.d.ts +1 -1
- package/dist/insertFromSelect.d.ts.map +1 -1
- package/dist/insertFromSelect.js.map +1 -1
- package/dist/int4Array.d.ts +5 -0
- package/dist/int4Array.d.ts.map +1 -0
- package/dist/int4Array.js +10 -0
- package/dist/int4Array.js.map +1 -0
- package/dist/int8.d.ts +5 -0
- package/dist/int8.d.ts.map +1 -0
- package/dist/int8.js +9 -0
- package/dist/int8.js.map +1 -0
- package/dist/invalidationKeys.d.ts +8 -0
- package/dist/invalidationKeys.d.ts.map +1 -0
- package/dist/invalidationKeys.js +15 -0
- package/dist/invalidationKeys.js.map +1 -0
- package/dist/keysetPage.d.ts +26 -0
- package/dist/keysetPage.d.ts.map +1 -0
- package/dist/keysetPage.js +23 -0
- package/dist/keysetPage.js.map +1 -0
- package/dist/listenForChanges.js +2 -2
- package/dist/listenForChanges.js.map +1 -1
- package/dist/makeConflictClause.d.ts +2 -1
- package/dist/makeConflictClause.d.ts.map +1 -1
- package/dist/makeConflictClause.js +36 -6
- package/dist/makeConflictClause.js.map +1 -1
- package/dist/migrationLock.d.ts.map +1 -0
- package/dist/migrationLock.js.map +1 -0
- package/dist/migrations.d.ts +1 -1
- package/dist/migrations.d.ts.map +1 -1
- package/dist/migrations.js +18 -9
- package/dist/migrations.js.map +1 -1
- package/dist/postgresChanges.d.ts.map +1 -1
- package/dist/postgresChanges.js +8 -4
- package/dist/postgresChanges.js.map +1 -1
- package/dist/postgresLayer.d.ts +2 -1
- package/dist/postgresLayer.d.ts.map +1 -1
- package/dist/postgresLayer.js +1 -1
- package/dist/postgresLayer.js.map +1 -1
- package/dist/postgresRelease.js +1 -1
- package/dist/postgresRelease.js.map +1 -1
- package/dist/repositoryChanges.d.ts.map +1 -1
- package/dist/repositoryChanges.js +4 -3
- package/dist/repositoryChanges.js.map +1 -1
- package/dist/transact.d.ts +13 -5
- package/dist/transact.d.ts.map +1 -1
- package/dist/transact.js +45 -19
- package/dist/transact.js.map +1 -1
- package/dist/upsertColumns.d.ts +16 -0
- package/dist/upsertColumns.d.ts.map +1 -0
- package/dist/upsertColumns.js +2 -0
- package/dist/upsertColumns.js.map +1 -0
- package/dist/versionedStatements.d.ts +1 -1
- package/package.json +7 -6
- package/src/{internal/MigrationStartedAt.ts → MigrationStartedAt.ts} +1 -1
- package/src/batchWrites.ts +2 -1
- package/src/batchedDelete.ts +53 -40
- package/src/changes.ts +12 -0
- package/src/conditionalUpsert.ts +4 -3
- package/src/conflictWrites.ts +11 -7
- package/src/deleteGroupsBatched.ts +41 -0
- package/src/inValues.ts +11 -0
- package/src/insertFromSelect.ts +1 -1
- package/src/int4Array.ts +14 -0
- package/src/int8.ts +14 -0
- package/src/invalidationKeys.ts +24 -0
- package/src/keysetPage.ts +54 -0
- package/src/listenForChanges.ts +2 -2
- package/src/makeConflictClause.ts +53 -8
- package/src/migrations.ts +21 -10
- package/src/postgresChanges.ts +13 -4
- package/src/postgresLayer.ts +1 -1
- package/src/postgresRelease.ts +1 -1
- package/src/repositoryChanges.ts +4 -3
- package/src/transact.ts +61 -24
- package/src/upsertColumns.ts +16 -0
- package/dist/internal/CommandRecovery.d.ts +0 -3
- package/dist/internal/CommandRecovery.d.ts.map +0 -1
- package/dist/internal/CommandRecovery.js +0 -5
- package/dist/internal/CommandRecovery.js.map +0 -1
- package/dist/internal/MigrationStartedAt.d.ts.map +0 -1
- package/dist/internal/MigrationStartedAt.js.map +0 -1
- package/dist/internal/migrationLock.d.ts.map +0 -1
- package/dist/internal/migrationLock.js.map +0 -1
- package/src/internal/CommandRecovery.ts +0 -5
- /package/dist/{internal/MigrationStartedAt.d.ts → MigrationStartedAt.d.ts} +0 -0
- /package/dist/{internal/migrationLock.d.ts → migrationLock.d.ts} +0 -0
- /package/dist/{internal/migrationLock.js → migrationLock.js} +0 -0
- /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
|
|
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
|
|
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
|
|
136
|
-
import {
|
|
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 =
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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*
|
|
896
|
+
yield* changed.record();
|
|
767
897
|
}),
|
|
768
|
-
|
|
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/
|
|
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"}
|
package/dist/batchWrites.d.ts
CHANGED
|
@@ -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,
|
|
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"}
|
package/dist/batchWrites.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"batchWrites.js","sourceRoot":"","sources":["../src/batchWrites.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAMxC,OAAO,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAUvE,MAAM,UAAU,cAAc,CAC7B,KAAgB,EAChB,GAAwB,EACxB,OAAyG,EACzG,SAAiB,EACjB,OAAO,
|
|
1
|
+
{"version":3,"file":"batchWrites.js","sourceRoot":"","sources":["../src/batchWrites.ts"],"names":[],"mappings":"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"}
|
package/dist/batchedDelete.d.ts
CHANGED
|
@@ -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
|
|
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,
|
|
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"}
|
package/dist/batchedDelete.js
CHANGED
|
@@ -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
|
|
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:
|
|
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*
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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":"AAAA,OAAO,EAAE,IAAI,EAAiB,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAErE,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAIlD,OAAO,EAAuB,UAAU,EAAE,MAAM,sBAAsB,CAAC;AACvE,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;
|
|
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"}
|
package/dist/changes.js
ADDED
|
@@ -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"}
|