@voltro/sql-postgres 0.1.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 +52 -0
- package/LICENSE +57 -0
- package/README.md +26 -0
- package/SECURITY.md +56 -0
- package/THIRD-PARTY-NOTICES.md +781 -0
- package/dist/index.d.ts +375 -0
- package/dist/index.js +530 -0
- package/package.json +47 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,530 @@
|
|
|
1
|
+
import { PgClient as e, PgClient as t } from "@effect/sql-pg";
|
|
2
|
+
import { Cause as n, Config as r, Effect as i, Exit as a, Fiber as o, Layer as s, ManagedRuntime as c, Option as l, Redacted as u, Schedule as d, Stream as f } from "effect";
|
|
3
|
+
import p from "pg";
|
|
4
|
+
import { EventEmitter as m } from "node:events";
|
|
5
|
+
import { createLogger as h } from "@voltro/logger";
|
|
6
|
+
import { SqlClient as g, TransactionConnection as _ } from "@effect/sql/SqlClient";
|
|
7
|
+
import { attachEagerLoads as v, compileEagerJson as y, compilePredicate as b, compileRawFragment as x, compileSelect as S, encodeRowForSchema as C, hasEagerLoads as w, requireTable as T } from "@voltro/database";
|
|
8
|
+
//#region src/sqlLayer.ts
|
|
9
|
+
var E = (e) => e ? { rejectUnauthorized: !1 } : !1, D = /^[A-Za-z_][A-Za-z0-9_]*$/, O = (t) => {
|
|
10
|
+
if (t.schema === void 0) return e.layerConfig({
|
|
11
|
+
host: r.succeed(t.host),
|
|
12
|
+
port: r.succeed(t.port),
|
|
13
|
+
username: r.succeed(t.username),
|
|
14
|
+
password: r.succeed(u.make(t.password)),
|
|
15
|
+
database: r.succeed(t.database),
|
|
16
|
+
...t.maxConnections === void 0 ? {} : { maxConnections: r.succeed(t.maxConnections) },
|
|
17
|
+
...t.ssl === void 0 ? {} : { ssl: r.succeed(E(t.ssl)) }
|
|
18
|
+
});
|
|
19
|
+
if (!D.test(t.schema)) throw Error(`DB_SCHEMA '${t.schema}' is not a valid postgres identifier (expected ${D}).`);
|
|
20
|
+
let n = t.schema, a = i.acquireRelease(i.sync(() => new p.Pool({
|
|
21
|
+
host: t.host,
|
|
22
|
+
port: t.port,
|
|
23
|
+
user: t.username,
|
|
24
|
+
password: t.password,
|
|
25
|
+
database: t.database,
|
|
26
|
+
...t.maxConnections === void 0 ? {} : { max: t.maxConnections },
|
|
27
|
+
...t.ssl === void 0 ? {} : { ssl: E(t.ssl) },
|
|
28
|
+
options: `-c search_path="${n}"`
|
|
29
|
+
})), (e) => i.promise(() => e.end()));
|
|
30
|
+
return e.layerFromPool({ acquire: a });
|
|
31
|
+
}, k = (e) => {
|
|
32
|
+
let t = e.get("sslmode");
|
|
33
|
+
if (t !== null) {
|
|
34
|
+
if (t === "require") return !0;
|
|
35
|
+
if (t === "disable") return !1;
|
|
36
|
+
throw Error(`DB_URL '?sslmode=${t}' is not supported by the postgres dialect — use 'require' (TLS without certificate verification) or 'disable' (plaintext).`);
|
|
37
|
+
}
|
|
38
|
+
let n = e.get("ssl");
|
|
39
|
+
if (n !== null) {
|
|
40
|
+
if (n === "true" || n === "1") return !0;
|
|
41
|
+
if (n === "false" || n === "0") return !1;
|
|
42
|
+
throw Error(`DB_URL '?ssl=${n}' is not supported by the postgres dialect — use 'true'/'1' or 'false'/'0'.`);
|
|
43
|
+
}
|
|
44
|
+
}, A = (e) => {
|
|
45
|
+
let t = e.schema === void 0 ? {} : { schema: e.schema };
|
|
46
|
+
if (e.url) {
|
|
47
|
+
let n = new URL(e.url), r = e.ssl ?? k(n.searchParams);
|
|
48
|
+
return {
|
|
49
|
+
host: n.hostname || "localhost",
|
|
50
|
+
port: n.port ? Number(n.port) : 5432,
|
|
51
|
+
username: decodeURIComponent(n.username || "app"),
|
|
52
|
+
password: decodeURIComponent(n.password || "app"),
|
|
53
|
+
database: n.pathname.replace(/^\//, "") || "app",
|
|
54
|
+
...e.maxConnections === void 0 ? {} : { maxConnections: e.maxConnections },
|
|
55
|
+
...r === void 0 ? {} : { ssl: r },
|
|
56
|
+
...t
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
return {
|
|
60
|
+
host: e.host ?? "localhost",
|
|
61
|
+
port: e.port ?? 5432,
|
|
62
|
+
username: e.username ?? "app",
|
|
63
|
+
password: e.password ?? "app",
|
|
64
|
+
database: e.database ?? "app",
|
|
65
|
+
...e.maxConnections === void 0 ? {} : { maxConnections: e.maxConnections },
|
|
66
|
+
...e.ssl === void 0 ? {} : { ssl: e.ssl },
|
|
67
|
+
...t
|
|
68
|
+
};
|
|
69
|
+
}, j = (e) => O(A(e)), M = /* @__PURE__ */ new Set(["40001", "40P01"]), N = (e) => {
|
|
70
|
+
let t = e;
|
|
71
|
+
for (let e = 0; e < 5 && typeof t == "object" && t; e++) {
|
|
72
|
+
let e = t.code;
|
|
73
|
+
if (typeof e == "string") return e;
|
|
74
|
+
t = t.cause;
|
|
75
|
+
}
|
|
76
|
+
}, P = (e) => {
|
|
77
|
+
let t = N(e);
|
|
78
|
+
return t !== void 0 && M.has(t);
|
|
79
|
+
}, F = (e) => P(e) ? "retry" : "noRetry", I = ["json"], L = h({ scope: "voltro:postgres" }), R = 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
|
+
return i === "cdc" && await a.startCdcConsumer(), a;
|
|
82
|
+
}, z = class {
|
|
83
|
+
sql;
|
|
84
|
+
runtime;
|
|
85
|
+
changeStrategy;
|
|
86
|
+
cdcChannel;
|
|
87
|
+
emitter = new m();
|
|
88
|
+
cdcFiber = null;
|
|
89
|
+
inflightTxns = 0;
|
|
90
|
+
constructor(e, t, n, r) {
|
|
91
|
+
this.sql = e, this.runtime = t, this.changeStrategy = n, this.cdcChannel = r;
|
|
92
|
+
}
|
|
93
|
+
withNamespace(e) {
|
|
94
|
+
return e === null ? this : new V(this, e);
|
|
95
|
+
}
|
|
96
|
+
async runInNamespace(e, t) {
|
|
97
|
+
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 B(this, a);
|
|
101
|
+
return i.flatMap(o, () => i.tryPromise({
|
|
102
|
+
try: () => t(s).then((e) => ({
|
|
103
|
+
result: e,
|
|
104
|
+
view: s
|
|
105
|
+
})),
|
|
106
|
+
catch: (e) => e
|
|
107
|
+
}));
|
|
108
|
+
})).pipe(i.withSpan("store.namespace", { attributes: {
|
|
109
|
+
"db.system": "postgresql",
|
|
110
|
+
"db.operation": "namespace.transaction"
|
|
111
|
+
} }));
|
|
112
|
+
try {
|
|
113
|
+
let e = await this.runtime.runPromise(r);
|
|
114
|
+
return e.view.commitEvents(), e.result;
|
|
115
|
+
} finally {
|
|
116
|
+
this.inflightTxns--;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
__postgresReplicationFriend = { runEffect: (e) => this.runtime.runPromise(e) };
|
|
120
|
+
async executeQuery(e, t) {
|
|
121
|
+
let n = S(e, this.sql), r = t ? i.provideService(n, _, t) : n;
|
|
122
|
+
return this.runtime.runPromise(r);
|
|
123
|
+
}
|
|
124
|
+
async executeInsert(e, t, n, r) {
|
|
125
|
+
let a = this.sql, o = a`INSERT INTO ${a(e)} ${a.insert(C(t, e, I))} RETURNING *`, s = n ? i.provideService(o, _, n) : o, c = (await this.runtime.runPromise(s))[0];
|
|
126
|
+
if (!c) throw Error(`PostgresDataStore.insert: no row returned for table '${e}'`);
|
|
127
|
+
return this.changeStrategy === "inline" && this.routeEvent({
|
|
128
|
+
table: e,
|
|
129
|
+
op: "insert",
|
|
130
|
+
old: null,
|
|
131
|
+
new: c
|
|
132
|
+
}, r), c;
|
|
133
|
+
}
|
|
134
|
+
async executeUpdate(e, t, n, r, a) {
|
|
135
|
+
let o = this.sql, s = o`UPDATE ${o(e)} SET ${o.update(C(n, e, I))} WHERE ${o("id")} = ${t} RETURNING *`, c = r ? i.provideService(s, _, r) : s, l = (await this.runtime.runPromise(c))[0];
|
|
136
|
+
return l ? (this.changeStrategy === "inline" && this.routeEvent({
|
|
137
|
+
table: e,
|
|
138
|
+
op: "update",
|
|
139
|
+
old: null,
|
|
140
|
+
new: l
|
|
141
|
+
}, a), l) : null;
|
|
142
|
+
}
|
|
143
|
+
async executeUpsert(e, t, n, r, a) {
|
|
144
|
+
let o = this.sql;
|
|
145
|
+
if (typeof n.update == "function") {
|
|
146
|
+
let s = n.update, c = n.conflictColumns.map((e) => o`${o(e)} = ${t[e]}`), u = (n) => {
|
|
147
|
+
let r = o`SELECT * FROM ${o(e)} WHERE ${o.and(c)} LIMIT 1 FOR UPDATE`;
|
|
148
|
+
return this.runtime.runPromise(i.flatMap(i.provideService(r, _, n), (r) => i.promise(() => r[0] ? this.executeUpdate(e, r[0].id, s(r[0]), n, a).then((e) => e) : this.executeInsert(e, t, n, a))));
|
|
149
|
+
};
|
|
150
|
+
if (r) return u(r);
|
|
151
|
+
this.inflightTxns++;
|
|
152
|
+
try {
|
|
153
|
+
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(P));
|
|
154
|
+
return await this.runtime.runPromise(e.pipe(i.retry(t)));
|
|
155
|
+
} finally {
|
|
156
|
+
this.inflightTxns--;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
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(C(t, e, I))} ON CONFLICT (${o.csv(s)}) DO UPDATE SET ${f} RETURNING *`, m = r ? i.provideService(p, _, r) : p, h = (await this.runtime.runPromise(m))[0];
|
|
160
|
+
if (!h) throw Error(`PostgresDataStore.upsert: no row returned for table '${e}'`);
|
|
161
|
+
if (this.changeStrategy === "inline") {
|
|
162
|
+
let n = t.id !== void 0 && t.id === h.id ? "insert" : "update";
|
|
163
|
+
this.routeEvent({
|
|
164
|
+
table: e,
|
|
165
|
+
op: n,
|
|
166
|
+
old: null,
|
|
167
|
+
new: h
|
|
168
|
+
}, a);
|
|
169
|
+
}
|
|
170
|
+
return h;
|
|
171
|
+
}
|
|
172
|
+
async executeInsertIgnore(e, t, n, r, a) {
|
|
173
|
+
let o = this.sql, s = n.conflictColumns.map((e) => o`${o(e)}`), c = o`INSERT INTO ${o(e)} ${o.insert(C(t, e, I))} ON CONFLICT (${o.csv(s)}) DO NOTHING RETURNING *`, l = r ? i.provideService(c, _, r) : c, u = (await this.runtime.runPromise(l))[0];
|
|
174
|
+
if (u) return this.changeStrategy === "inline" && this.routeEvent({
|
|
175
|
+
table: e,
|
|
176
|
+
op: "insert",
|
|
177
|
+
old: null,
|
|
178
|
+
new: u
|
|
179
|
+
}, a), u;
|
|
180
|
+
let d = n.conflictColumns.map((e) => o`${o(e)} = ${t[e]}`), f = o`SELECT * FROM ${o(e)} WHERE ${o.and(d)} LIMIT 1`, p = r ? i.provideService(f, _, r) : f, m = await this.runtime.runPromise(p);
|
|
181
|
+
if (!m[0]) throw Error("PostgresDataStore.insertIgnore: row conflicted but lookup found nothing");
|
|
182
|
+
return m[0];
|
|
183
|
+
}
|
|
184
|
+
async executeInsertMany(e, t, n, r) {
|
|
185
|
+
if (t.length === 0) return [];
|
|
186
|
+
let a = this.sql, o = a`INSERT INTO ${a(e)} ${a.insert(t.map((t) => C(t, e, I)))} RETURNING *`, s = n ? i.provideService(o, _, n) : o, c = await this.runtime.runPromise(s);
|
|
187
|
+
if (this.changeStrategy === "inline") for (let t of c) this.routeEvent({
|
|
188
|
+
table: e,
|
|
189
|
+
op: "insert",
|
|
190
|
+
old: null,
|
|
191
|
+
new: t
|
|
192
|
+
}, r);
|
|
193
|
+
return c;
|
|
194
|
+
}
|
|
195
|
+
async executePatchJson(e, t, n, r, a, o) {
|
|
196
|
+
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];
|
|
197
|
+
return h ? (this.changeStrategy === "inline" && this.routeEvent({
|
|
198
|
+
table: e,
|
|
199
|
+
op: "update",
|
|
200
|
+
old: null,
|
|
201
|
+
new: h
|
|
202
|
+
}, o), h) : null;
|
|
203
|
+
}
|
|
204
|
+
async executeDelete(e, t, n, r) {
|
|
205
|
+
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];
|
|
206
|
+
return c ? (this.changeStrategy === "inline" && this.routeEvent({
|
|
207
|
+
table: e,
|
|
208
|
+
op: "delete",
|
|
209
|
+
old: c,
|
|
210
|
+
new: null
|
|
211
|
+
}, r), !0) : !1;
|
|
212
|
+
}
|
|
213
|
+
routeEvent(e, t) {
|
|
214
|
+
t === null ? this.emitter.emit("change", e) : t.push(e);
|
|
215
|
+
}
|
|
216
|
+
query(e) {
|
|
217
|
+
return this.runWithEager(e, null);
|
|
218
|
+
}
|
|
219
|
+
raw(e, t) {
|
|
220
|
+
let n = x(e, this.sql);
|
|
221
|
+
return this.runtime.runPromise(n);
|
|
222
|
+
}
|
|
223
|
+
async runWithEager(e, t) {
|
|
224
|
+
if (!w(e)) return this.executeQuery(e, t);
|
|
225
|
+
let n = y(e, this.sql, "postgres");
|
|
226
|
+
if (n !== null) try {
|
|
227
|
+
let e = t ? i.provideService(n.fragment, _, t) : n.fragment, r = await this.runtime.runPromise(e);
|
|
228
|
+
return n.decode(r);
|
|
229
|
+
} catch (e) {
|
|
230
|
+
L.warn("postgres JSON-agg eager-load failed; falling back to walker", { err: e });
|
|
231
|
+
}
|
|
232
|
+
return v(await this.executeQuery(e, t), e.eager, e.sourceTable ?? T(e.table), (e) => this.executeQuery(e, t));
|
|
233
|
+
}
|
|
234
|
+
getInternalRunWithEager() {
|
|
235
|
+
return (e, t) => this.runWithEager(e, t);
|
|
236
|
+
}
|
|
237
|
+
insert(e, t) {
|
|
238
|
+
return this.executeInsert(e, t, null, null);
|
|
239
|
+
}
|
|
240
|
+
insertMany(e, t) {
|
|
241
|
+
return this.executeInsertMany(e, t, null, null);
|
|
242
|
+
}
|
|
243
|
+
patchJson(e, t, n, r) {
|
|
244
|
+
return this.executePatchJson(e, t, n, r, null, null);
|
|
245
|
+
}
|
|
246
|
+
update(e, t, n) {
|
|
247
|
+
return this.executeUpdate(e, t, n, null, null);
|
|
248
|
+
}
|
|
249
|
+
delete(e, t) {
|
|
250
|
+
return this.executeDelete(e, t, null, null);
|
|
251
|
+
}
|
|
252
|
+
async updateMany(e, t, n) {
|
|
253
|
+
return this.executeUpdateMany(e, t, n, null, null);
|
|
254
|
+
}
|
|
255
|
+
async deleteMany(e, t) {
|
|
256
|
+
return this.executeDeleteMany(e, t, null, null);
|
|
257
|
+
}
|
|
258
|
+
async executeUpdateMany(e, t, n, r, a) {
|
|
259
|
+
let o = this.sql, s = b(n.where, o), c = o`UPDATE ${o(e)} SET ${o.update(C(t, e, I))} WHERE ${s} RETURNING *`, l = r ? i.provideService(c, _, r) : c, u = await this.runtime.runPromise(l);
|
|
260
|
+
if (this.changeStrategy === "inline") for (let t of u) this.routeEvent({
|
|
261
|
+
table: e,
|
|
262
|
+
op: "update",
|
|
263
|
+
old: null,
|
|
264
|
+
new: t
|
|
265
|
+
}, a);
|
|
266
|
+
return u.length;
|
|
267
|
+
}
|
|
268
|
+
async executeDeleteMany(e, t, n, r) {
|
|
269
|
+
let a = this.sql, o = b(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);
|
|
270
|
+
if (this.changeStrategy === "inline") for (let t of l) this.routeEvent({
|
|
271
|
+
table: e,
|
|
272
|
+
op: "delete",
|
|
273
|
+
old: t,
|
|
274
|
+
new: null
|
|
275
|
+
}, r);
|
|
276
|
+
return l.length;
|
|
277
|
+
}
|
|
278
|
+
upsert(e, t, n) {
|
|
279
|
+
return this.executeUpsert(e, t, n, null, null);
|
|
280
|
+
}
|
|
281
|
+
insertIgnore(e, t, n) {
|
|
282
|
+
return this.executeInsertIgnore(e, t, n, null, null);
|
|
283
|
+
}
|
|
284
|
+
emitChange(e) {
|
|
285
|
+
this.emitter.emit("change", e);
|
|
286
|
+
}
|
|
287
|
+
getInternalExecuteQuery() {
|
|
288
|
+
return this.executeQuery.bind(this);
|
|
289
|
+
}
|
|
290
|
+
getInternalExecuteInsert() {
|
|
291
|
+
return this.executeInsert.bind(this);
|
|
292
|
+
}
|
|
293
|
+
getInternalExecuteInsertMany() {
|
|
294
|
+
return this.executeInsertMany.bind(this);
|
|
295
|
+
}
|
|
296
|
+
getInternalExecutePatchJson() {
|
|
297
|
+
return this.executePatchJson.bind(this);
|
|
298
|
+
}
|
|
299
|
+
getInternalExecuteUpdate() {
|
|
300
|
+
return this.executeUpdate.bind(this);
|
|
301
|
+
}
|
|
302
|
+
getInternalExecuteDelete() {
|
|
303
|
+
return this.executeDelete.bind(this);
|
|
304
|
+
}
|
|
305
|
+
getInternalExecuteUpdateMany() {
|
|
306
|
+
return this.executeUpdateMany.bind(this);
|
|
307
|
+
}
|
|
308
|
+
getInternalExecuteDeleteMany() {
|
|
309
|
+
return this.executeDeleteMany.bind(this);
|
|
310
|
+
}
|
|
311
|
+
getInternalExecuteUpsert() {
|
|
312
|
+
return this.executeUpsert.bind(this);
|
|
313
|
+
}
|
|
314
|
+
getInternalExecuteInsertIgnore() {
|
|
315
|
+
return this.executeInsertIgnore.bind(this);
|
|
316
|
+
}
|
|
317
|
+
async transactional(e) {
|
|
318
|
+
this.inflightTxns++;
|
|
319
|
+
let t = 0, r = i.suspend(() => {
|
|
320
|
+
let n = ++t;
|
|
321
|
+
return this.sql.withTransaction(i.flatMap(i.serviceOption(_), (t) => {
|
|
322
|
+
if (l.isNone(t)) return i.fail(/* @__PURE__ */ Error("PostgresDataStore.transactional: TransactionConnection unexpectedly missing inside withTransaction."));
|
|
323
|
+
let r = new B(this, t.value);
|
|
324
|
+
return i.tryPromise({
|
|
325
|
+
try: () => e(r).then((e) => ({
|
|
326
|
+
result: e,
|
|
327
|
+
view: r,
|
|
328
|
+
attempt: n
|
|
329
|
+
})),
|
|
330
|
+
catch: (e) => e
|
|
331
|
+
});
|
|
332
|
+
}));
|
|
333
|
+
}), o = d.exponential("10 millis").pipe(d.compose(d.recurs(3)), d.whileInput(P)), s = r.pipe(i.retry(o), i.withSpan("store.transactional", { attributes: {
|
|
334
|
+
"db.system": "postgresql",
|
|
335
|
+
"db.operation": "transaction"
|
|
336
|
+
} }));
|
|
337
|
+
try {
|
|
338
|
+
let e = await this.runtime.runPromiseExit(s);
|
|
339
|
+
if (a.isSuccess(e)) return e.value.view.commitEvents(), e.value.result;
|
|
340
|
+
throw n.squash(e.cause);
|
|
341
|
+
} finally {
|
|
342
|
+
this.inflightTxns--;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
onChange(e) {
|
|
346
|
+
return this.emitter.on("change", e), () => {
|
|
347
|
+
this.emitter.off("change", e);
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
get changeScope() {
|
|
351
|
+
return this.changeStrategy === "cdc" ? "fleet" : "local";
|
|
352
|
+
}
|
|
353
|
+
injectExternalChange(e) {
|
|
354
|
+
this.emitter.emit("change", {
|
|
355
|
+
...e,
|
|
356
|
+
origin: "injected"
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
run(e) {
|
|
360
|
+
return this.runtime.runPromise(e);
|
|
361
|
+
}
|
|
362
|
+
async close(e = 5e3) {
|
|
363
|
+
if (this.inflightTxns > 0) {
|
|
364
|
+
let t = Date.now() + e;
|
|
365
|
+
for (; this.inflightTxns > 0 && Date.now() < t;) await new Promise((e) => setTimeout(e, 25));
|
|
366
|
+
this.inflightTxns > 0 && L.warn("close: grace period expired with in-flight transactions — forcing dispose", {
|
|
367
|
+
gracePeriodMs: e,
|
|
368
|
+
inflight: this.inflightTxns
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
this.cdcFiber &&= (await this.runtime.runPromise(o.interrupt(this.cdcFiber)), null), await this.runtime.dispose();
|
|
372
|
+
}
|
|
373
|
+
async ping() {
|
|
374
|
+
await this.runtime.runPromise(this.sql`SELECT 1`);
|
|
375
|
+
}
|
|
376
|
+
async startCdcConsumer() {
|
|
377
|
+
let e = (await this.runtime.runPromise(t.PgClient)).listen(this.cdcChannel), n = (e) => {
|
|
378
|
+
try {
|
|
379
|
+
let t = JSON.parse(e);
|
|
380
|
+
this.injectExternalChange(t);
|
|
381
|
+
} catch (e) {
|
|
382
|
+
L.warn("cdc: bad payload", { channel: this.cdcChannel }, e);
|
|
383
|
+
}
|
|
384
|
+
};
|
|
385
|
+
this.cdcFiber = this.runtime.runFork(e.pipe(f.runForEach((e) => i.sync(() => n(e)))));
|
|
386
|
+
}
|
|
387
|
+
}, B = class {
|
|
388
|
+
parent;
|
|
389
|
+
txn;
|
|
390
|
+
events = [];
|
|
391
|
+
committed = !1;
|
|
392
|
+
constructor(e, t) {
|
|
393
|
+
this.parent = e, this.txn = t;
|
|
394
|
+
}
|
|
395
|
+
query(e) {
|
|
396
|
+
return this.parent.getInternalRunWithEager()(e, this.txn);
|
|
397
|
+
}
|
|
398
|
+
insert(e, t) {
|
|
399
|
+
return this.parent.getInternalExecuteInsert()(e, t, this.txn, this.events);
|
|
400
|
+
}
|
|
401
|
+
insertMany(e, t) {
|
|
402
|
+
return this.parent.getInternalExecuteInsertMany()(e, t, this.txn, this.events);
|
|
403
|
+
}
|
|
404
|
+
patchJson(e, t, n, r) {
|
|
405
|
+
return this.parent.getInternalExecutePatchJson()(e, t, n, r, this.txn, this.events);
|
|
406
|
+
}
|
|
407
|
+
update(e, t, n) {
|
|
408
|
+
return this.parent.getInternalExecuteUpdate()(e, t, n, this.txn, this.events);
|
|
409
|
+
}
|
|
410
|
+
delete(e, t) {
|
|
411
|
+
return this.parent.getInternalExecuteDelete()(e, t, this.txn, this.events);
|
|
412
|
+
}
|
|
413
|
+
updateMany(e, t, n) {
|
|
414
|
+
return this.parent.getInternalExecuteUpdateMany()(e, t, n, this.txn, this.events);
|
|
415
|
+
}
|
|
416
|
+
deleteMany(e, t) {
|
|
417
|
+
return this.parent.getInternalExecuteDeleteMany()(e, t, this.txn, this.events);
|
|
418
|
+
}
|
|
419
|
+
upsert(e, t, n) {
|
|
420
|
+
return this.parent.getInternalExecuteUpsert()(e, t, n, this.txn, this.events);
|
|
421
|
+
}
|
|
422
|
+
insertIgnore(e, t, n) {
|
|
423
|
+
return this.parent.getInternalExecuteInsertIgnore()(e, t, n, this.txn, this.events);
|
|
424
|
+
}
|
|
425
|
+
transactional(e) {
|
|
426
|
+
return Promise.reject(/* @__PURE__ */ Error("PostgresDataStore.transactional: nested transactions are not supported. Mutations should execute a single top-level transaction."));
|
|
427
|
+
}
|
|
428
|
+
onChange(e) {
|
|
429
|
+
throw Error("PostgresTransactionalView.onChange: subscribing from inside a transaction is not supported. Subscribe on the parent store outside `transactional()`.");
|
|
430
|
+
}
|
|
431
|
+
injectExternalChange(e) {
|
|
432
|
+
throw Error("PostgresTransactionalView.injectExternalChange: injecting external changes from inside a transaction is not supported. Inject on the parent store outside `transactional()`.");
|
|
433
|
+
}
|
|
434
|
+
commitEvents() {
|
|
435
|
+
if (!this.committed) {
|
|
436
|
+
this.committed = !0;
|
|
437
|
+
for (let e of this.events) this.parent.emitChange(e);
|
|
438
|
+
this.events.length = 0;
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
}, V = class {
|
|
442
|
+
parent;
|
|
443
|
+
namespace;
|
|
444
|
+
constructor(e, t) {
|
|
445
|
+
this.parent = e, this.namespace = t;
|
|
446
|
+
}
|
|
447
|
+
query(e) {
|
|
448
|
+
return this.parent.runInNamespace(this.namespace, (t) => t.query(e));
|
|
449
|
+
}
|
|
450
|
+
insert(e, t) {
|
|
451
|
+
return this.parent.runInNamespace(this.namespace, (n) => n.insert(e, t));
|
|
452
|
+
}
|
|
453
|
+
insertMany(e, t) {
|
|
454
|
+
return this.parent.runInNamespace(this.namespace, (n) => n.insertMany(e, t));
|
|
455
|
+
}
|
|
456
|
+
patchJson(e, t, n, r) {
|
|
457
|
+
return this.parent.runInNamespace(this.namespace, (i) => i.patchJson(e, t, n, r));
|
|
458
|
+
}
|
|
459
|
+
update(e, t, n) {
|
|
460
|
+
return this.parent.runInNamespace(this.namespace, (r) => r.update(e, t, n));
|
|
461
|
+
}
|
|
462
|
+
delete(e, t) {
|
|
463
|
+
return this.parent.runInNamespace(this.namespace, (n) => n.delete(e, t));
|
|
464
|
+
}
|
|
465
|
+
updateMany(e, t, n) {
|
|
466
|
+
return this.parent.runInNamespace(this.namespace, (r) => r.updateMany(e, t, n));
|
|
467
|
+
}
|
|
468
|
+
deleteMany(e, t) {
|
|
469
|
+
return this.parent.runInNamespace(this.namespace, (n) => n.deleteMany(e, t));
|
|
470
|
+
}
|
|
471
|
+
upsert(e, t, n) {
|
|
472
|
+
return this.parent.runInNamespace(this.namespace, (r) => r.upsert(e, t, n));
|
|
473
|
+
}
|
|
474
|
+
insertIgnore(e, t, n) {
|
|
475
|
+
return this.parent.runInNamespace(this.namespace, (r) => r.insertIgnore(e, t, n));
|
|
476
|
+
}
|
|
477
|
+
transactional(e) {
|
|
478
|
+
return this.parent.runInNamespace(this.namespace, e);
|
|
479
|
+
}
|
|
480
|
+
withNamespace(e) {
|
|
481
|
+
return this.parent.withNamespace(e);
|
|
482
|
+
}
|
|
483
|
+
onChange(e) {
|
|
484
|
+
return this.parent.onChange(e);
|
|
485
|
+
}
|
|
486
|
+
injectExternalChange(e) {
|
|
487
|
+
this.parent.injectExternalChange(e);
|
|
488
|
+
}
|
|
489
|
+
raw(e, t) {
|
|
490
|
+
return this.parent.runInNamespace(this.namespace, (n) => {
|
|
491
|
+
let r = n.raw;
|
|
492
|
+
return r === void 0 ? Promise.reject(/* @__PURE__ */ Error("PostgresNamespaceView.raw: underlying store has no raw()")) : r(e, t);
|
|
493
|
+
});
|
|
494
|
+
}
|
|
495
|
+
}, H = (e) => e.__postgresReplicationFriend ?? null, U = (e, t) => {
|
|
496
|
+
let [n, r] = e.split("/"), [i, a] = t.split("/");
|
|
497
|
+
if (!n || !r || !i || !a) throw Error(`postgres LSN compare: invalid format (${e} vs ${t})`);
|
|
498
|
+
let o = parseInt(n, 16), s = parseInt(r, 16), c = parseInt(i, 16);
|
|
499
|
+
return o === c ? s - parseInt(a, 16) : o - c;
|
|
500
|
+
}, W = () => ({
|
|
501
|
+
async capturePrimaryPosition(e) {
|
|
502
|
+
let t = H(e);
|
|
503
|
+
if (t === null) throw Error("postgresReplicationAdapter: primary is not a PostgresDataStore (missing __postgresReplicationFriend). Pass the postgres store directly.");
|
|
504
|
+
return t.runEffect(i.gen(function* () {
|
|
505
|
+
let e = (yield* (yield* g)`SELECT pg_current_wal_lsn()::text AS lsn`)[0]?.lsn;
|
|
506
|
+
return typeof e == "string" ? e : yield* i.die("postgres did not return a WAL LSN");
|
|
507
|
+
}));
|
|
508
|
+
},
|
|
509
|
+
async probeReplicaPosition(e) {
|
|
510
|
+
let t = H(e);
|
|
511
|
+
if (t === null) throw Error("postgresReplicationAdapter: replica is not a PostgresDataStore.");
|
|
512
|
+
return t.runEffect(i.gen(function* () {
|
|
513
|
+
let e = (yield* (yield* g)`
|
|
514
|
+
SELECT COALESCE(pg_last_wal_replay_lsn()::text, pg_current_wal_lsn()::text) AS lsn
|
|
515
|
+
`)[0]?.lsn;
|
|
516
|
+
return typeof e == "string" ? e : yield* i.die("postgres did not return a replay LSN");
|
|
517
|
+
}));
|
|
518
|
+
},
|
|
519
|
+
compare(e, t) {
|
|
520
|
+
return U(t, e) >= 0 ? "caught-up" : "behind";
|
|
521
|
+
}
|
|
522
|
+
}), G = {
|
|
523
|
+
id: "postgres",
|
|
524
|
+
makeSqlLayer: (e) => j(e),
|
|
525
|
+
makeStore: (e) => R(e),
|
|
526
|
+
compileContains: (e, t, n) => n`${e} ILIKE ${`%${t.replace(/[\\%_]/g, (e) => `\\${e}`)}%`} ESCAPE '\\'`,
|
|
527
|
+
retryFilter: F
|
|
528
|
+
};
|
|
529
|
+
//#endregion
|
|
530
|
+
export { e as PgClient, A as connectionFromConfig, R as makePostgresDataStore, O as makePostgresSqlLayer, j as makePostgresSqlLayerFromConfig, G as postgresDialect, W as postgresReplicationAdapter, F as postgresRetryFilter };
|
package/package.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@voltro/sql-postgres",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "PostgreSQL dialect adapter for Voltro's cross-dialect DataStore (LISTEN/NOTIFY reactivity, logical-replication CDC).",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"voltro",
|
|
7
|
+
"typescript",
|
|
8
|
+
"framework"
|
|
9
|
+
],
|
|
10
|
+
"license": "SEE LICENSE IN LICENSE",
|
|
11
|
+
"homepage": "https://voltro.dev",
|
|
12
|
+
"bugs": {
|
|
13
|
+
"email": "support@voltro.dev"
|
|
14
|
+
},
|
|
15
|
+
"author": {
|
|
16
|
+
"name": "Voltro UG",
|
|
17
|
+
"url": "https://voltro.dev"
|
|
18
|
+
},
|
|
19
|
+
"type": "module",
|
|
20
|
+
"exports": {
|
|
21
|
+
".": {
|
|
22
|
+
"types": "./dist/index.d.ts",
|
|
23
|
+
"import": "./dist/index.js",
|
|
24
|
+
"default": "./dist/index.js"
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
"main": "./dist/index.js",
|
|
28
|
+
"module": "./dist/index.js",
|
|
29
|
+
"types": "./dist/index.d.ts",
|
|
30
|
+
"sideEffects": false,
|
|
31
|
+
"engines": {
|
|
32
|
+
"node": ">=24.0.0"
|
|
33
|
+
},
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"@effect/sql": "^0.51.1",
|
|
36
|
+
"@effect/sql-pg": "^0.52.1",
|
|
37
|
+
"@voltro/database": "0.1.0",
|
|
38
|
+
"@voltro/logger": "0.1.0",
|
|
39
|
+
"pg": "^8.22.0"
|
|
40
|
+
},
|
|
41
|
+
"peerDependencies": {
|
|
42
|
+
"effect": "^3.21.4"
|
|
43
|
+
},
|
|
44
|
+
"publishConfig": {
|
|
45
|
+
"access": "public"
|
|
46
|
+
}
|
|
47
|
+
}
|