@voltro/sql-mssql 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/dist/index.js ADDED
@@ -0,0 +1,618 @@
1
+ import { MssqlClient as e } from "@effect/sql-mssql";
2
+ import { Config as t, Effect as n, Layer as r, ManagedRuntime as i, Option as a, Redacted as o, Schedule as s } from "effect";
3
+ import { EventEmitter as c } from "node:events";
4
+ import { createLogger as l } from "@voltro/logger";
5
+ import { SqlClient as u, TransactionConnection as d } from "@effect/sql/SqlClient";
6
+ import { attachEagerLoads as f, compileEagerJson as p, compilePredicate as m, compileRawFragment as h, compileSelect as g, decodeRowsFromSchema as _, hasEagerLoads as v, id as y, qualifyTable as b, requireTable as x, table as S, text as C, timestamp as w } from "@voltro/database";
7
+ //#region src/sqlLayer.ts
8
+ var T = {
9
+ ...e.defaultParameterTypes,
10
+ null: e.defaultParameterTypes.object
11
+ }, E = (n) => e.layerConfig({
12
+ server: t.succeed(n.server),
13
+ parameterTypes: t.succeed(T),
14
+ ...n.port === void 0 ? {} : { port: t.succeed(n.port) },
15
+ ...n.database === void 0 ? {} : { database: t.succeed(n.database) },
16
+ ...n.username === void 0 ? {} : { username: t.succeed(n.username) },
17
+ ...n.password === void 0 ? {} : { password: t.succeed(o.make(n.password)) },
18
+ ...n.encrypt === void 0 ? {} : { encrypt: t.succeed(n.encrypt) },
19
+ ...n.trustServer === void 0 ? {} : { trustServer: t.succeed(n.trustServer) },
20
+ ...n.maxConnections === void 0 ? {} : { maxConnections: t.succeed(n.maxConnections) }
21
+ }), D = (e) => {
22
+ if (e.url) {
23
+ let t = new URL(e.url);
24
+ return {
25
+ server: t.hostname || "localhost",
26
+ port: t.port ? Number(t.port) : 1433,
27
+ username: decodeURIComponent(t.username || "sa"),
28
+ password: decodeURIComponent(t.password || ""),
29
+ database: t.pathname.replace(/^\//, "") || "master",
30
+ trustServer: !0,
31
+ ...e.maxConnections === void 0 ? {} : { maxConnections: e.maxConnections }
32
+ };
33
+ }
34
+ return {
35
+ server: e.host ?? "localhost",
36
+ port: e.port ?? 1433,
37
+ username: e.username ?? "sa",
38
+ password: e.password ?? "",
39
+ database: e.database ?? "master",
40
+ trustServer: !0,
41
+ ...e.maxConnections === void 0 ? {} : { maxConnections: e.maxConnections }
42
+ };
43
+ }, O = (e) => E(D(e)), k = /* @__PURE__ */ new Set(["1205"]), A = (e) => {
44
+ let t = e;
45
+ for (let e = 0; e < 5 && typeof t == "object" && t; e++) {
46
+ let e = t.number;
47
+ if (typeof e == "number") return String(e);
48
+ let n = t.code;
49
+ if (typeof n == "string") return n;
50
+ t = t.cause;
51
+ }
52
+ }, j = (e) => {
53
+ let t = A(e);
54
+ return t !== void 0 && k.has(t);
55
+ }, M = /* @__PURE__ */ new Set(["2601", "2627"]), N = (e) => {
56
+ let t = A(e);
57
+ return t !== void 0 && M.has(t);
58
+ }, P = (e) => j(e) ? "retry" : "noRetry", F = {
59
+ I: "insert",
60
+ U: "update",
61
+ D: "delete"
62
+ }, I = (e, t) => e(n.gen(function* () {
63
+ let e = yield* u;
64
+ if (((yield* e`
65
+ SELECT COUNT(*) AS ${e("on")}
66
+ FROM sys.change_tracking_databases
67
+ WHERE database_id = DB_ID()`)[0]?.on ?? 0) === 0) return yield* n.fail(/* @__PURE__ */ Error("mssql Change Tracking CDC requires CT enabled on the database. Run `ALTER DATABASE <db> SET CHANGE_TRACKING = ON (CHANGE_RETENTION = 2 DAYS, AUTO_CLEANUP = ON)`, or set CDC=0 for inline emit."));
68
+ for (let n of t) yield* e.unsafe(`IF NOT EXISTS (
69
+ SELECT 1 FROM sys.change_tracking_tables
70
+ WHERE object_id = OBJECT_ID(${L(n)})
71
+ )
72
+ ALTER TABLE ${R(n)} ENABLE CHANGE_TRACKING WITH (TRACK_COLUMNS_UPDATED = OFF)`);
73
+ })).then(() => void 0), L = (e) => `'${e.replace(/'/g, "''")}'`, R = (e) => `[${e.replace(/\]/g, "]]")}]`, z = async (e) => {
74
+ let t = l({ scope: "voltro:mssql:cdc" }), r = e.pollIntervalMs ?? 500;
75
+ await I(e.run, e.tables);
76
+ let i = async () => (await e.run(n.gen(function* () {
77
+ return yield* (yield* u)`SELECT CONVERT(VARCHAR(40), CHANGE_TRACKING_CURRENT_VERSION()) AS v`;
78
+ })))[0]?.v ?? "0", a = e.startVersion ?? await i(), o = !1, s = null, c = !1, d = (e, t) => BigInt(e) > BigInt(t), f = async (r, o) => {
79
+ let s = await e.run(n.gen(function* () {
80
+ let e = yield* u, t = e.unsafe(R(r)), n = (yield* e`
81
+ SELECT CONVERT(VARCHAR(40), CHANGE_TRACKING_MIN_VALID_VERSION(OBJECT_ID(${e.unsafe(L(r))}))) AS floor`)[0]?.floor;
82
+ return n != null && BigInt(o) < BigInt(n) ? {
83
+ resync: !0,
84
+ rows: []
85
+ } : {
86
+ resync: !1,
87
+ rows: yield* e`
88
+ SELECT ct.SYS_CHANGE_OPERATION AS __ct_op, ct.id AS __ct_id, t.*
89
+ FROM CHANGETABLE(CHANGES ${t}, ${BigInt(o)}) AS ct
90
+ LEFT JOIN ${t} AS t ON t.id = ct.id
91
+ ORDER BY ct.SYS_CHANGE_VERSION`.unprepared
92
+ };
93
+ }));
94
+ if (s.resync) {
95
+ t.warn("cdc: CT retention floor passed our cursor — resyncing to current version", { table: r }), a = await i(), e.onResync?.(), e.onVersion?.(a);
96
+ return;
97
+ }
98
+ for (let t of s.rows) {
99
+ let n = F[t.__ct_op];
100
+ if (!n) continue;
101
+ let { __ct_op: i, __ct_id: a, ...o } = t;
102
+ if (n === "delete") e.onChange({
103
+ table: r,
104
+ op: "delete",
105
+ old: { id: a },
106
+ new: null
107
+ });
108
+ else {
109
+ let t = _([o], r, "mssql")[0];
110
+ e.onChange({
111
+ table: r,
112
+ op: n,
113
+ old: null,
114
+ new: t
115
+ });
116
+ }
117
+ }
118
+ }, p = async () => {
119
+ if (!(o || c)) {
120
+ c = !0;
121
+ try {
122
+ let t = await i();
123
+ if (d(t, a)) {
124
+ for (let t of e.tables) {
125
+ if (o) break;
126
+ await f(t, a);
127
+ }
128
+ a = d(t, a) ? t : a, e.onVersion?.(a);
129
+ }
130
+ } catch (n) {
131
+ t.warn("cdc: poll cycle failed — retrying next tick", {}, n), e.onError?.(n);
132
+ } finally {
133
+ c = !1, o || (s = setTimeout(() => {
134
+ p();
135
+ }, r));
136
+ }
137
+ }
138
+ };
139
+ return s = setTimeout(() => {
140
+ p();
141
+ }, r), s.unref && s.unref(), t.info("cdc: Change Tracking reader attached", {
142
+ tables: e.tables.length,
143
+ from: e.startVersion ?? a
144
+ }), {
145
+ stop: async () => {
146
+ o = !0, s &&= (clearTimeout(s), null);
147
+ },
148
+ currentVersion: () => a
149
+ };
150
+ }, B = "_voltro_cdc_offsets", V = S(B, {
151
+ id: y({ prefix: "cdcoff" }),
152
+ replicaId: C(),
153
+ streamName: C(),
154
+ ctVersion: C(),
155
+ updatedAt: w()
156
+ }).index(["replicaId"]), H = l({ scope: "voltro:mssql" }), U = async (e) => {
157
+ let t = e.changeStrategy ?? "inline", n = t;
158
+ t === "cdc" && !e.cdcConfig && (H.warn("changeStrategy='cdc' requires cdcConfig (replicaId + includeTables); falling back to 'inline'."), n = "inline");
159
+ let a = e.tracerLayer ? r.mergeAll(e.sqlLayer, e.tracerLayer) : e.sqlLayer, o = i.make(a), s = new W(await o.runPromise(u), o, n);
160
+ return n === "cdc" && e.cdcConfig && await s.startCdcConsumer(e.cdcConfig), s;
161
+ }, W = class e {
162
+ sql;
163
+ runtime;
164
+ changeStrategy;
165
+ namespace;
166
+ emitter;
167
+ inflightTxns = 0;
168
+ cdcHandle = null;
169
+ cdcCheckpointTimer = null;
170
+ cdcPendingVersion = null;
171
+ cdcReplicaId = "";
172
+ cdcStreamName = "default";
173
+ constructor(e, t, n = "inline", r = null, i) {
174
+ this.sql = e, this.runtime = t, this.changeStrategy = n, this.namespace = r, this.emitter = i ?? new c();
175
+ }
176
+ get inlineEmit() {
177
+ return this.changeStrategy === "inline";
178
+ }
179
+ withNamespace(t) {
180
+ return t === this.namespace ? this : new e(this.sql, this.runtime, this.changeStrategy, t, this.emitter);
181
+ }
182
+ nsT(e) {
183
+ return b(this.namespace, e);
184
+ }
185
+ __mssqlReplicationFriend = { runEffect: (e) => this.runtime.runPromise(e) };
186
+ async executeQuery(e, t) {
187
+ let r = g(e, this.sql, this.namespace), i = t ? n.provideService(r, d, t) : r;
188
+ return _(await this.runtime.runPromise(i), e.table, "mssql");
189
+ }
190
+ async executeInsert(e, t, r, i) {
191
+ let a = this.sql, o = a`INSERT INTO ${a(this.nsT(e))} ${a.insert(t).returning("*")}`, s = r ? n.provideService(o, d, r) : o, c = (await this.runtime.runPromise(s))[0];
192
+ if (!c) throw Error(`MssqlStore.insert: row not returned post-insert in '${e}'`);
193
+ return this.routeEvent({
194
+ table: e,
195
+ op: "insert",
196
+ old: null,
197
+ new: c
198
+ }, i), c;
199
+ }
200
+ async executeUpdate(e, t, r, i, a) {
201
+ let o = this.sql, s = o`UPDATE ${o(this.nsT(e))} SET ${o.update(r).returning("*")} WHERE ${o("id")} = ${t}`, c = i ? n.provideService(s, d, i) : s, l = (await this.runtime.runPromise(c))[0];
202
+ return l ? (this.routeEvent({
203
+ table: e,
204
+ op: "update",
205
+ old: null,
206
+ new: l
207
+ }, a), l) : null;
208
+ }
209
+ async executeInsertMany(e, t, r, i) {
210
+ if (t.length === 0) return [];
211
+ let a = this.sql, o = a`INSERT INTO ${a(this.nsT(e))} ${a.insert(t).returning("*")}`, s = r ? n.provideService(o, d, r) : o, c = await this.runtime.runPromise(s), l = t.map((e) => e.id), u = new Map(c.map((e) => [e.id, e])), f = l.map((e) => u.get(e)).filter((e) => e !== void 0), p = f.length === c.length ? f : [...c];
212
+ for (let t of p) this.routeEvent({
213
+ table: e,
214
+ op: "insert",
215
+ old: null,
216
+ new: t
217
+ }, i);
218
+ return p;
219
+ }
220
+ async executePatchJson(e, t, r, i, a, o) {
221
+ let s = this.sql, c = r.split("."), l = c[0], u = c.slice(1), f = u.length === 0 ? "$" : `$.${u.join(".")}`, p = JSON.stringify(i ?? null), m = s`UPDATE ${s(this.nsT(e))} SET ${s(l)} = JSON_MODIFY(${s(l)}, ${f}, JSON_QUERY(${p})) OUTPUT INSERTED.* WHERE ${s("id")} = ${t}`, h = a ? n.provideService(m, d, a) : m, g = (await this.runtime.runPromise(h))[0];
222
+ return g ? (this.routeEvent({
223
+ table: e,
224
+ op: "update",
225
+ old: null,
226
+ new: g
227
+ }, o), g) : null;
228
+ }
229
+ async executeDelete(e, t, r, i) {
230
+ let a = this.sql, o = a`DELETE FROM ${a(this.nsT(e))} OUTPUT DELETED.* WHERE ${a("id")} = ${t}`, s = r ? n.provideService(o, d, r) : o, c = (await this.runtime.runPromise(s))[0];
231
+ return c ? (this.routeEvent({
232
+ table: e,
233
+ op: "delete",
234
+ old: c,
235
+ new: null
236
+ }, i), !0) : !1;
237
+ }
238
+ routeEvent(e, t) {
239
+ t === null ? this.inlineEmit && this.emitter.emit("change", e) : t.push(e);
240
+ }
241
+ query(e) {
242
+ return this.runWithEager(e, null);
243
+ }
244
+ raw(e, t) {
245
+ let n = h(e, this.sql);
246
+ return this.runtime.runPromise(n);
247
+ }
248
+ async runWithEager(e, t) {
249
+ if (!v(e)) return this.executeQuery(e, t);
250
+ let r = this.namespace === null ? p(e, this.sql, "mssql") : null;
251
+ if (r !== null) try {
252
+ let e = t ? n.provideService(r.fragment, d, t) : r.fragment, i = await this.runtime.runPromise(e);
253
+ return r.decode(i);
254
+ } catch (e) {
255
+ H.warn("mssql JSON-agg eager-load failed; falling back to walker", { err: e });
256
+ }
257
+ return f(await this.executeQuery(e, t), e.eager, e.sourceTable ?? x(e.table), (e) => this.executeQuery(e, t));
258
+ }
259
+ getInternalRunWithEager() {
260
+ return this.runWithEager.bind(this);
261
+ }
262
+ insert(e, t) {
263
+ return this.executeInsert(e, t, null, null);
264
+ }
265
+ insertMany(e, t) {
266
+ return this.executeInsertMany(e, t, null, null);
267
+ }
268
+ patchJson(e, t, n, r) {
269
+ return this.executePatchJson(e, t, n, r, null, null);
270
+ }
271
+ update(e, t, n) {
272
+ return this.executeUpdate(e, t, n, null, null);
273
+ }
274
+ delete(e, t) {
275
+ return this.executeDelete(e, t, null, null);
276
+ }
277
+ async updateMany(e, t, n) {
278
+ let r = this.sql, i = m(n.where, r, this.namespace), a = r`UPDATE ${r(this.nsT(e))} SET ${r.update(t)} OUTPUT INSERTED.* WHERE ${i}`, o = await this.runtime.runPromise(a);
279
+ for (let t of o) this.routeEvent({
280
+ table: e,
281
+ op: "update",
282
+ old: null,
283
+ new: t
284
+ }, null);
285
+ return o.length;
286
+ }
287
+ async deleteMany(e, t) {
288
+ let n = this.sql, r = m(t.where, n, this.namespace), i = n`DELETE FROM ${n(this.nsT(e))} OUTPUT DELETED.* WHERE ${r}`, a = await this.runtime.runPromise(i);
289
+ for (let t of a) this.routeEvent({
290
+ table: e,
291
+ op: "delete",
292
+ old: t,
293
+ new: null
294
+ }, null);
295
+ return a.length;
296
+ }
297
+ upsert(e, t, n) {
298
+ return this.executeUpsert(e, t, n, null, null);
299
+ }
300
+ insertIgnore(e, t, n) {
301
+ return this.executeInsertIgnore(e, t, n, null, null);
302
+ }
303
+ upsertPatch(e, t, n) {
304
+ if (typeof n.update == "function") return n.update(t);
305
+ if (Array.isArray(n.update)) {
306
+ let t = {};
307
+ for (let r of n.update) r in e && (t[r] = e[r]);
308
+ return t;
309
+ }
310
+ let r = {};
311
+ for (let [t, i] of Object.entries(e)) t !== "id" && (n.conflictColumns.includes(t) || (r[t] = i));
312
+ return r;
313
+ }
314
+ async executeUpsert(e, t, n, r, i) {
315
+ if (typeof n.update != "function" && n.conflictColumns.length > 0) return this.mergeUpsert(e, t, {
316
+ conflictColumns: n.conflictColumns,
317
+ ...n.update ? { update: n.update } : {}
318
+ }, r, i);
319
+ let a = await this.findByConflict(e, t, n.conflictColumns, r);
320
+ if (a) return await this.executeUpdate(e, a.id, this.upsertPatch(t, a, n), r, i) ?? a;
321
+ try {
322
+ return await this.executeInsert(e, t, r, i);
323
+ } catch (a) {
324
+ if (!N(a)) throw a;
325
+ let o = await this.findByConflict(e, t, n.conflictColumns, r);
326
+ if (!o) throw a;
327
+ return await this.executeUpdate(e, o.id, this.upsertPatch(t, o, n), r, i) ?? o;
328
+ }
329
+ }
330
+ async mergeUpsert(e, t, r, i, a) {
331
+ let o = this.sql, s = Object.keys(t), c = Array.isArray(r.update) ? r.update.filter((e) => s.includes(e)) : s.filter((e) => e !== "id" && !r.conflictColumns.includes(e)), l = s.map((e) => o`${t[e]}`), u = s.map((e) => o`${o(e)}`), f = r.conflictColumns.map((e) => o`tgt.${o(e)} = src.${o(e)}`), p = s.map((e) => o`${o(e)}`), m = s.map((e) => o`src.${o(e)}`), h = (c.length > 0 ? c : [r.conflictColumns[0]]).map((e) => o`tgt.${o(e)} = src.${o(e)}`), g = o`
332
+ MERGE ${o(this.nsT(e))} WITH (HOLDLOCK) AS tgt
333
+ USING (VALUES (${o.csv(l)})) AS src (${o.csv(u)})
334
+ ON ${o.and(f)}
335
+ WHEN MATCHED THEN UPDATE SET ${o.csv(h)}
336
+ WHEN NOT MATCHED THEN INSERT (${o.csv(p)}) VALUES (${o.csv(m)})
337
+ OUTPUT $action AS ${o("__action")}, INSERTED.*;`, _ = i ? n.provideService(g, d, i) : g, v = (await this.runtime.runPromise(_))[0];
338
+ if (!v) throw Error(`MssqlStore.upsert: MERGE returned no row for '${e}'`);
339
+ let y = v.__action, { __action: b, ...x } = v;
340
+ return this.routeEvent({
341
+ table: e,
342
+ op: y === "INSERT" ? "insert" : "update",
343
+ old: null,
344
+ new: x
345
+ }, a), x;
346
+ }
347
+ async executeInsertIgnore(e, t, n, r, i) {
348
+ let a = await this.findByConflict(e, t, n.conflictColumns, r);
349
+ if (a) return a;
350
+ try {
351
+ return await this.executeInsert(e, t, r, i);
352
+ } catch (i) {
353
+ if (!N(i)) throw i;
354
+ let a = await this.findByConflict(e, t, n.conflictColumns, r);
355
+ if (!a) throw i;
356
+ return a;
357
+ }
358
+ }
359
+ async findByConflict(e, t, r, i) {
360
+ if (r.length === 0) return;
361
+ let a = this.sql, o = r.map((e) => a`${a(e)} = ${t[e]}`), s = a`SELECT TOP 1 * FROM ${a(this.nsT(e))} WHERE ${a.and(o)}`, c = i ? n.provideService(s, d, i) : s;
362
+ return (await this.runtime.runPromise(c))[0];
363
+ }
364
+ emitChange(e) {
365
+ this.emitter.emit("change", e);
366
+ }
367
+ async startCdcConsumer(e) {
368
+ if (this.cdcHandle) return;
369
+ this.cdcReplicaId = e.replicaId;
370
+ let t = e.includeTables ?? [];
371
+ t.length === 0 && H.warn("cdc: no reactive tables to tail; Change Tracking reader idle. Set includeTables to enable.");
372
+ let n = await this.readCdcOffset(e.replicaId);
373
+ this.cdcHandle = await z({
374
+ run: this.__mssqlReplicationFriend.runEffect,
375
+ tables: t,
376
+ startVersion: n,
377
+ onChange: (e) => this.injectExternalChange(e),
378
+ onVersion: (e) => {
379
+ this.cdcPendingVersion = e;
380
+ },
381
+ onError: (e) => H.warn("cdc: consumer error", {}, e),
382
+ onResync: () => {
383
+ H.warn("cdc: resynced to current CT version after retention gap — subscriptions self-heal on the next change");
384
+ }
385
+ }), this.cdcCheckpointTimer = setInterval(() => {
386
+ this.flushCdcOffset();
387
+ }, 1e3), this.cdcCheckpointTimer.unref && this.cdcCheckpointTimer.unref();
388
+ }
389
+ async readCdcOffset(e) {
390
+ try {
391
+ let t = this.sql, n = (await this.runtime.runPromise(t`
392
+ SELECT TOP 1 ${t("ctVersion")} FROM ${t(B)}
393
+ WHERE ${t("id")} = ${e}`))[0]?.ctVersion;
394
+ return n ? (H.info("cdc: resuming from persisted CT version", {
395
+ replicaId: e,
396
+ version: n
397
+ }), n) : null;
398
+ } catch (t) {
399
+ return H.warn("cdc: no readable offset; starting at current CT version", { replicaId: e }, t), null;
400
+ }
401
+ }
402
+ async flushCdcOffset() {
403
+ let e = this.cdcPendingVersion;
404
+ if (e !== null) {
405
+ this.cdcPendingVersion = null;
406
+ try {
407
+ let t = this.cdcReplicaId;
408
+ await this.executeUpsert(B, {
409
+ id: t,
410
+ replicaId: t,
411
+ streamName: this.cdcStreamName,
412
+ ctVersion: e,
413
+ updatedAt: /* @__PURE__ */ new Date()
414
+ }, {
415
+ conflictColumns: ["id"],
416
+ update: ["ctVersion", "updatedAt"]
417
+ }, null, null);
418
+ } catch (t) {
419
+ this.cdcPendingVersion = e, H.warn("cdc: checkpoint write failed", {}, t);
420
+ }
421
+ }
422
+ }
423
+ getInternalExecuteQuery() {
424
+ return this.executeQuery.bind(this);
425
+ }
426
+ getInternalExecuteInsert() {
427
+ return this.executeInsert.bind(this);
428
+ }
429
+ getInternalExecuteInsertMany() {
430
+ return this.executeInsertMany.bind(this);
431
+ }
432
+ getInternalExecutePatchJson() {
433
+ return this.executePatchJson.bind(this);
434
+ }
435
+ getInternalExecuteUpdate() {
436
+ return this.executeUpdate.bind(this);
437
+ }
438
+ getInternalExecuteDelete() {
439
+ return this.executeDelete.bind(this);
440
+ }
441
+ getInternalExecuteUpsert() {
442
+ return this.executeUpsert.bind(this);
443
+ }
444
+ getInternalExecuteInsertIgnore() {
445
+ return this.executeInsertIgnore.bind(this);
446
+ }
447
+ async transactional(e) {
448
+ this.inflightTxns++;
449
+ let t = 0, r = n.suspend(() => {
450
+ let r = ++t;
451
+ return this.sql.withTransaction(n.flatMap(n.serviceOption(d), (t) => {
452
+ if (a.isNone(t)) return n.fail(/* @__PURE__ */ Error("MssqlStore.transactional: TransactionConnection missing."));
453
+ let i = new G(this, t.value);
454
+ return n.tryPromise({
455
+ try: () => e(i).then((e) => ({
456
+ result: e,
457
+ view: i,
458
+ attempt: r
459
+ })),
460
+ catch: (e) => e
461
+ });
462
+ }));
463
+ }), i = s.exponential("10 millis").pipe(s.compose(s.recurs(3)), s.whileInput(j)), o = r.pipe(n.retry(i), n.withSpan("store.transactional", { attributes: {
464
+ "db.system": "mssql",
465
+ "db.operation": "transaction"
466
+ } }));
467
+ try {
468
+ let e = await this.runtime.runPromise(o);
469
+ return e.view.commitEvents(), e.result;
470
+ } finally {
471
+ this.inflightTxns--;
472
+ }
473
+ }
474
+ onChange(e) {
475
+ return this.emitter.on("change", e), () => {
476
+ this.emitter.off("change", e);
477
+ };
478
+ }
479
+ get changeScope() {
480
+ return this.changeStrategy === "cdc" ? "fleet" : "local";
481
+ }
482
+ injectExternalChange(e) {
483
+ this.emitter.emit("change", {
484
+ ...e,
485
+ origin: "injected"
486
+ });
487
+ }
488
+ run(e) {
489
+ return this.runtime.runPromise(e);
490
+ }
491
+ async close(e = 5e3) {
492
+ if (this.cdcCheckpointTimer &&= (clearInterval(this.cdcCheckpointTimer), null), this.cdcHandle) {
493
+ let e = this.cdcHandle.currentVersion();
494
+ e !== null && (this.cdcPendingVersion = e), await this.flushCdcOffset(), await this.cdcHandle.stop(), this.cdcHandle = null;
495
+ }
496
+ if (this.inflightTxns > 0) {
497
+ let t = Date.now() + e;
498
+ for (; this.inflightTxns > 0 && Date.now() < t;) await new Promise((e) => setTimeout(e, 25));
499
+ this.inflightTxns > 0 && H.warn("close: grace period expired with in-flight transactions", {
500
+ gracePeriodMs: e,
501
+ inflight: this.inflightTxns
502
+ });
503
+ }
504
+ await this.runtime.dispose();
505
+ }
506
+ async ping() {
507
+ await this.runtime.runPromise(this.sql`SELECT 1`);
508
+ }
509
+ }, G = class {
510
+ parent;
511
+ txn;
512
+ events = [];
513
+ committed = !1;
514
+ constructor(e, t) {
515
+ this.parent = e, this.txn = t;
516
+ }
517
+ query(e) {
518
+ return this.parent.getInternalRunWithEager()(e, this.txn);
519
+ }
520
+ insert(e, t) {
521
+ return this.parent.getInternalExecuteInsert()(e, t, this.txn, this.events);
522
+ }
523
+ insertMany(e, t) {
524
+ return this.parent.getInternalExecuteInsertMany()(e, t, this.txn, this.events);
525
+ }
526
+ patchJson(e, t, n, r) {
527
+ return this.parent.getInternalExecutePatchJson()(e, t, n, r, this.txn, this.events);
528
+ }
529
+ update(e, t, n) {
530
+ return this.parent.getInternalExecuteUpdate()(e, t, n, this.txn, this.events);
531
+ }
532
+ delete(e, t) {
533
+ return this.parent.getInternalExecuteDelete()(e, t, this.txn, this.events);
534
+ }
535
+ async updateMany(e, t, n) {
536
+ let r = await this.query({
537
+ table: e,
538
+ predicate: n.where,
539
+ order: [],
540
+ take: void 0,
541
+ skip: void 0,
542
+ projection: ["id"]
543
+ }), i = 0;
544
+ for (let n of r) await this.update(e, n.id, t) && i++;
545
+ return i;
546
+ }
547
+ async deleteMany(e, t) {
548
+ let n = await this.query({
549
+ table: e,
550
+ predicate: t.where,
551
+ order: [],
552
+ take: void 0,
553
+ skip: void 0,
554
+ projection: ["id"]
555
+ }), r = 0;
556
+ for (let t of n) await this.delete(e, t.id) && r++;
557
+ return r;
558
+ }
559
+ upsert(e, t, n) {
560
+ return this.parent.getInternalExecuteUpsert()(e, t, n, this.txn, this.events);
561
+ }
562
+ insertIgnore(e, t, n) {
563
+ return this.parent.getInternalExecuteInsertIgnore()(e, t, n, this.txn, this.events);
564
+ }
565
+ transactional(e) {
566
+ return Promise.reject(/* @__PURE__ */ Error("MssqlStore.transactional: nested transactions are not supported."));
567
+ }
568
+ onChange(e) {
569
+ throw Error("MssqlTransactionalView.onChange: not supported inside a transaction.");
570
+ }
571
+ injectExternalChange(e) {
572
+ throw Error("MssqlTransactionalView.injectExternalChange: not supported inside a transaction.");
573
+ }
574
+ commitEvents() {
575
+ if (!this.committed) {
576
+ this.committed = !0;
577
+ for (let e of this.events) this.parent.emitChange(e);
578
+ this.events.length = 0;
579
+ }
580
+ }
581
+ }, K = (e) => e.__mssqlReplicationFriend ?? null, q = (e, t) => e === t ? 0 : e < t ? -1 : 1, J = () => ({
582
+ async capturePrimaryPosition(e) {
583
+ let t = K(e);
584
+ if (t === null) throw Error("mssqlReplicationAdapter: primary is not an MssqlStore (missing __mssqlReplicationFriend).");
585
+ return t.runEffect(n.gen(function* () {
586
+ return (yield* (yield* u)`
587
+ SELECT CONVERT(VARCHAR(40), end_of_log_lsn) AS lsn
588
+ FROM sys.dm_hadr_database_replica_states
589
+ WHERE database_id = DB_ID()
590
+ AND is_local = 1
591
+ AND is_primary_replica = 1
592
+ `)[0]?.lsn ?? "00000000:00000000:0000";
593
+ }));
594
+ },
595
+ async probeReplicaPosition(e) {
596
+ let t = K(e);
597
+ if (t === null) throw Error("mssqlReplicationAdapter: replica is not an MssqlStore.");
598
+ return t.runEffect(n.gen(function* () {
599
+ return (yield* (yield* u)`
600
+ SELECT CONVERT(VARCHAR(40), last_hardened_lsn) AS lsn
601
+ FROM sys.dm_hadr_database_replica_states
602
+ WHERE database_id = DB_ID()
603
+ AND is_local = 1
604
+ `)[0]?.lsn ?? "00000000:00000000:0000";
605
+ }));
606
+ },
607
+ compare(e, t) {
608
+ return q(t, e) >= 0 ? "caught-up" : "behind";
609
+ }
610
+ }), Y = {
611
+ id: "mssql",
612
+ makeSqlLayer: (e) => O(e),
613
+ makeStore: (e) => U(e),
614
+ compileContains: (e, t, n) => n`${e} LIKE ${`%${t.replace(/[\\%_[\]]/g, (e) => `\\${e}`)}%`} ESCAPE '\\'`,
615
+ retryFilter: P
616
+ };
617
+ //#endregion
618
+ export { B as CDC_OFFSETS_TABLE, e as MssqlClient, V as _voltroMssqlCdcOffsetsTable, D as connectionFromConfig, I as ensureChangeTracking, E as makeMssqlSqlLayer, O as makeMssqlSqlLayerFromConfig, U as makeMssqlStore, Y as mssqlDialect, J as mssqlReplicationAdapter, P as mssqlRetryFilter, z as startChangeTrackingCdc };
package/package.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "name": "@voltro/sql-mssql",
3
+ "version": "0.1.0",
4
+ "description": "SQL Server (mssql) dialect adapter for Voltro's cross-dialect DataStore (OUTPUT-clause returning, OFFSET/FETCH paging, inline reactivity).",
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-mssql": "^0.52.0",
37
+ "@voltro/database": "0.1.0",
38
+ "@voltro/logger": "0.1.0"
39
+ },
40
+ "peerDependencies": {
41
+ "effect": "^3.21.4"
42
+ },
43
+ "publishConfig": {
44
+ "access": "public"
45
+ }
46
+ }