@voltro/sql-postgres 0.19.0 → 0.20.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 CHANGED
@@ -39,6 +39,93 @@ _Changes staged for the next release accumulate here (rolled up from
39
39
 
40
40
  ---
41
41
 
42
+ ## [0.20.0] — 2026-07-29
43
+
44
+ ### ⚠ BREAKING
45
+
46
+ - **@voltro/plugin-versioning, @voltro/database, @voltro/voltro, @voltro/sql-postgres, @voltro/sql-mysql, @voltro/sql-sqlite, @voltro/sql-mssql** — `versioningPlugin({ timing: 'in-transaction' })` produced a WRONG trail, not merely a slow one. Reported and reproduced against MariaDB 11 by a team that wired both plugins and measured before migrating a single call site.
47
+
48
+ **It recorded every change twice.** The two timings are alternatives, but the post-commit change tap stayed wired when the in-transaction recorder was registered, so both ran. One `bookmarks.create` → two history rows.
49
+
50
+ **And the trail was mis-ordered, which is worse.** Each path numbered independently: one insert plus one update produced versions `0, 0, 1, 2` across four rows. `selectAsOf`, `sortHistory` and `diffVersionRows` all read `version`, so `rowAsOf` returned the wrong snapshot and `diffVersions` found nothing. A duplicate can be deduped; a wrong order cannot be detected from the data.
51
+
52
+ The recorder wrote a constant `version: 0` on purpose, with a design note arguing that ordering could come from `changedAt` and that a read per covered write was too expensive. Both halves were wrong: `changedAt` is millisecond-resolution, so two writes to one row inside one transaction tie routinely, and the number is what every reader consults.
53
+
54
+ **BREAKING —** a `WriteRecorder` now receives a PORT (`{ append, maxOf }`) rather than a bare `append`. `maxOf` is one aggregate with an equality filter on the connection the write already holds; it is what lets an append-only trail number its own entries. A recorder still cannot UPDATE, DELETE or open a nested transaction, and a throw from either operation still rolls the caller's write back. Apps that merely ENABLE the timing need no change — only a hand-written recorder does, and `tsc` names every site.
55
+
56
+ **Cost, stated rather than avoided:** `'in-transaction'` now takes TWO round-trips per recorded write, roughly doubling this timing's published per-write overhead. Both timings number from 1, so switching `timing` no longer shifts version numbers.
57
+
58
+ ### Fixed
59
+
60
+ - **@voltro/database, @voltro/sql-postgres, @voltro/sql-mysql, @voltro/sql-sqlite, @voltro/sql-mssql** — The correlation bridge did not survive a transaction, and did not survive CDC. Both are fixed, and both were found by measuring against live databases after a consumer isolated the symptom in a scratch app.
61
+
62
+ **Every write a framework mutation makes was unattributed.** `transactional()` is entered from the request's async-local scope, but its callback runs from inside the Effect the store builds — and measured against live postgres AND live mariadb, the scope is active at the call site and EMPTY inside the callback. Framework mutations are auto-transactional, so this was every handler write. Same class as the `bindMutation` defect fixed alongside it: a scope covering the construction of an Effect and not its execution. The caller's attribution is now captured at `transactional()` entry and re-established around the callback, in all four dialect stores.
63
+
64
+ **And the CDC transports could not carry it at all.** Under `changeStrategy: 'cdc'` — the DEFAULT — the event a subscriber receives is rebuilt from a postgres NOTIFY payload or a mysql binlog row image, neither of which can hold a request context. `registerPendingAttribution` / `claimPendingAttribution` (`@voltro/database`) let the write path hand its identity to the echo, keyed by `(table, op, id)` and claimed once. A write made on ANOTHER replica has nothing pending and stays unattributed, which is the correct answer rather than a gap.
65
+
66
+ **Plus one nobody had reported, found on the way:** on postgres under CDC the write path skipped `routeEvent` entirely, and `runWriteRecorders` lives inside it — so `versioningPlugin({ timing: 'in-transaction' })` with the default `CDC=1` recorded NOTHING. The mode whose entire promise is "if the change committed, the entry is there" wrote an empty trail, silently. `routeEvent` now runs in both modes; only the DELIVERY decision is strategy-dependent.
67
+
68
+ New live-dialect suites (`cdcAttribution.integration.test.ts` in `sql-postgres` and `sql-mysql`) pin all of it, and were verified red against the previous code.
69
+ - **@voltro/plugin-versioning, @voltro/runtime, @voltro/cli** — `_voltro_row_history.traceId` and `.subjectId` were NULL on every write. Three independent causes, all found from one consumer report whose evidence pinned the diagnosis before we looked: `subjectId` was NULL while `changedBy` on the SAME row carried the acting user — so the identity was known and was not travelling.
70
+
71
+ - **The adapter dropped them.** `dataStoreHistoryStore.append` hand-wrote its insert object and listed `changedBy` but not `traceId` / `subjectId`. This is the second time that shape has bitten in this file — the READ side (`rowToVersion`) had drifted identically. A row built by hand in one place and read by hand in another disagree exactly when a field is ADDED, because nothing fails. Both now spread the row. - **An Effect-returning handler was unattributed.** `bindMutation` established the scope around the CALL, which covers an async executor for its whole run — but an Effect-returning one is only CONSTRUCTED there and runs later. It is now forked inside the scope, with interruption and typed failures preserved (both pinned by tests). Verified by measurement, not assumption: an Effect forked inside an ALS scope keeps seeing it across `sleep`, `yieldNow` and a `setTimeout` promise, while the same effect merely constructed inside sees nothing. - **The devtools `/invoke` path never entered the scope at all.** It bypasses the rpc stack by design, and that also bypassed everything `bindMutation` sets up. The audit plugin recorded a traceId (it reads `requestContext.traceId`, which this path does build) while every write underneath carried none — three consumers of one call disagreeing about its trace. It also synthesised `inspect-<8 random chars>`, which no trace consumer can parse; the reporter's framing is the rule worth keeping — *a synthesised id produces a column that looks joinable and is not; NULL at least fails honestly.* It is a real 32-hex id now, and it reaches all three sinks.
72
+
73
+ `actingUserId` is imported at the new call site rather than re-derived — one answer to "who is writing", shared with what `audit()` stamps.
74
+ - **@voltro/cli** — Three ways a check reported nothing while checking nothing, all found by a consumer verifying the silence instead of trusting it.
75
+
76
+ - **`unexercised-row-filter` never fired on a typed registration.** The match was `/\bsetRowFilter\s*\(/`, which demands the paren directly after the name, so `setRowFilter<Ctx>({…})` — the spelling our own generic signature invites — broke it. The rule was blind for exactly the teams that had wired `load`/`predicate` carefully. It counts CALLS now. - **…and its test-side condition was satisfiable by a COMMENT.** It matched `rowFilter:` in raw text. Comments and string literals are stripped, and the suite must both call `makeTestContext` and bind `rowFilter` in real code. - **The same paren-adjacent shape sat in two shipped codemod gates.** `0.7.0/01` (row filter) and `0.7.0/02` (`invoke`) both gate on a generic export, so a typed call made `voltro update` print nothing at all: the upgrade reads as clean and the behaviour change lands unread. Both now use the shared `callPattern`, which allows type arguments including nested ones.
77
+
78
+ Two false-positive fixes in the hand-roll detector, from the same report:
79
+
80
+ - **A file that WIRES a plugin is no longer told to adopt it.** The `presence` rule reported `app.config.ts` (which calls `presencePlugin()`) to an app that had just deleted its hand-rolled table. Rules that recommend a package now declare it, and a file referencing that package is skipped. - **Generated files and `.d.ts` are out of the scan.** A recommendation aimed at a file the next boot overwrites is never actionable.
81
+
82
+ And one more of the first kind, found while checking why a withdrawn report's probe had stayed silent: `raw-fetch` counted only a BARE `fetch(…)` callee, so `globalThis.fetch(url)` / `self.fetch(url)` in a server file read as clean.
83
+ - **@voltro/cli** — `voltro doctor`'s `serverOnly: NOT CHECKED` line now names the failure, and the field exists in `--json`.
84
+
85
+ The refusal to claim a pass was right. What shipped with it was nothing to act on: the `catch` discarded the error entirely, so there was no reason, no failing module, and — because the field was absent from `--json` — no way for CI to assert "still unchecked" rather than reading silence as a pass.
86
+
87
+ A consumer's verdict, which is the useful part: *"The message is honest and that is the problem."* They had already verified that every descriptor, `app.config.ts` and the generated rpc group imported cleanly under `tsx` on their own, so the difference had to be in what `loadDiscovered` does BEYOND importing — and none of that was visible from outside. It matters more than its size because `.serverOnly()` is what guards their `sessions.tokenHash` and `apiKeys.keyHash`, markers they added after finding a query whose output schema shipped a hash over the wire.
88
+
89
+ `--json` now carries `serverOnly: { checked, reason?, leaks? }`. Gate CI on `checked === false`.
90
+ - **@voltro/plugin-versioning** — A version snapshot no longer copies `.serverOnly()` columns into `_voltro_row_history`. `.encrypted()` columns are KEPT, and that distinction is the whole finding.
91
+
92
+ Reported by a team choosing which tables to version: `sessions` holds `.encrypted()` PATs and a `tokenHash`, `apiKeys` holds a `keyHash`, and they could not determine from outside what the snapshot would contain. They excluded both tables — then went and measured it, which corrected their own report:
93
+
94
+ ```
95
+ probeItems.secret enc:v1:a56iziEV9THLhzmJ:Vk0ux+0bECleTLBJkCa0Rg==:3AtMwP…
96
+ _voltro_row_history {"secret":"enc:v1:a56iziEV9THLhzmJ:Vk0ux+0bECleTLBJkCa0Rg==:…"}
97
+ ```
98
+
99
+ **`.encrypted()` lands as ciphertext, byte-identical to the source column**, so versioning such a table widens nothing — the history is exactly as readable as the row it came from. Withholding it would have cost real audit data to prevent an exposure that does not exist.
100
+
101
+ **`.serverOnly()` is withheld**, and the reason is not "a second copy under different retention" — that argument is weak on its own, since the hash already sits in the source table. The decisive one: `crud.*` STRIPS `.serverOnly()` columns from every row it returns, and a snapshot would smuggle the same value back past that stripping inside a `json()` blob, where no column-level rule applies.
102
+
103
+ Withheld names are listed under `data._omitted`, so a reader can tell "this column was withheld" from "this column did not exist then". Both timings apply the same policy. `.sensitive()` is not involved: it is an export-masking marker for values that are legitimately readable in the app.
104
+ - **@voltro/cli** — A page that RE-EXPORTS its component (`export { default, renderMode } from '../page'`) no longer fails the codegen gate with "exports no default". The check required the literal words `as default`, so the one spelling that lets two routes share a screen without copying it was the one spelling it refused — and it refused in `voltro build`, while dev and tests stayed green because nothing prerenders there. `export { default as Screen }` is still correctly rejected: it renames the default away.
105
+
106
+ Two follow-ons from the same shape:
107
+
108
+ - The refusal message said the file "ends in `.page.tsx`" and offered "drop the `.page` suffix" as a fix. That is the 0.15.0 convention, replaced by directory routing in 0.17.0 — it named a convention that no longer exists and a fix that could not work. It now names `page.tsx` and both real fixes. - `scanRenderProfile` read a forwarded `renderMode` as absent and fell back to `'static'`, so `staticSafe` and the deploy-target classification could call an app CDN-deployable with an `ssr` route in it. The forward is now followed (relative specifiers, depth-capped); an unresolvable one still falls back rather than failing the scan.
109
+ - **@voltro/database** — `VOLTRO_SOFT_DROP=1` could never converge. The applier renames the object to `<name>__dropped_<ts>` instead of dropping it, which leaves it undeclared — and the differ read that as one more forgotten table, planning the drop again. The re-plan inside `applyPlan` then found an operation still outstanding and aborted with "the DDL for these operations is a no-op — this is a framework bug", which was a wrong diagnosis of a real defect: the DDL had worked. No fingerprint was recorded, so the migration counted as unapplied and every later `db apply` / boot hit the same wall. The only exit was a hard drop of the snapshot — exactly the recoverability the flag is chosen for.
110
+
111
+ The planner now treats `<name>__dropped_<YYYYMMDDHHMMSS>` as framework-managed, alongside `_voltro_*` / `cluster_*`. Deliberately not retention-aware: a planner whose output depends on the clock would produce different plans before and after midnight, and `db gc-snapshots` already owns expiry.
112
+
113
+ Reported against tables; the same defect existed one level down for soft-dropped COLUMNS, where it was worse — a re-planned `drop-column` carries no `dropped()` marker and so refuses to plan at all. Both are fixed.
114
+
115
+ The convergence message itself no longer asserts a cause it cannot know. It said "the DDL for these operations is a no-op", which was flatly wrong here and sent the reporter looking for dead DDL. It now names both causes — no-op DDL, and a planner that cannot see what the DDL did — and says which one an operation naming a just-renamed object usually is.
116
+
117
+ ### Internal (no consumer-facing effect)
118
+
119
+ - **The `0.20.0/01_write-recorder-port` codemod gains the gate test its two predecessors have.**
120
+
121
+ `codemodRegistry.test.ts` asserts that every `*.codemod.ts` on disk is registered and that ids are unique — registration, not behaviour. What it cannot see is the one way a `manual` codemod fails in practice: an `appliesTo` that is too broad, so the note prints for projects that have nothing to do. That is not a cosmetic problem. A note which fires on every app is how readers learn to skip notes, and the next one carries a boot refusal.
122
+
123
+ This codemod is the case where the silent direction matters most. The break is a TYPE error, so `tsc` already names every affected site; the note exists only to explain `maxOf`, which the compiler cannot. Apps that merely ENABLE `timing: 'in-transaction'` need to do nothing — `plugin-versioning` ships the recorder and it is already updated — and they are the large majority.
124
+
125
+ Four cases, covering both directions: a project registering its own recorder (note prints, and names `{ append }`, `maxOf`, and the `null`-is-not-zero distinction that a hand-written sequence gets wrong), an app that only enables the timing (silent), the identifier in a comment or a string (silent), and the generic call form `registerWriteRecorder<Row>(…)`, which `callPattern` admits and a naive match would miss.
126
+
127
+ ---
128
+
42
129
  ## [0.19.0] — 2026-07-29
43
130
 
44
131
  ### ⚠ BREAKING
package/dist/index.d.ts CHANGED
@@ -171,6 +171,12 @@ export declare class PostgresDataStore implements DataStore {
171
171
  * re-enter `routeEvent` and emit a change event for the trail's own table.
172
172
  */
173
173
  private appendInTxn;
174
+ /**
175
+ * The other half of the recorder port: ONE aggregate on the caller's
176
+ * connection, so an append-only trail can number its own entries. Equality
177
+ * filter only — see `writeRecorder.ts` for why it is this narrow.
178
+ */
179
+ private maxInTxn;
174
180
  private routeEvent;
175
181
  query(descriptor: QueryDescriptor): Promise<ReadonlyArray<Row>>;
176
182
  raw<T extends object = Row>(fragment: RawSqlFragment, _opts?: {
package/dist/index.js CHANGED
@@ -4,9 +4,9 @@ import p from "pg";
4
4
  import { EventEmitter as m } from "node:events";
5
5
  import { createLogger as h } from "@voltro/logger";
6
6
  import { SqlClient as g, TransactionConnection as _ } from "@effect/sql/SqlClient";
7
- import { EagerCardinalityError as v, attachEagerLoads as y, attributionFields as b, compileEagerJson as x, compilePredicate as S, compileRawFragment as C, compileSelect as w, encodeRowForSchema as T, hasEagerLoads as E, isTableReactive as D, raiseChangeListenerCeiling as O, recordsTable as k, requireTable as A, runWriteRecorders as j, stampGeneratedId as M, stampGeneratedIds as N } from "@voltro/database";
7
+ import { EagerCardinalityError as v, attachEagerLoads as y, attributionFields as b, attributionKey as x, claimPendingAttribution as S, compileEagerJson as C, compilePredicate as w, compileRawFragment as T, compileSelect as E, currentWriteAttribution as D, encodeRowForSchema as O, hasEagerLoads as k, isTableReactive as A, raiseChangeListenerCeiling as j, recordsTable as M, registerPendingAttribution as N, requireTable as P, runWithWriteAttribution as ee, runWriteRecorders as te, stampGeneratedId as F, stampGeneratedIds as I } from "@voltro/database";
8
8
  //#region src/sqlLayer.ts
9
- var P = (e) => e ? { rejectUnauthorized: !1 } : !1, F = /^[A-Za-z_][A-Za-z0-9_]*$/, I = (t) => {
9
+ var L = (e) => e ? { rejectUnauthorized: !1 } : !1, R = /^[A-Za-z_][A-Za-z0-9_]*$/, z = (t) => {
10
10
  if (t.schema === void 0) return e.layerConfig({
11
11
  host: r.succeed(t.host),
12
12
  port: r.succeed(t.port),
@@ -14,9 +14,9 @@ var P = (e) => e ? { rejectUnauthorized: !1 } : !1, F = /^[A-Za-z_][A-Za-z0-9_]*
14
14
  password: r.succeed(u.make(t.password)),
15
15
  database: r.succeed(t.database),
16
16
  ...t.maxConnections === void 0 ? {} : { maxConnections: r.succeed(t.maxConnections) },
17
- ...t.ssl === void 0 ? {} : { ssl: r.succeed(P(t.ssl)) }
17
+ ...t.ssl === void 0 ? {} : { ssl: r.succeed(L(t.ssl)) }
18
18
  });
19
- if (!F.test(t.schema)) throw Error(`DB_SCHEMA '${t.schema}' is not a valid postgres identifier (expected ${F}).`);
19
+ if (!R.test(t.schema)) throw Error(`DB_SCHEMA '${t.schema}' is not a valid postgres identifier (expected ${R}).`);
20
20
  let n = t.schema, a = i.acquireRelease(i.sync(() => new p.Pool({
21
21
  host: t.host,
22
22
  port: t.port,
@@ -24,11 +24,11 @@ var P = (e) => e ? { rejectUnauthorized: !1 } : !1, F = /^[A-Za-z_][A-Za-z0-9_]*
24
24
  password: t.password,
25
25
  database: t.database,
26
26
  ...t.maxConnections === void 0 ? {} : { max: t.maxConnections },
27
- ...t.ssl === void 0 ? {} : { ssl: P(t.ssl) },
27
+ ...t.ssl === void 0 ? {} : { ssl: L(t.ssl) },
28
28
  options: `-c search_path="${n}"`
29
29
  })), (e) => i.promise(() => e.end()));
30
30
  return e.layerFromPool({ acquire: a });
31
- }, L = (e) => {
31
+ }, B = (e) => {
32
32
  let t = e.get("sslmode");
33
33
  if (t !== null) {
34
34
  if (t === "require") return !0;
@@ -41,10 +41,10 @@ var P = (e) => e ? { rejectUnauthorized: !1 } : !1, F = /^[A-Za-z_][A-Za-z0-9_]*
41
41
  if (n === "false" || n === "0") return !1;
42
42
  throw Error(`DB_URL '?ssl=${n}' is not supported by the postgres dialect — use 'true'/'1' or 'false'/'0'.`);
43
43
  }
44
- }, R = (e) => {
44
+ }, V = (e) => {
45
45
  let t = e.schema === void 0 ? {} : { schema: e.schema };
46
46
  if (e.url) {
47
- let n = new URL(e.url), r = e.ssl ?? L(n.searchParams);
47
+ let n = new URL(e.url), r = e.ssl ?? B(n.searchParams);
48
48
  return {
49
49
  host: n.hostname || "localhost",
50
50
  port: n.port ? Number(n.port) : 5432,
@@ -66,20 +66,23 @@ var P = (e) => e ? { rejectUnauthorized: !1 } : !1, F = /^[A-Za-z_][A-Za-z0-9_]*
66
66
  ...e.ssl === void 0 ? {} : { ssl: e.ssl },
67
67
  ...t
68
68
  };
69
- }, z = (e) => I(R(e)), B = /* @__PURE__ */ new Set(["40001", "40P01"]), V = (e) => {
69
+ }, H = (e) => z(V(e)), U = /* @__PURE__ */ new Set(["40001", "40P01"]), W = (e) => {
70
70
  let t = e;
71
71
  for (let e = 0; e < 5 && typeof t == "object" && t; e++) {
72
72
  let e = t.code;
73
73
  if (typeof e == "string") return e;
74
74
  t = t.cause;
75
75
  }
76
- }, H = (e) => {
77
- let t = V(e);
78
- return t !== void 0 && B.has(t);
79
- }, U = (e) => H(e) ? "retry" : "noRetry", W = ["json"], G = h({ scope: "voltro:postgres" }), K = async (e) => {
80
- let t = e.tracerLayer ? s.mergeAll(e.sqlLayer, e.tracerLayer) : e.sqlLayer, n = c.make(t), r = await n.runPromise(g), i = e.changeStrategy ?? "inline", a = new q(r, n, i, e.cdcChannel ?? "framework_changes");
76
+ }, G = (e) => {
77
+ let t = W(e);
78
+ return t !== void 0 && U.has(t);
79
+ }, K = (e) => G(e) ? "retry" : "noRetry", q = ["json"], J = h({ scope: "voltro:postgres" }), Y = async (e) => {
80
+ let t = e.tracerLayer ? s.mergeAll(e.sqlLayer, e.tracerLayer) : e.sqlLayer, n = c.make(t), r = await n.runPromise(g), i = e.changeStrategy ?? "inline", a = new Z(r, n, i, e.cdcChannel ?? "framework_changes");
81
81
  return i === "cdc" && await a.startCdcConsumer(), a;
82
- }, q = class {
82
+ }, X = () => {
83
+ let e = D();
84
+ return e === void 0 ? (e) => e() : (t) => ee(e, t);
85
+ }, Z = class {
83
86
  sql;
84
87
  runtime;
85
88
  changeStrategy;
@@ -88,20 +91,20 @@ var P = (e) => e ? { rejectUnauthorized: !1 } : !1, F = /^[A-Za-z_][A-Za-z0-9_]*
88
91
  cdcFiber = null;
89
92
  inflightTxns = 0;
90
93
  constructor(e, t, n, r) {
91
- this.sql = e, this.runtime = t, this.changeStrategy = n, this.cdcChannel = r, O(this.emitter);
94
+ this.sql = e, this.runtime = t, this.changeStrategy = n, this.cdcChannel = r, j(this.emitter);
92
95
  }
93
96
  withNamespace(e) {
94
- return e === null ? this : new Y(this, e);
97
+ return e === null ? this : new ne(this, e);
95
98
  }
96
99
  async runInNamespace(e, t) {
97
100
  this.inflightTxns++;
98
- let n = this.sql, r = this.sql.withTransaction(i.flatMap(i.serviceOption(_), (r) => {
99
- if (l.isNone(r)) return i.fail(/* @__PURE__ */ Error("PostgresDataStore.runInNamespace: TransactionConnection missing."));
100
- let a = r.value, o = i.provideService(n`SET LOCAL search_path TO ${n(e)}`, _, a), s = new J(this, a);
101
- return i.flatMap(o, () => i.tryPromise({
102
- try: () => t(s).then((e) => ({
101
+ let n = X(), r = this.sql, a = this.sql.withTransaction(i.flatMap(i.serviceOption(_), (a) => {
102
+ if (l.isNone(a)) return i.fail(/* @__PURE__ */ Error("PostgresDataStore.runInNamespace: TransactionConnection missing."));
103
+ let o = a.value, s = i.provideService(r`SET LOCAL search_path TO ${r(e)}`, _, o), c = new Q(this, o);
104
+ return i.flatMap(s, () => i.tryPromise({
105
+ try: () => n(() => t(c)).then((e) => ({
103
106
  result: e,
104
- view: s
107
+ view: c
105
108
  })),
106
109
  catch: (e) => e
107
110
  }));
@@ -110,7 +113,7 @@ var P = (e) => e ? { rejectUnauthorized: !1 } : !1, F = /^[A-Za-z_][A-Za-z0-9_]*
110
113
  "db.operation": "namespace.transaction"
111
114
  } }));
112
115
  try {
113
- let e = await this.runtime.runPromise(r);
116
+ let e = await this.runtime.runPromise(a);
114
117
  return e.view.commitEvents(), e.result;
115
118
  } finally {
116
119
  this.inflightTxns--;
@@ -118,14 +121,14 @@ var P = (e) => e ? { rejectUnauthorized: !1 } : !1, F = /^[A-Za-z_][A-Za-z0-9_]*
118
121
  }
119
122
  __postgresReplicationFriend = { runEffect: (e) => this.runtime.runPromise(e) };
120
123
  async executeQuery(e, t) {
121
- let n = w(e, this.sql), r = t ? i.provideService(n, _, t) : n;
124
+ let n = E(e, this.sql), r = t ? i.provideService(n, _, t) : n;
122
125
  return this.runtime.runPromise(r);
123
126
  }
124
127
  async executeInsert(e, t, n, r) {
125
- t = M(e, t);
126
- let a = this.sql, o = a`INSERT INTO ${a(e)} ${a.insert(T(t, e, W))} RETURNING *`, s = n ? i.provideService(o, _, n) : o, c = (await this.runtime.runPromise(s))[0];
128
+ t = F(e, t);
129
+ let a = this.sql, o = a`INSERT INTO ${a(e)} ${a.insert(O(t, e, q))} RETURNING *`, s = n ? i.provideService(o, _, n) : o, c = (await this.runtime.runPromise(s))[0];
127
130
  if (!c) throw Error(`PostgresDataStore.insert: no row returned for table '${e}'`);
128
- return this.changeStrategy === "inline" && await this.routeEvent({
131
+ return await this.routeEvent({
129
132
  table: e,
130
133
  op: "insert",
131
134
  old: null,
@@ -133,8 +136,8 @@ var P = (e) => e ? { rejectUnauthorized: !1 } : !1, F = /^[A-Za-z_][A-Za-z0-9_]*
133
136
  }, r, n), c;
134
137
  }
135
138
  async executeUpdate(e, t, n, r, a) {
136
- let o = this.sql, s = o`UPDATE ${o(e)} SET ${o.update(T(n, e, W))} WHERE ${o("id")} = ${t} RETURNING *`, c = r ? i.provideService(s, _, r) : s, l = (await this.runtime.runPromise(c))[0];
137
- return l ? (this.changeStrategy === "inline" && await this.routeEvent({
139
+ let o = this.sql, s = o`UPDATE ${o(e)} SET ${o.update(O(n, e, q))} WHERE ${o("id")} = ${t} RETURNING *`, c = r ? i.provideService(s, _, r) : s, l = (await this.runtime.runPromise(c))[0];
140
+ return l ? (await this.routeEvent({
138
141
  table: e,
139
142
  op: "update",
140
143
  old: null,
@@ -151,15 +154,15 @@ var P = (e) => e ? { rejectUnauthorized: !1 } : !1, F = /^[A-Za-z_][A-Za-z0-9_]*
151
154
  if (r) return u(r);
152
155
  this.inflightTxns++;
153
156
  try {
154
- let e = i.suspend(() => this.sql.withTransaction(i.flatMap(i.serviceOption(_), (e) => l.isNone(e) ? i.fail(/* @__PURE__ */ Error("PostgresDataStore.upsert: TransactionConnection missing.")) : i.promise(() => u(e.value))))), t = d.exponential("10 millis").pipe(d.compose(d.recurs(3)), d.whileInput(H));
157
+ let e = i.suspend(() => this.sql.withTransaction(i.flatMap(i.serviceOption(_), (e) => l.isNone(e) ? i.fail(/* @__PURE__ */ Error("PostgresDataStore.upsert: TransactionConnection missing.")) : i.promise(() => u(e.value))))), t = d.exponential("10 millis").pipe(d.compose(d.recurs(3)), d.whileInput(G));
155
158
  return await this.runtime.runPromise(e.pipe(i.retry(t)));
156
159
  } finally {
157
160
  this.inflightTxns--;
158
161
  }
159
162
  }
160
- let s = n.conflictColumns.map((e) => o`${o(e)}`), c = Object.keys(t).filter((e) => t[e] !== void 0), u = n.update === void 0 ? c.filter((e) => e !== "id" && !n.conflictColumns.includes(e)) : n.update, f = u.length > 0 ? o.csv(u.map((e) => o`${o(e)} = EXCLUDED.${o(e)}`)) : o`${o(n.conflictColumns[0])} = EXCLUDED.${o(n.conflictColumns[0])}`, p = o`INSERT INTO ${o(e)} ${o.insert(T(t, e, W))} ON CONFLICT (${o.csv(s)}) DO UPDATE SET ${f} RETURNING *`, m = r ? i.provideService(p, _, r) : p, h = (await this.runtime.runPromise(m))[0];
163
+ let s = n.conflictColumns.map((e) => o`${o(e)}`), c = Object.keys(t).filter((e) => t[e] !== void 0), u = n.update === void 0 ? c.filter((e) => e !== "id" && !n.conflictColumns.includes(e)) : n.update, f = u.length > 0 ? o.csv(u.map((e) => o`${o(e)} = EXCLUDED.${o(e)}`)) : o`${o(n.conflictColumns[0])} = EXCLUDED.${o(n.conflictColumns[0])}`, p = o`INSERT INTO ${o(e)} ${o.insert(O(t, e, q))} ON CONFLICT (${o.csv(s)}) DO UPDATE SET ${f} RETURNING *`, m = r ? i.provideService(p, _, r) : p, h = (await this.runtime.runPromise(m))[0];
161
164
  if (!h) throw Error(`PostgresDataStore.upsert: no row returned for table '${e}'`);
162
- if (this.changeStrategy === "inline") {
165
+ {
163
166
  let n = t.id !== void 0 && t.id === h.id ? "insert" : "update";
164
167
  await this.routeEvent({
165
168
  table: e,
@@ -171,9 +174,9 @@ var P = (e) => e ? { rejectUnauthorized: !1 } : !1, F = /^[A-Za-z_][A-Za-z0-9_]*
171
174
  return h;
172
175
  }
173
176
  async executeInsertIgnore(e, t, n, r, a) {
174
- t = M(e, t);
175
- let o = this.sql, s = n.conflictColumns.map((e) => o`${o(e)}`), c = o`INSERT INTO ${o(e)} ${o.insert(T(t, e, W))} ON CONFLICT (${o.csv(s)}) DO NOTHING RETURNING *`, l = r ? i.provideService(c, _, r) : c, u = (await this.runtime.runPromise(l))[0];
176
- if (u) return this.changeStrategy === "inline" && await this.routeEvent({
177
+ t = F(e, t);
178
+ let o = this.sql, s = n.conflictColumns.map((e) => o`${o(e)}`), c = o`INSERT INTO ${o(e)} ${o.insert(O(t, e, q))} ON CONFLICT (${o.csv(s)}) DO NOTHING RETURNING *`, l = r ? i.provideService(c, _, r) : c, u = (await this.runtime.runPromise(l))[0];
179
+ if (u) return await this.routeEvent({
177
180
  table: e,
178
181
  op: "insert",
179
182
  old: null,
@@ -184,9 +187,9 @@ var P = (e) => e ? { rejectUnauthorized: !1 } : !1, F = /^[A-Za-z_][A-Za-z0-9_]*
184
187
  return m[0];
185
188
  }
186
189
  async executeInsertMany(e, t, n, r) {
187
- if (t = N(e, t), t.length === 0) return [];
188
- let a = this.sql, o = a`INSERT INTO ${a(e)} ${a.insert(t.map((t) => T(t, e, W)))} RETURNING *`, s = n ? i.provideService(o, _, n) : o, c = await this.runtime.runPromise(s);
189
- if (this.changeStrategy === "inline") for (let t of c) await this.routeEvent({
190
+ if (t = I(e, t), t.length === 0) return [];
191
+ let a = this.sql, o = a`INSERT INTO ${a(e)} ${a.insert(t.map((t) => O(t, e, q)))} RETURNING *`, s = n ? i.provideService(o, _, n) : o, c = await this.runtime.runPromise(s);
192
+ for (let t of c) await this.routeEvent({
190
193
  table: e,
191
194
  op: "insert",
192
195
  old: null,
@@ -196,7 +199,7 @@ var P = (e) => e ? { rejectUnauthorized: !1 } : !1, F = /^[A-Za-z_][A-Za-z0-9_]*
196
199
  }
197
200
  async executePatchJson(e, t, n, r, a, o) {
198
201
  let s = this.sql, c = n.split("."), l = c[0], u = c.slice(1), d = JSON.stringify(r ?? null), f = u.length === 0 ? s`${s(l)} = COALESCE(${s(l)}, '{}'::jsonb) || ${d}::jsonb` : s`${s(l)} = jsonb_set(COALESCE(${s(l)}, '{}'::jsonb), ${`{${u.join(",")}}`}, ${d}::jsonb, true)`, p = s`UPDATE ${s(e)} SET ${f} WHERE ${s("id")} = ${t} RETURNING *`, m = a ? i.provideService(p, _, a) : p, h = (await this.runtime.runPromise(m))[0];
199
- return h ? (this.changeStrategy === "inline" && await this.routeEvent({
202
+ return h ? (await this.routeEvent({
200
203
  table: e,
201
204
  op: "update",
202
205
  old: null,
@@ -205,7 +208,7 @@ var P = (e) => e ? { rejectUnauthorized: !1 } : !1, F = /^[A-Za-z_][A-Za-z0-9_]*
205
208
  }
206
209
  async executeDelete(e, t, n, r) {
207
210
  let a = this.sql, o = a`DELETE FROM ${a(e)} WHERE ${a("id")} = ${t} RETURNING *`, s = n ? i.provideService(o, _, n) : o, c = (await this.runtime.runPromise(s))[0];
208
- return c ? (this.changeStrategy === "inline" && await this.routeEvent({
211
+ return c ? (await this.routeEvent({
209
212
  table: e,
210
213
  op: "delete",
211
214
  old: c,
@@ -213,68 +216,85 @@ var P = (e) => e ? { rejectUnauthorized: !1 } : !1, F = /^[A-Za-z_][A-Za-z0-9_]*
213
216
  }, r, n), !0) : !1;
214
217
  }
215
218
  async appendInTxn(e, t, n) {
216
- let r = this.sql, a = r`INSERT INTO ${r(e)} ${r.insert(T(t, e, W))}`;
219
+ let r = this.sql, a = r`INSERT INTO ${r(e)} ${r.insert(O(t, e, q))}`;
217
220
  await this.runtime.runPromise(n ? i.provideService(a, _, n) : a);
218
221
  }
222
+ async maxInTxn(e, t, n, r) {
223
+ let a = this.sql, o = Object.entries(n).map(([e, t]) => a`${a(e)} = ${t}`), s = a`SELECT MAX(${a(t)}) AS ${a("m")} FROM ${a(e)} WHERE ${a.and(o)}`, c = (await this.runtime.runPromise(r ? i.provideService(s, _, r) : s))[0]?.m;
224
+ return c == null ? null : Number(c);
225
+ }
219
226
  async routeEvent(e, t, n = null) {
220
- e = {
227
+ if (e = {
221
228
  ...b(),
222
229
  ...e
223
- }, k(e.table) && await j((e, t) => this.appendInTxn(e, t, n), {
230
+ }, M(e.table) && await te({
231
+ append: (e, t) => this.appendInTxn(e, t, n),
232
+ maxOf: (e, t, r) => this.maxInTxn(e, t, r, n)
233
+ }, {
224
234
  table: e.table,
225
235
  op: e.op,
226
236
  next: e.new,
227
237
  prev: e.old,
228
238
  traceId: e.traceId,
229
239
  subjectId: e.subjectId
230
- }), D(e.table) && (t === null ? this.emitter.emit("change", e) : t.push(e));
240
+ }), A(e.table)) {
241
+ if (this.changeStrategy === "cdc") {
242
+ let t = (e.op === "delete" ? e.old : e.new)?.id;
243
+ t != null && N(x(e.table, e.op, t), {
244
+ ...e.traceId === void 0 ? {} : { traceId: e.traceId },
245
+ ...e.subjectId === void 0 ? {} : { subjectId: e.subjectId }
246
+ });
247
+ return;
248
+ }
249
+ t === null ? this.emitter.emit("change", e) : t.push(e);
250
+ }
231
251
  }
232
252
  query(e) {
233
253
  return this.runWithEager(e, null);
234
254
  }
235
255
  raw(e, t) {
236
- let n = C(e, this.sql);
256
+ let n = T(e, this.sql);
237
257
  return this.runtime.runPromise(n);
238
258
  }
239
259
  async runWithEager(e, t) {
240
- if (!E(e)) return this.executeQuery(e, t);
241
- let n = x(e, this.sql, "postgres");
260
+ if (!k(e)) return this.executeQuery(e, t);
261
+ let n = C(e, this.sql, "postgres");
242
262
  if (n !== null) try {
243
263
  let e = t ? i.provideService(n.fragment, _, t) : n.fragment, r = await this.runtime.runPromise(e);
244
264
  return n.decode(r);
245
265
  } catch (e) {
246
266
  if (e instanceof v) throw e;
247
- G.warn("postgres JSON-agg eager-load failed; falling back to walker", { err: e });
267
+ J.warn("postgres JSON-agg eager-load failed; falling back to walker", { err: e });
248
268
  }
249
- return y(await this.executeQuery(e, t), e.eager, e.sourceTable ?? A(e.table), (e) => this.executeQuery(e, t));
269
+ return y(await this.executeQuery(e, t), e.eager, e.sourceTable ?? P(e.table), (e) => this.executeQuery(e, t));
250
270
  }
251
271
  getInternalRunWithEager() {
252
272
  return (e, t) => this.runWithEager(e, t);
253
273
  }
254
274
  insert(e, t) {
255
- return this.executeInsert(e, t, null, null);
275
+ return X()(() => this.executeInsert(e, t, null, null));
256
276
  }
257
277
  insertMany(e, t) {
258
- return this.executeInsertMany(e, t, null, null);
278
+ return X()(() => this.executeInsertMany(e, t, null, null));
259
279
  }
260
280
  patchJson(e, t, n, r) {
261
- return this.executePatchJson(e, t, n, r, null, null);
281
+ return X()(() => this.executePatchJson(e, t, n, r, null, null));
262
282
  }
263
283
  update(e, t, n) {
264
- return this.executeUpdate(e, t, n, null, null);
284
+ return X()(() => this.executeUpdate(e, t, n, null, null));
265
285
  }
266
286
  delete(e, t) {
267
- return this.executeDelete(e, t, null, null);
287
+ return X()(() => this.executeDelete(e, t, null, null));
268
288
  }
269
289
  async updateMany(e, t, n) {
270
- return this.executeUpdateMany(e, t, n, null, null);
290
+ return X()(() => this.executeUpdateMany(e, t, n, null, null));
271
291
  }
272
292
  async deleteMany(e, t) {
273
- return this.executeDeleteMany(e, t, null, null);
293
+ return X()(() => this.executeDeleteMany(e, t, null, null));
274
294
  }
275
295
  async executeUpdateMany(e, t, n, r, a) {
276
- let o = this.sql, s = S(n.where, o), c = o`UPDATE ${o(e)} SET ${o.update(T(t, e, W))} WHERE ${s} RETURNING *`, l = r ? i.provideService(c, _, r) : c, u = await this.runtime.runPromise(l);
277
- if (this.changeStrategy === "inline") for (let t of u) await this.routeEvent({
296
+ let o = this.sql, s = w(n.where, o), c = o`UPDATE ${o(e)} SET ${o.update(O(t, e, q))} WHERE ${s} RETURNING *`, l = r ? i.provideService(c, _, r) : c, u = await this.runtime.runPromise(l);
297
+ for (let t of u) await this.routeEvent({
278
298
  table: e,
279
299
  op: "update",
280
300
  old: null,
@@ -283,8 +303,8 @@ var P = (e) => e ? { rejectUnauthorized: !1 } : !1, F = /^[A-Za-z_][A-Za-z0-9_]*
283
303
  return u.length;
284
304
  }
285
305
  async executeDeleteMany(e, t, n, r) {
286
- let a = this.sql, o = S(t.where, a), s = a`DELETE FROM ${a(e)} WHERE ${o} RETURNING *`, c = n ? i.provideService(s, _, n) : s, l = await this.runtime.runPromise(c);
287
- if (this.changeStrategy === "inline") for (let t of l) await this.routeEvent({
306
+ let a = this.sql, o = w(t.where, a), s = a`DELETE FROM ${a(e)} WHERE ${o} RETURNING *`, c = n ? i.provideService(s, _, n) : s, l = await this.runtime.runPromise(c);
307
+ for (let t of l) await this.routeEvent({
288
308
  table: e,
289
309
  op: "delete",
290
310
  old: t,
@@ -293,10 +313,10 @@ var P = (e) => e ? { rejectUnauthorized: !1 } : !1, F = /^[A-Za-z_][A-Za-z0-9_]*
293
313
  return l.length;
294
314
  }
295
315
  upsert(e, t, n) {
296
- return this.executeUpsert(e, t, n, null, null);
316
+ return X()(() => this.executeUpsert(e, t, n, null, null));
297
317
  }
298
318
  insertIgnore(e, t, n) {
299
- return this.executeInsertIgnore(e, t, n, null, null);
319
+ return X()(() => this.executeInsertIgnore(e, t, n, null, null));
300
320
  }
301
321
  emitChange(e) {
302
322
  this.emitter.emit("change", e);
@@ -333,26 +353,26 @@ var P = (e) => e ? { rejectUnauthorized: !1 } : !1, F = /^[A-Za-z_][A-Za-z0-9_]*
333
353
  }
334
354
  async transactional(e) {
335
355
  this.inflightTxns++;
336
- let t = 0, r = i.suspend(() => {
337
- let n = ++t;
338
- return this.sql.withTransaction(i.flatMap(i.serviceOption(_), (t) => {
339
- if (l.isNone(t)) return i.fail(/* @__PURE__ */ Error("PostgresDataStore.transactional: TransactionConnection unexpectedly missing inside withTransaction."));
340
- let r = new J(this, t.value);
356
+ let t = X(), r = 0, o = i.suspend(() => {
357
+ let n = ++r;
358
+ return this.sql.withTransaction(i.flatMap(i.serviceOption(_), (r) => {
359
+ if (l.isNone(r)) return i.fail(/* @__PURE__ */ Error("PostgresDataStore.transactional: TransactionConnection unexpectedly missing inside withTransaction."));
360
+ let a = new Q(this, r.value);
341
361
  return i.tryPromise({
342
- try: () => e(r).then((e) => ({
362
+ try: () => t(() => e(a)).then((e) => ({
343
363
  result: e,
344
- view: r,
364
+ view: a,
345
365
  attempt: n
346
366
  })),
347
367
  catch: (e) => e
348
368
  });
349
369
  }));
350
- }), o = d.exponential("10 millis").pipe(d.compose(d.recurs(3)), d.whileInput(H)), s = r.pipe(i.retry(o), i.withSpan("store.transactional", { attributes: {
370
+ }), s = d.exponential("10 millis").pipe(d.compose(d.recurs(3)), d.whileInput(G)), c = o.pipe(i.retry(s), i.withSpan("store.transactional", { attributes: {
351
371
  "db.system": "postgresql",
352
372
  "db.operation": "transaction"
353
373
  } }));
354
374
  try {
355
- let e = await this.runtime.runPromiseExit(s);
375
+ let e = await this.runtime.runPromiseExit(c);
356
376
  if (a.isSuccess(e)) return e.value.view.commitEvents(), e.value.result;
357
377
  throw n.squash(e.cause);
358
378
  } finally {
@@ -368,7 +388,10 @@ var P = (e) => e ? { rejectUnauthorized: !1 } : !1, F = /^[A-Za-z_][A-Za-z0-9_]*
368
388
  return this.changeStrategy === "cdc" ? "fleet" : "local";
369
389
  }
370
390
  injectExternalChange(e) {
371
- D(e.table) && this.emitter.emit("change", {
391
+ if (!A(e.table)) return;
392
+ let t = (e.op === "delete" ? e.old : e.new)?.id, n = t == null ? void 0 : S(x(e.table, e.op, t));
393
+ this.emitter.emit("change", {
394
+ ...n,
372
395
  ...e,
373
396
  origin: "injected"
374
397
  });
@@ -380,7 +403,7 @@ var P = (e) => e ? { rejectUnauthorized: !1 } : !1, F = /^[A-Za-z_][A-Za-z0-9_]*
380
403
  if (this.inflightTxns > 0) {
381
404
  let t = Date.now() + e;
382
405
  for (; this.inflightTxns > 0 && Date.now() < t;) await new Promise((e) => setTimeout(e, 25));
383
- this.inflightTxns > 0 && G.warn("close: grace period expired with in-flight transactions — forcing dispose", {
406
+ this.inflightTxns > 0 && J.warn("close: grace period expired with in-flight transactions — forcing dispose", {
384
407
  gracePeriodMs: e,
385
408
  inflight: this.inflightTxns
386
409
  });
@@ -396,12 +419,12 @@ var P = (e) => e ? { rejectUnauthorized: !1 } : !1, F = /^[A-Za-z_][A-Za-z0-9_]*
396
419
  let t = JSON.parse(e);
397
420
  this.injectExternalChange(t);
398
421
  } catch (e) {
399
- G.warn("cdc: bad payload", { channel: this.cdcChannel }, e);
422
+ J.warn("cdc: bad payload", { channel: this.cdcChannel }, e);
400
423
  }
401
424
  };
402
425
  this.cdcFiber = this.runtime.runFork(e.pipe(f.runForEach((e) => i.sync(() => n(e)))));
403
426
  }
404
- }, J = class {
427
+ }, Q = class {
405
428
  parent;
406
429
  txn;
407
430
  events = [];
@@ -455,7 +478,7 @@ var P = (e) => e ? { rejectUnauthorized: !1 } : !1, F = /^[A-Za-z_][A-Za-z0-9_]*
455
478
  this.events.length = 0;
456
479
  }
457
480
  }
458
- }, Y = class {
481
+ }, ne = class {
459
482
  parent;
460
483
  namespace;
461
484
  constructor(e, t) {
@@ -509,14 +532,14 @@ var P = (e) => e ? { rejectUnauthorized: !1 } : !1, F = /^[A-Za-z_][A-Za-z0-9_]*
509
532
  return r === void 0 ? Promise.reject(/* @__PURE__ */ Error("PostgresNamespaceView.raw: underlying store has no raw()")) : r(e, t);
510
533
  });
511
534
  }
512
- }, X = (e) => e.__postgresReplicationFriend ?? null, Z = (e, t) => {
535
+ }, $ = (e) => e.__postgresReplicationFriend ?? null, re = (e, t) => {
513
536
  let [n, r] = e.split("/"), [i, a] = t.split("/");
514
537
  if (!n || !r || !i || !a) throw Error(`postgres LSN compare: invalid format (${e} vs ${t})`);
515
538
  let o = parseInt(n, 16), s = parseInt(r, 16), c = parseInt(i, 16);
516
539
  return o === c ? s - parseInt(a, 16) : o - c;
517
- }, Q = () => ({
540
+ }, ie = () => ({
518
541
  async capturePrimaryPosition(e) {
519
- let t = X(e);
542
+ let t = $(e);
520
543
  if (t === null) throw Error("postgresReplicationAdapter: primary is not a PostgresDataStore (missing __postgresReplicationFriend). Pass the postgres store directly.");
521
544
  return t.runEffect(i.gen(function* () {
522
545
  let e = (yield* (yield* g)`SELECT pg_current_wal_lsn()::text AS lsn`)[0]?.lsn;
@@ -524,7 +547,7 @@ var P = (e) => e ? { rejectUnauthorized: !1 } : !1, F = /^[A-Za-z_][A-Za-z0-9_]*
524
547
  }));
525
548
  },
526
549
  async probeReplicaPosition(e) {
527
- let t = X(e);
550
+ let t = $(e);
528
551
  if (t === null) throw Error("postgresReplicationAdapter: replica is not a PostgresDataStore.");
529
552
  return t.runEffect(i.gen(function* () {
530
553
  let e = (yield* (yield* g)`
@@ -534,14 +557,14 @@ var P = (e) => e ? { rejectUnauthorized: !1 } : !1, F = /^[A-Za-z_][A-Za-z0-9_]*
534
557
  }));
535
558
  },
536
559
  compare(e, t) {
537
- return Z(t, e) >= 0 ? "caught-up" : "behind";
560
+ return re(t, e) >= 0 ? "caught-up" : "behind";
538
561
  }
539
- }), $ = {
562
+ }), ae = {
540
563
  id: "postgres",
541
- makeSqlLayer: (e) => z(e),
542
- makeStore: (e) => K(e),
564
+ makeSqlLayer: (e) => H(e),
565
+ makeStore: (e) => Y(e),
543
566
  compileContains: (e, t, n) => n`${e} ILIKE ${`%${t.replace(/[\\%_]/g, (e) => `\\${e}`)}%`} ESCAPE '\\'`,
544
- retryFilter: U
567
+ retryFilter: K
545
568
  };
546
569
  //#endregion
547
- export { e as PgClient, R as connectionFromConfig, K as makePostgresDataStore, I as makePostgresSqlLayer, z as makePostgresSqlLayerFromConfig, $ as postgresDialect, Q as postgresReplicationAdapter, U as postgresRetryFilter };
570
+ export { e as PgClient, V as connectionFromConfig, Y as makePostgresDataStore, z as makePostgresSqlLayer, H as makePostgresSqlLayerFromConfig, ae as postgresDialect, ie as postgresReplicationAdapter, K as postgresRetryFilter };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@voltro/sql-postgres",
3
- "version": "0.19.0",
3
+ "version": "0.20.0",
4
4
  "description": "PostgreSQL dialect adapter for Voltro's cross-dialect DataStore (LISTEN/NOTIFY reactivity, logical-replication CDC).",
5
5
  "keywords": [
6
6
  "voltro",
@@ -34,8 +34,8 @@
34
34
  "dependencies": {
35
35
  "@effect/sql": "^0.51.1",
36
36
  "@effect/sql-pg": "^0.52.1",
37
- "@voltro/database": "0.19.0",
38
- "@voltro/logger": "0.19.0",
37
+ "@voltro/database": "0.20.0",
38
+ "@voltro/logger": "0.20.0",
39
39
  "pg": "^8.22.0"
40
40
  },
41
41
  "peerDependencies": {