@voltro/sql-mssql 0.85.0 → 0.87.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/dist/store.js ADDED
@@ -0,0 +1,576 @@
1
+ import { writeOutput as e } from "./writeOutput.js";
2
+ import { isDuplicateKeyMssqlFailure as t, isRetryableMssqlFailure as n } from "./retry.js";
3
+ import { startChangeTrackingCdc as r } from "./changeTrackingCdc.js";
4
+ import { CDC_OFFSETS_TABLE as i } from "./cdcOffsetsTable.js";
5
+ import { Effect as a, Layer as o, ManagedRuntime as s, Option as c } from "effect";
6
+ import { EagerCardinalityError as l, attachEagerLoads as u, attributionFields as d, bulkInsertLimitsFor as f, chunkRowsForInsert as p, compileEagerJson as m, compileJsonPatch as h, compilePredicate as g, compileRawFragment as _, compileSelect as v, decodeRowsFromSchema as y, decodeSelectRows as b, emitCommittedChanges as x, encodeRowForSchema as S, externalChangeEvent as C, hasEagerLoads as w, isTableReactive as T, jsonPatchAssignments as E, makeEagerFallbackReporter as D, makeTransactionAbortTracker as O, observeDbOp as k, onTransaction as A, qualifyTable as j, quoteIdent as M, raiseChangeListenerCeiling as N, recordsTable as P, requireTable as F, rowKeyColumns as I, runSavepoint as L, runStoreTransaction as R, runWriteRecorders as z, stampGeneratedId as B, stampGeneratedIds as V, upsertUpdatePatch as H, upsertUpdateValues as U, withCapturedAttribution as W, writeImagesObserved as G } from "@voltro/database";
7
+ import { EventEmitter as K } from "node:events";
8
+ import { createLogger as q } from "@voltro/logger";
9
+ import { SqlClient as J } from "effect/sql/SqlClient";
10
+ //#region src/store.ts
11
+ var Y = q({ scope: "voltro:mssql" }), X = async (e) => {
12
+ let t = e.changeStrategy ?? "inline", n = t;
13
+ t === "cdc" && !e.cdcConfig && (Y.warn("changeStrategy='cdc' requires cdcConfig (replicaId + includeTables); falling back to 'inline'."), n = "inline");
14
+ let r = e.tracerLayer ? o.mergeAll(e.sqlLayer, e.tracerLayer) : e.sqlLayer, i = s.make(r), a = new Z(await i.runPromise(J), i, n);
15
+ return n === "cdc" && e.cdcConfig && await a.startCdcConsumer(e.cdcConfig), a;
16
+ }, Z = class o {
17
+ sql;
18
+ runtime;
19
+ changeStrategy;
20
+ namespace;
21
+ emitter;
22
+ inflightTxns = 0;
23
+ reportEagerFallback = D(Y);
24
+ cdcHandle = null;
25
+ cdcCheckpointTimer = null;
26
+ cdcPendingVersion = null;
27
+ cdcReplicaId = "";
28
+ cdcStreamName = "default";
29
+ constructor(e, t, n = "inline", r = null, i) {
30
+ this.sql = e, this.runtime = t, this.changeStrategy = n, this.namespace = r, this.emitter = i ?? new K(), N(this.emitter);
31
+ }
32
+ get inlineEmit() {
33
+ return this.changeStrategy === "inline";
34
+ }
35
+ withNamespace(e) {
36
+ return e === this.namespace ? this : new o(this.sql, this.runtime, this.changeStrategy, e, this.emitter);
37
+ }
38
+ nsT(e) {
39
+ return j(this.namespace, e);
40
+ }
41
+ __mssqlReplicationFriend = { runEffect: (e) => this.runtime.runPromise(e) };
42
+ async executeQuery(e, t) {
43
+ let n = v(e, this.sql, this.namespace), r = t ? a.provideService(n, this.sql.transactionService, t) : n, i = await k("mssql", "select", () => this.runtime.runPromise(r));
44
+ return b(i, e, "mssql");
45
+ }
46
+ decodeWritten(e, t) {
47
+ return y([t], e, "mssql")[0] ?? t;
48
+ }
49
+ async executeInsert(t, n, r, i, o) {
50
+ n = B(t, n);
51
+ let s = this.sql, c = e(s, this.nsT(t), (e) => s`INSERT INTO ${s(this.nsT(t))} ${s.insert(S(n, t)).returning(s`INSERTED.* INTO ${e}`)}`), l = r ? a.provideService(c, this.sql.transactionService, r) : c, u = (await this.runtime.runPromise(l))[0];
52
+ if (!u) throw Error(`MssqlStore.insert: row not returned post-insert in '${t}'`);
53
+ let d = this.decodeWritten(t, u);
54
+ return await this.routeEvent({
55
+ table: t,
56
+ op: "insert",
57
+ old: null,
58
+ new: d
59
+ }, i, r, o), d;
60
+ }
61
+ async executeUpdate(t, n, r, i, o, s) {
62
+ let c = this.sql, l = e(c, this.nsT(t), (e) => c`UPDATE ${c(this.nsT(t))} SET ${c.update(S(r, t)).returning(c`INSERTED.* INTO ${e}`)} WHERE ${c.and(I(n).map(([e, t]) => c`${c(e)} = ${t}`))}`), u = i ? a.provideService(l, this.sql.transactionService, i) : l, d = (await this.runtime.runPromise(u))[0];
63
+ if (!d) return null;
64
+ let f = this.decodeWritten(t, d);
65
+ return await this.routeEvent({
66
+ table: t,
67
+ op: "update",
68
+ old: null,
69
+ new: f
70
+ }, o, i, s), f;
71
+ }
72
+ async executeInsertMany(t, n, r, i, o, s) {
73
+ if (n = V(t, n), n.length === 0) return [];
74
+ let c = this.sql, l = p(n, f("mssql")), u = s?.returning === "none", d = u && !G(t), m = (n) => {
75
+ let r = c.insert(n.map((e) => S(e, t)));
76
+ return d ? a.as(c`INSERT INTO ${c(this.nsT(t))} ${r}`, []) : e(c, this.nsT(t), (e) => c`INSERT INTO ${c(this.nsT(t))} ${r.returning(c`INSERTED.* INTO ${e}`)}`);
77
+ }, h;
78
+ if (l.length === 1) {
79
+ let e = m(l[0]), t = r ? a.provideService(e, this.sql.transactionService, r) : e;
80
+ h = await this.runtime.runPromise(t);
81
+ } else if (r) {
82
+ let e = [];
83
+ for (let t of l) e.push(...await this.runtime.runPromise(a.provideService(m(t), this.sql.transactionService, r)));
84
+ h = e;
85
+ } else h = await this.runtime.runPromise(c.withTransaction(a.map(a.forEach(l, m, { concurrency: 1 }), (e) => e.flat())));
86
+ if (d) return [];
87
+ let g = n.map((e) => e.id), _ = y(h, t, "mssql"), v = new Map(_.map((e) => [e.id, e])), b = g.map((e) => v.get(e)).filter((e) => e !== void 0), x = b.length === h.length ? b : _;
88
+ for (let e of x) await this.routeEvent({
89
+ table: t,
90
+ op: "insert",
91
+ old: null,
92
+ new: e
93
+ }, i, r, o);
94
+ return u ? [] : x;
95
+ }
96
+ async executePatchJson(t, n, r, i, o, s, c, l) {
97
+ let u = this.sql, d = S(E(n, r, l), t), f = u.csv([h(u, "mssql", r, i), ...Object.entries(d).map(([e, t]) => u`${u(e)} = ${t}`)]), p = e(u, this.nsT(t), (e) => u`UPDATE ${u(this.nsT(t))} SET ${f} OUTPUT INSERTED.* INTO ${e} WHERE ${u.and(I(n).map(([e, t]) => u`${u(e)} = ${t}`))}`), m = o ? a.provideService(p, this.sql.transactionService, o) : p, g = (await this.runtime.runPromise(m))[0];
98
+ if (!g) return null;
99
+ let _ = this.decodeWritten(t, g);
100
+ return await this.routeEvent({
101
+ table: t,
102
+ op: "update",
103
+ old: null,
104
+ new: _
105
+ }, s, o, c), _;
106
+ }
107
+ async executeDelete(t, n, r, i, o) {
108
+ let s = this.sql, c = e(s, this.nsT(t), (e) => s`DELETE FROM ${s(this.nsT(t))} OUTPUT DELETED.* INTO ${e} WHERE ${s.and(I(n).map(([e, t]) => s`${s(e)} = ${t}`))}`), l = r ? a.provideService(c, this.sql.transactionService, r) : c, u = (await this.runtime.runPromise(l))[0];
109
+ if (!u) return !1;
110
+ let d = this.decodeWritten(t, u);
111
+ return await this.routeEvent({
112
+ table: t,
113
+ op: "delete",
114
+ old: d,
115
+ new: null
116
+ }, i, r, o), !0;
117
+ }
118
+ async appendInTxn(e, t, n) {
119
+ let r = this.sql, i = r`INSERT INTO ${r(this.nsT(e))} ${r.insert(S(t, e))}`;
120
+ await this.runtime.runPromise(n ? a.provideService(i, this.sql.transactionService, n) : i);
121
+ }
122
+ async maxInTxn(e, t, n, r) {
123
+ let i = this.sql, o = Object.entries(n).map(([e, t]) => i`${i(e)} = ${t}`), s = i`SELECT MAX(${i(t)}) AS ${i("m")} FROM ${i(this.nsT(e))} WHERE ${i.and(o)}`, c = (await this.runtime.runPromise(r ? a.provideService(s, this.sql.transactionService, r) : s))[0]?.m;
124
+ return c == null ? null : Number(c);
125
+ }
126
+ async routeEvent(e, t, n = null, r) {
127
+ e = {
128
+ ...d(r),
129
+ ...e
130
+ }, P(e.table) && await z({
131
+ append: (e, t) => this.appendInTxn(e, t, n),
132
+ maxOf: (e, t, r) => this.maxInTxn(e, t, r, n)
133
+ }, {
134
+ table: e.table,
135
+ op: e.op,
136
+ next: e.new,
137
+ prev: e.old,
138
+ traceId: e.traceId,
139
+ subjectId: e.subjectId
140
+ }), T(e.table) && (t === null ? this.inlineEmit && this.emitter.emit("change", e) : t.push(e));
141
+ }
142
+ integrityQuery(e) {
143
+ return this.query(e);
144
+ }
145
+ query(e) {
146
+ return this.runWithEager(e, null);
147
+ }
148
+ raw(e, t) {
149
+ return this.rawOn(e, null);
150
+ }
151
+ rawOn(e, t) {
152
+ let n = _(e, this.sql), r = t ? a.provideService(n, this.sql.transactionService, t) : n;
153
+ return k("mssql", "raw", () => this.runtime.runPromise(r));
154
+ }
155
+ async runWithEager(e, t) {
156
+ if (!w(e)) return this.executeQuery(e, t);
157
+ let n = this.namespace === null ? m(e, this.sql, "mssql") : null;
158
+ if (n !== null) try {
159
+ let e = t ? a.provideService(n.fragment, this.sql.transactionService, t) : n.fragment, r = await k("mssql", "select", () => this.runtime.runPromise(e));
160
+ return n.decode(r);
161
+ } catch (t) {
162
+ if (t instanceof l) throw t;
163
+ this.reportEagerFallback({
164
+ dialect: "mssql",
165
+ table: e.table,
166
+ reason: "execute-failed",
167
+ error: t
168
+ });
169
+ }
170
+ else this.reportEagerFallback({
171
+ dialect: "mssql",
172
+ table: e.table,
173
+ reason: "not-compilable"
174
+ });
175
+ let r = await this.executeQuery(e, t);
176
+ return u(r, e.eager, e.sourceTable ?? F(e.table), (e) => this.executeQuery(e, t));
177
+ }
178
+ getInternalRunWithEager() {
179
+ return this.runWithEager.bind(this);
180
+ }
181
+ localWrite(e, t, n) {
182
+ let r = (e) => P(t) ? this.writeWithRecorders(n, e) : n(e, null, null);
183
+ return k("mssql", e, () => W(r));
184
+ }
185
+ async writeWithRecorders(e, t) {
186
+ let n = [], r = this.sql, i = await this.runtime.runPromise(r.withTransaction(a.flatMap(a.serviceOption(this.sql.transactionService), (r) => c.isNone(r) ? a.fail(/* @__PURE__ */ Error("MssqlStore.write: TransactionConnection missing.")) : a.tryPromise({
187
+ try: () => e(t, r.value, n),
188
+ catch: (e) => e
189
+ }))));
190
+ return x(n, (e) => this.emitChange(e)), i;
191
+ }
192
+ insert(e, t) {
193
+ return this.localWrite("insert", e, (n, r, i) => this.executeInsert(e, t, r, i, n));
194
+ }
195
+ insertMany(e, t, n) {
196
+ return this.localWrite("insert", e, (r, i, a) => this.executeInsertMany(e, t, i, a, r, n));
197
+ }
198
+ patchJson(e, t, n, r, i) {
199
+ return this.localWrite("update", e, (a, o, s) => this.executePatchJson(e, t, n, r, o, s, a, i));
200
+ }
201
+ update(e, t, n) {
202
+ return this.localWrite("update", e, (r, i, a) => this.executeUpdate(e, t, n, i, a, r));
203
+ }
204
+ delete(e, t) {
205
+ return this.localWrite("delete", e, (n, r, i) => this.executeDelete(e, t, r, i, n));
206
+ }
207
+ updateMany(e, t, n) {
208
+ return this.executeUpdateMany(e, t, n, null, null);
209
+ }
210
+ deleteMany(e, t) {
211
+ return this.executeDeleteMany(e, t, null, null);
212
+ }
213
+ async executeUpdateMany(t, n, r, i, o, s) {
214
+ let c = this.sql, l = g(r.where, c, this.namespace), u = c.update(S(n, t));
215
+ if (!G(t)) return this.countWritten(c`UPDATE ${c(this.nsT(t))} SET ${u} WHERE ${l}`, "update", i);
216
+ let d = e(c, this.nsT(t), (e) => c`UPDATE ${c(this.nsT(t))} SET ${u} OUTPUT INSERTED.* INTO ${e} WHERE ${l}`), f = i ? a.provideService(d, this.sql.transactionService, i) : d, p = await k("mssql", "update", () => this.runtime.runPromise(f));
217
+ for (let e of y(p, t, "mssql")) await this.routeEvent({
218
+ table: t,
219
+ op: "update",
220
+ old: null,
221
+ new: e
222
+ }, o, i, s);
223
+ return p.length;
224
+ }
225
+ async countWritten(e, t, n) {
226
+ let r = this.sql`${e}; SELECT @@ROWCOUNT AS n`, i = n ? a.provideService(r, this.sql.transactionService, n) : r, [o] = await k("mssql", t, () => this.runtime.runPromise(i));
227
+ return Number(o?.n ?? 0);
228
+ }
229
+ async executeDeleteMany(t, n, r, i, o) {
230
+ let s = this.sql, c = g(n.where, s, this.namespace);
231
+ if (!G(t)) return this.countWritten(s`DELETE FROM ${s(this.nsT(t))} WHERE ${c}`, "delete", r);
232
+ let l = e(s, this.nsT(t), (e) => s`DELETE FROM ${s(this.nsT(t))} OUTPUT DELETED.* INTO ${e} WHERE ${c}`), u = r ? a.provideService(l, this.sql.transactionService, r) : l, d = await k("mssql", "delete", () => this.runtime.runPromise(u));
233
+ for (let e of y(d, t, "mssql")) await this.routeEvent({
234
+ table: t,
235
+ op: "delete",
236
+ old: e,
237
+ new: null
238
+ }, i, r, o);
239
+ return d.length;
240
+ }
241
+ upsert(e, t, n) {
242
+ return this.localWrite("upsert", e, (r, i, a) => this.executeUpsert(e, t, n, i, a, r));
243
+ }
244
+ insertIgnore(e, t, n) {
245
+ return this.insertIgnoreWithOutcome(e, t, n).then((e) => e.row);
246
+ }
247
+ insertIgnoreWithOutcome(e, t, n) {
248
+ return this.localWrite("upsert", e, (r, i, a) => this.executeInsertIgnore(e, t, n, i, a, r));
249
+ }
250
+ async executeUpsert(e, n, r, i, a, o) {
251
+ if (U(r), n = B(e, n), typeof r.update != "function" && r.conflictColumns.length > 0) return this.mergeUpsert(e, n, {
252
+ conflictColumns: r.conflictColumns,
253
+ ...r.update === void 0 ? {} : { update: r.update },
254
+ ...r.updateValues === void 0 ? {} : { updateValues: r.updateValues }
255
+ }, i, a, o);
256
+ let s = await this.findByConflict(e, n, r.conflictColumns, i);
257
+ if (s) return await this.executeUpdate(e, Object.fromEntries(r.conflictColumns.map((e) => [e, s[e]])), H(n, s, r), i, a, o) ?? s;
258
+ try {
259
+ return await this.executeInsert(e, n, i, a, o);
260
+ } catch (s) {
261
+ if (!t(s)) throw s;
262
+ let c = await this.findByConflict(e, n, r.conflictColumns, i);
263
+ if (!c) throw s;
264
+ return await this.executeUpdate(e, Object.fromEntries(r.conflictColumns.map((e) => [e, c[e]])), H(n, c, r), i, a, o) ?? c;
265
+ }
266
+ }
267
+ async mergeUpsert(t, n, r, i, o, s) {
268
+ let c = this.sql, l = Object.keys(n), u = Array.isArray(r.update) ? r.update.filter((e) => l.includes(e)) : l.filter((e) => e !== "id" && !r.conflictColumns.includes(e)), d = S(n, t), f = l.map((e) => c`${d[e]}`), p = l.map((e) => c`${c(e)}`), m = r.conflictColumns.map((e) => c`tgt.${c(e)} = src.${c(e)}`), h = l.map((e) => c`${c(e)}`), g = l.map((e) => c`src.${c(e)}`), _ = U(r), v = S(_, t), y = [...u.filter((e) => !Object.hasOwn(_, e)).map((e) => c`tgt.${c(e)} = src.${c(e)}`), ...Object.entries(v).map(([e, t]) => c`tgt.${c(e)} = ${t}`)];
269
+ y.length === 0 && y.push(c`tgt.${c(r.conflictColumns[0])} = src.${c(r.conflictColumns[0])}`);
270
+ let b = e(c, this.nsT(t), (e) => c`
271
+ MERGE ${c(this.nsT(t))} WITH (HOLDLOCK) AS tgt
272
+ USING (VALUES (${c.csv(f)})) AS src (${c.csv(p)})
273
+ ON ${c.and(m)}
274
+ WHEN MATCHED THEN UPDATE SET ${c.csv(y)}
275
+ WHEN NOT MATCHED THEN INSERT (${c.csv(h)}) VALUES (${c.csv(g)})
276
+ OUTPUT $action AS ${c("__action")}, INSERTED.* INTO ${e};`, !0), x = i ? a.provideService(b, this.sql.transactionService, i) : b, C = (await this.runtime.runPromise(x))[0];
277
+ if (!C) throw Error(`MssqlStore.upsert: MERGE returned no row for '${t}'`);
278
+ let w = C.__action, { __action: T, ...E } = C, D = this.decodeWritten(t, E);
279
+ return await this.routeEvent({
280
+ table: t,
281
+ op: w === "INSERT" ? "insert" : "update",
282
+ old: null,
283
+ new: D
284
+ }, o, i, s), D;
285
+ }
286
+ async executeInsertIgnore(e, n, r, i, a, o) {
287
+ n = B(e, n);
288
+ let s = await this.findByConflict(e, n, r.conflictColumns, i);
289
+ if (s) return {
290
+ row: s,
291
+ outcome: "ignored"
292
+ };
293
+ try {
294
+ return {
295
+ row: await this.executeInsert(e, n, i, a, o),
296
+ outcome: "inserted"
297
+ };
298
+ } catch (a) {
299
+ if (!t(a)) throw a;
300
+ let o = await this.findByConflict(e, n, r.conflictColumns, i);
301
+ if (!o) throw a;
302
+ return {
303
+ row: o,
304
+ outcome: "ignored"
305
+ };
306
+ }
307
+ }
308
+ async findByConflict(e, t, n, r) {
309
+ if (n.length === 0) return;
310
+ let i = this.sql, o = n.map((e) => i`${i(e)} = ${t[e]}`), s = i`SELECT TOP 1 * FROM ${i(this.nsT(e))} WHERE ${i.and(o)}`, c = r ? a.provideService(s, this.sql.transactionService, r) : s, l = (await this.runtime.runPromise(c))[0];
311
+ return l ? this.decodeWritten(e, l) : void 0;
312
+ }
313
+ emitChange(e) {
314
+ this.emitter.emit("change", e);
315
+ }
316
+ async startCdcConsumer(e) {
317
+ if (this.cdcHandle) return;
318
+ this.cdcReplicaId = e.replicaId;
319
+ let t = e.includeTables ?? [];
320
+ t.length === 0 && Y.warn("cdc: no reactive tables to tail; Change Tracking reader idle. Set includeTables to enable.");
321
+ let n = await this.readCdcOffset(e.replicaId);
322
+ this.cdcHandle = await r({
323
+ run: this.__mssqlReplicationFriend.runEffect,
324
+ tables: t,
325
+ startVersion: n,
326
+ onChange: (e) => this.injectExternalChange(e),
327
+ onVersion: (e) => {
328
+ this.cdcPendingVersion = e;
329
+ },
330
+ onError: (e) => Y.warn("cdc: consumer error", {}, e),
331
+ onResync: () => {
332
+ Y.warn("cdc: resynced to current CT version after retention gap — subscriptions self-heal on the next change");
333
+ }
334
+ }), this.cdcCheckpointTimer = setInterval(() => {
335
+ this.flushCdcOffset();
336
+ }, 1e3), this.cdcCheckpointTimer.unref && this.cdcCheckpointTimer.unref();
337
+ }
338
+ async readCdcOffset(e) {
339
+ try {
340
+ let t = this.sql, n = (await this.runtime.runPromise(t`
341
+ SELECT TOP 1 ${t("ctVersion")} FROM ${t(i)}
342
+ WHERE ${t("id")} = ${e}`))[0]?.ctVersion;
343
+ return n ? (Y.info("cdc: resuming from persisted CT version", {
344
+ replicaId: e,
345
+ version: n
346
+ }), n) : null;
347
+ } catch (t) {
348
+ return Y.warn("cdc: no readable offset; starting at current CT version", { replicaId: e }, t), null;
349
+ }
350
+ }
351
+ async flushCdcOffset() {
352
+ let e = this.cdcPendingVersion;
353
+ if (e !== null) {
354
+ this.cdcPendingVersion = null;
355
+ try {
356
+ let t = this.cdcReplicaId;
357
+ await this.executeUpsert(i, {
358
+ id: t,
359
+ replicaId: t,
360
+ streamName: this.cdcStreamName,
361
+ ctVersion: e,
362
+ updatedAt: /* @__PURE__ */ new Date()
363
+ }, {
364
+ conflictColumns: ["id"],
365
+ update: ["ctVersion", "updatedAt"]
366
+ }, null, null);
367
+ } catch (t) {
368
+ this.cdcPendingVersion = e, Y.warn("cdc: checkpoint write failed", {}, t);
369
+ }
370
+ }
371
+ }
372
+ getInternalExecuteQuery() {
373
+ return this.executeQuery.bind(this);
374
+ }
375
+ getInternalExecuteInsert() {
376
+ return this.executeInsert.bind(this);
377
+ }
378
+ getInternalExecuteInsertMany() {
379
+ return this.executeInsertMany.bind(this);
380
+ }
381
+ getInternalExecutePatchJson() {
382
+ return this.executePatchJson.bind(this);
383
+ }
384
+ getInternalExecuteUpdate() {
385
+ return this.executeUpdate.bind(this);
386
+ }
387
+ getInternalExecuteUpdateMany() {
388
+ return this.executeUpdateMany.bind(this);
389
+ }
390
+ getInternalExecuteDeleteMany() {
391
+ return this.executeDeleteMany.bind(this);
392
+ }
393
+ getInternalExecuteDelete() {
394
+ return this.executeDelete.bind(this);
395
+ }
396
+ getInternalExecuteUpsert() {
397
+ return this.executeUpsert.bind(this);
398
+ }
399
+ getInternalExecuteInsertIgnore() {
400
+ return this.executeInsertIgnore.bind(this);
401
+ }
402
+ async emptyTables(e) {
403
+ await this.emptyTablesOn(e, null);
404
+ }
405
+ async incomingForeignKeys(e) {
406
+ if (e.length === 0) return [];
407
+ let t = this.sql;
408
+ return (await this.runtime.runPromise(t`
409
+ SELECT child.name AS fromTable, col.name AS columnName, parent.name AS toTable
410
+ FROM sys.foreign_key_columns fkc
411
+ JOIN sys.tables child ON child.object_id = fkc.parent_object_id
412
+ JOIN sys.tables parent ON parent.object_id = fkc.referenced_object_id
413
+ JOIN sys.columns col ON col.object_id = fkc.parent_object_id AND col.column_id = fkc.parent_column_id
414
+ WHERE parent.name IN ${t.in(e)}
415
+ AND child.name NOT IN ${t.in(e)}`)).map((e) => ({
416
+ from: e.fromTable,
417
+ column: e.columnName,
418
+ to: e.toTable
419
+ }));
420
+ }
421
+ async emptyTablesOn(e, t) {
422
+ if (e.length === 0) return;
423
+ let n = this.sql, r = (e) => this.nsT(e), i = (e, t) => n.unsafe(`ALTER TABLE ${M(r(e), "mssql")} ${t ? "WITH CHECK CHECK" : "NOCHECK"} CONSTRAINT ALL`), o = a.acquireUseRelease(a.forEach(e, (e) => i(e, !1), { discard: !0 }), () => a.forEach(e, (e) => n`DELETE FROM ${n(r(e))}`, { discard: !0 }), () => a.orDie(a.ignore(a.forEach(e, (e) => i(e, !0), { discard: !0 })))), s = t === null ? n.withTransaction(o) : a.provideService(o, this.sql.transactionService, t);
424
+ await this.runtime.runPromise(s);
425
+ }
426
+ async transactional(e) {
427
+ this.inflightTxns++;
428
+ try {
429
+ return await R({
430
+ ...this.txnSpec("MssqlStore.transactional"),
431
+ work: e,
432
+ makeView: (e, t) => new Q(this, e, t)
433
+ });
434
+ } finally {
435
+ this.inflightTxns--;
436
+ }
437
+ }
438
+ get transactionService() {
439
+ return this.sql.transactionService;
440
+ }
441
+ txnSpec(e) {
442
+ return {
443
+ label: e,
444
+ dialect: "mssql",
445
+ withTransaction: (e) => this.sql.withTransaction(e),
446
+ transactionService: this.sql.transactionService,
447
+ runPromiseExit: (e) => this.runtime.runPromiseExit(e),
448
+ isRetryable: n,
449
+ span: {
450
+ name: "store.transactional",
451
+ attributes: {
452
+ "db.system": "mssql",
453
+ "db.operation": "transaction"
454
+ }
455
+ }
456
+ };
457
+ }
458
+ onChange(e) {
459
+ return this.emitter.on("change", e), () => {
460
+ this.emitter.off("change", e);
461
+ };
462
+ }
463
+ get changeScope() {
464
+ return this.changeStrategy === "cdc" ? "fleet" : "local";
465
+ }
466
+ injectExternalChange(e) {
467
+ T(e.table) && this.emitter.emit("change", C(e));
468
+ }
469
+ run(e) {
470
+ return this.runtime.runPromise(e);
471
+ }
472
+ async close(e = 5e3) {
473
+ if (this.cdcCheckpointTimer &&= (clearInterval(this.cdcCheckpointTimer), null), this.cdcHandle) {
474
+ let e = this.cdcHandle.currentVersion();
475
+ e !== null && (this.cdcPendingVersion = e), await this.flushCdcOffset(), await this.cdcHandle.stop(), this.cdcHandle = null;
476
+ }
477
+ if (this.inflightTxns > 0) {
478
+ let t = Date.now() + e;
479
+ for (; this.inflightTxns > 0 && Date.now() < t;) await new Promise((e) => setTimeout(e, 25));
480
+ this.inflightTxns > 0 && Y.warn("close: grace period expired with in-flight transactions", {
481
+ gracePeriodMs: e,
482
+ inflight: this.inflightTxns
483
+ });
484
+ }
485
+ await this.runtime.dispose();
486
+ }
487
+ async ping() {
488
+ await this.runtime.runPromise(this.sql`SELECT 1`);
489
+ }
490
+ }, Q = class {
491
+ parent;
492
+ txn;
493
+ attr;
494
+ events = [];
495
+ aborts = O((e) => n(e));
496
+ committed = !1;
497
+ constructor(e, t, n = void 0) {
498
+ this.parent = e, this.txn = t, this.attr = n;
499
+ }
500
+ async savepoint(e) {
501
+ let t = this.events.length;
502
+ try {
503
+ return await L({
504
+ tracker: this.aborts,
505
+ name: `voltro_sp_${++this.savepointCounter}`,
506
+ spelling: "mssql",
507
+ exec: (e) => this.parent.run(a.provideService(J.pipe(a.flatMap((t) => t.unsafe(e))), this.parent.transactionService, this.txn))
508
+ }, e);
509
+ } catch (e) {
510
+ throw this.events.length = t, e;
511
+ }
512
+ }
513
+ savepointCounter = 0;
514
+ run(e) {
515
+ return this.aborts.track(() => this.parent.run(A(e, this.parent.transactionService, this.txn)));
516
+ }
517
+ abortedBy() {
518
+ return this.aborts.abortedBy();
519
+ }
520
+ raw(e, t) {
521
+ return this.aborts.track(() => this.parent.rawOn(e, this.txn));
522
+ }
523
+ integrityQuery(e) {
524
+ return this.query(e);
525
+ }
526
+ query(e) {
527
+ return this.aborts.track(() => this.parent.getInternalRunWithEager()(e, this.txn));
528
+ }
529
+ emptyTables(e) {
530
+ return this.aborts.track(() => this.parent.emptyTablesOn(e, this.txn));
531
+ }
532
+ insert(e, t) {
533
+ return this.aborts.track(() => this.parent.getInternalExecuteInsert()(e, t, this.txn, this.events, this.attr));
534
+ }
535
+ insertMany(e, t, n) {
536
+ return this.aborts.track(() => this.parent.getInternalExecuteInsertMany()(e, t, this.txn, this.events, this.attr, n));
537
+ }
538
+ patchJson(e, t, n, r, i) {
539
+ return this.aborts.track(() => this.parent.getInternalExecutePatchJson()(e, t, n, r, this.txn, this.events, this.attr, i));
540
+ }
541
+ update(e, t, n) {
542
+ return this.aborts.track(() => this.parent.getInternalExecuteUpdate()(e, t, n, this.txn, this.events, this.attr));
543
+ }
544
+ delete(e, t) {
545
+ return this.aborts.track(() => this.parent.getInternalExecuteDelete()(e, t, this.txn, this.events, this.attr));
546
+ }
547
+ updateMany(e, t, n) {
548
+ return this.aborts.track(() => this.parent.getInternalExecuteUpdateMany()(e, t, n, this.txn, this.events, this.attr));
549
+ }
550
+ deleteMany(e, t) {
551
+ return this.aborts.track(() => this.parent.getInternalExecuteDeleteMany()(e, t, this.txn, this.events, this.attr));
552
+ }
553
+ upsert(e, t, n) {
554
+ return this.aborts.track(() => this.parent.getInternalExecuteUpsert()(e, t, n, this.txn, this.events, this.attr));
555
+ }
556
+ insertIgnore(e, t, n) {
557
+ return this.insertIgnoreWithOutcome(e, t, n).then((e) => e.row);
558
+ }
559
+ insertIgnoreWithOutcome(e, t, n) {
560
+ return this.aborts.track(() => this.parent.getInternalExecuteInsertIgnore()(e, t, n, this.txn, this.events, this.attr));
561
+ }
562
+ transactional(e) {
563
+ return Promise.reject(/* @__PURE__ */ Error("MssqlStore.transactional: nested transactions are not supported."));
564
+ }
565
+ onChange(e) {
566
+ throw Error("MssqlTransactionalView.onChange: not supported inside a transaction.");
567
+ }
568
+ injectExternalChange(e) {
569
+ throw Error("MssqlTransactionalView.injectExternalChange: not supported inside a transaction.");
570
+ }
571
+ commitEvents() {
572
+ this.committed || (this.committed = !0, x(this.events, (e) => this.parent.emitChange(e)), this.events.length = 0);
573
+ }
574
+ };
575
+ //#endregion
576
+ export { Z as MssqlStore, X as makeMssqlStore };
@@ -0,0 +1,18 @@
1
+ import { mssqlRowResultProjection as e, readMssqlPhysicalColumns as t } from "./rowResultProjection.js";
2
+ import { Effect as n } from "effect";
3
+ //#region src/writeOutput.ts
4
+ var r = (r, i, a, o = !1) => n.uninterruptible(n.gen(function* () {
5
+ let n = r.literal("[#voltro_write_output]"), s = yield* t(r, i), c = r.join(", ", !1)(s.map((e) => {
6
+ let t = r.literal(`[${e.name.replaceAll("]", "]]")}]`);
7
+ return e.system_type_id === 189 ? r`CONVERT(binary(8), ${t}) AS ${t}` : t;
8
+ })), l = e(r, s);
9
+ return yield* r`
10
+ SELECT TOP (0) ${o ? r.literal("CAST(NULL AS nvarchar(10)) AS [__action],") : r.literal("")} source.* INTO ${n}
11
+ FROM (SELECT ${c} FROM ${r(i)} UNION ALL SELECT ${c} FROM ${r(i)}) AS source;
12
+ ${a(n)};
13
+ SELECT ${o ? r.literal("[__action],") : r.literal("")}${l} FROM ${n};
14
+ DROP TABLE ${n};
15
+ `;
16
+ }));
17
+ //#endregion
18
+ export { r as writeOutput };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@voltro/sql-mssql",
3
- "version": "0.85.0",
3
+ "version": "0.87.0",
4
4
  "description": "SQL Server (mssql) dialect adapter for Voltro's cross-dialect DataStore (OUTPUT-clause returning, OFFSET/FETCH paging, inline reactivity).",
5
5
  "keywords": [
6
6
  "voltro",
@@ -34,8 +34,8 @@
34
34
  },
35
35
  "dependencies": {
36
36
  "@effect/sql-mssql": "4.0.1",
37
- "@voltro/database": "0.85.0",
38
- "@voltro/logger": "0.85.0",
37
+ "@voltro/database": "0.87.0",
38
+ "@voltro/logger": "0.87.0",
39
39
  "tedious": "20.3.3"
40
40
  },
41
41
  "peerDependencies": {