@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 +87 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +110 -87
- package/package.json +3 -3
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,
|
|
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
|
|
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(
|
|
17
|
+
...t.ssl === void 0 ? {} : { ssl: r.succeed(L(t.ssl)) }
|
|
18
18
|
});
|
|
19
|
-
if (!
|
|
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:
|
|
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
|
-
},
|
|
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
|
-
},
|
|
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 ??
|
|
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
|
-
},
|
|
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
|
-
},
|
|
77
|
-
let t =
|
|
78
|
-
return t !== void 0 &&
|
|
79
|
-
},
|
|
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
|
|
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
|
-
},
|
|
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,
|
|
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
|
|
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,
|
|
99
|
-
if (l.isNone(
|
|
100
|
-
let
|
|
101
|
-
return i.flatMap(
|
|
102
|
-
try: () => t(
|
|
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:
|
|
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(
|
|
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 =
|
|
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 =
|
|
126
|
-
let a = this.sql, o = a`INSERT INTO ${a(e)} ${a.insert(
|
|
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
|
|
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(
|
|
137
|
-
return l ? (
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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 =
|
|
175
|
-
let o = this.sql, s = n.conflictColumns.map((e) => o`${o(e)}`), c = o`INSERT INTO ${o(e)} ${o.insert(
|
|
176
|
-
if (u) return
|
|
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 =
|
|
188
|
-
let a = this.sql, o = a`INSERT INTO ${a(e)} ${a.insert(t.map((t) =>
|
|
189
|
-
|
|
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 ? (
|
|
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 ? (
|
|
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(
|
|
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
|
-
},
|
|
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
|
-
}),
|
|
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 =
|
|
256
|
+
let n = T(e, this.sql);
|
|
237
257
|
return this.runtime.runPromise(n);
|
|
238
258
|
}
|
|
239
259
|
async runWithEager(e, t) {
|
|
240
|
-
if (!
|
|
241
|
-
let n =
|
|
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
|
-
|
|
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 ??
|
|
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 =
|
|
277
|
-
|
|
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 =
|
|
287
|
-
|
|
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 =
|
|
337
|
-
let n = ++
|
|
338
|
-
return this.sql.withTransaction(i.flatMap(i.serviceOption(_), (
|
|
339
|
-
if (l.isNone(
|
|
340
|
-
let
|
|
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(
|
|
362
|
+
try: () => t(() => e(a)).then((e) => ({
|
|
343
363
|
result: e,
|
|
344
|
-
view:
|
|
364
|
+
view: a,
|
|
345
365
|
attempt: n
|
|
346
366
|
})),
|
|
347
367
|
catch: (e) => e
|
|
348
368
|
});
|
|
349
369
|
}));
|
|
350
|
-
}),
|
|
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(
|
|
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
|
-
|
|
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 &&
|
|
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
|
-
|
|
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
|
-
},
|
|
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
|
-
},
|
|
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
|
-
},
|
|
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
|
-
},
|
|
540
|
+
}, ie = () => ({
|
|
518
541
|
async capturePrimaryPosition(e) {
|
|
519
|
-
let t =
|
|
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 =
|
|
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
|
|
560
|
+
return re(t, e) >= 0 ? "caught-up" : "behind";
|
|
538
561
|
}
|
|
539
|
-
}),
|
|
562
|
+
}), ae = {
|
|
540
563
|
id: "postgres",
|
|
541
|
-
makeSqlLayer: (e) =>
|
|
542
|
-
makeStore: (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:
|
|
567
|
+
retryFilter: K
|
|
545
568
|
};
|
|
546
569
|
//#endregion
|
|
547
|
-
export { e as PgClient,
|
|
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.
|
|
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.
|
|
38
|
-
"@voltro/logger": "0.
|
|
37
|
+
"@voltro/database": "0.20.0",
|
|
38
|
+
"@voltro/logger": "0.20.0",
|
|
39
39
|
"pg": "^8.22.0"
|
|
40
40
|
},
|
|
41
41
|
"peerDependencies": {
|