@voltro/plugin-cdc-out 0.55.0 → 0.57.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 CHANGED
@@ -1,7 +1,6 @@
1
1
  import { Duration as e, Effect as t, Fiber as n, Schedule as r, Schema as i } from "effect";
2
2
  import { definePlugin as a, pluginInstanceName as o } from "@voltro/protocol";
3
- import { and as s, count as c, eq as l, generateId as u, id as d, inSet as f, integer as p, json as m, lt as h, registerRetention as ee, retentionTtlMsFromEnv as te, table as g, text as _, timestamp as v } from "@voltro/database";
4
- import { createHash as y } from "node:crypto";
3
+ import { OccurrenceCounter as s, and as c, changeDigest as ee, composeChangeKey as l, count as u, eq as d, generateId as f, id as p, inSet as m, integer as h, json as te, lt as g, registerRetention as ne, retentionTtlMsFromEnv as re, table as _, text as v, timestamp as y } from "@voltro/database";
5
4
  import { withServerTracer as b } from "@voltro/runtime";
6
5
  //#region src/errors.ts
7
6
  var x = class extends i.TaggedError()("CdcConfigError", { message: i.String }) {}, S = class extends i.TaggedError()("CdcDeliveryError", {
@@ -17,34 +16,34 @@ var x = class extends i.TaggedError()("CdcConfigError", { message: i.String }) {
17
16
  }, D = {
18
17
  kind: "typeid",
19
18
  prefix: "cdcoutclaim"
20
- }, O = () => u(E, C), k = () => u(D, T), A = g(C, {
21
- id: d({ prefix: "cdcout" }),
22
- pipe: _(),
23
- sourceTable: _(),
24
- op: _(),
25
- key: _(),
26
- payload: m().nullable(),
27
- status: _(),
28
- attempts: p().default(0),
29
- nextAttemptAt: v(),
30
- claimedBy: _().nullable(),
31
- claimedAt: v().nullable(),
32
- createdAt: v(),
33
- deliveredAt: v().nullable(),
34
- lastError: _().nullable()
35
- }).index(["pipe", "status"]), ne = g(T, {
36
- id: d({ prefix: "cdcoutclaim" }),
37
- pipe: _(),
38
- changeKey: _(),
39
- claimedBy: _(),
40
- seenAt: v()
41
- }).unique("cdcoutClaimKey", ["pipe", "changeKey"]).index(["seenAt"]), re = g(w, {
42
- id: d({ scheme: "numeric" }),
43
- key: _(),
44
- holder: _(),
45
- expiresAt: v(),
46
- renewedAt: v()
47
- }).unique("cdcoutLeaseKey", ["key"]), j = (e) => e instanceof Date ? e.getTime() : new Date(String(e)).getTime(), M = class {
19
+ }, O = () => f(E, C), ie = () => f(D, T), k = _(C, {
20
+ id: p({ prefix: "cdcout" }),
21
+ pipe: v(),
22
+ sourceTable: v(),
23
+ op: v(),
24
+ key: v(),
25
+ payload: te().nullable(),
26
+ status: v(),
27
+ attempts: h().default(0),
28
+ nextAttemptAt: y(),
29
+ claimedBy: v().nullable(),
30
+ claimedAt: y().nullable(),
31
+ createdAt: y(),
32
+ deliveredAt: y().nullable(),
33
+ lastError: v().nullable()
34
+ }).index(["pipe", "status"]), A = _(T, {
35
+ id: p({ prefix: "cdcoutclaim" }),
36
+ pipe: v(),
37
+ changeKey: v(),
38
+ claimedBy: v(),
39
+ seenAt: y()
40
+ }).unique("cdcoutClaimKey", ["pipe", "changeKey"]).index(["seenAt"]), j = _(w, {
41
+ id: p({ scheme: "numeric" }),
42
+ key: v(),
43
+ holder: v(),
44
+ expiresAt: y(),
45
+ renewedAt: y()
46
+ }).unique("cdcoutLeaseKey", ["key"]), M = (e) => e instanceof Date ? e.getTime() : new Date(String(e)).getTime(), N = class {
48
47
  opts;
49
48
  held = !1;
50
49
  constructor(e) {
@@ -60,13 +59,13 @@ var x = class extends i.TaggedError()("CdcConfigError", { message: i.String }) {
60
59
  renewedAt: new Date(t)
61
60
  };
62
61
  try {
63
- if (await e.updateMany("_voltro_cdcout_leases", n, { where: s(l("key", this.opts.key), l("holder", this.opts.holder)) }) === 1) return this.held = !0, !0;
62
+ if (await e.updateMany("_voltro_cdcout_leases", n, { where: c(d("key", this.opts.key), d("holder", this.opts.holder)) }) === 1) return this.held = !0, !0;
64
63
  let r = await e.insertIgnore(w, {
65
64
  key: this.opts.key,
66
65
  ...n
67
66
  }, { conflictColumns: ["key"] });
68
67
  if (r.holder === this.opts.holder) return this.held = !0, !0;
69
- let i = await e.updateMany(w, n, { where: s(l("key", this.opts.key), l("holder", String(r.holder)), h("expiresAt", new Date(t))) });
68
+ let i = await e.updateMany(w, n, { where: c(d("key", this.opts.key), d("holder", String(r.holder)), g("expiresAt", new Date(t))) });
70
69
  return this.held = i === 1, this.held;
71
70
  } catch {
72
71
  return this.held = !1, !1;
@@ -75,67 +74,10 @@ var x = class extends i.TaggedError()("CdcConfigError", { message: i.String }) {
75
74
  async release(e) {
76
75
  this.held = !1;
77
76
  try {
78
- await e.updateMany(w, { expiresAt: /* @__PURE__ */ new Date(0) }, { where: s(l("key", this.opts.key), l("holder", this.opts.holder)) });
77
+ await e.updateMany(w, { expiresAt: /* @__PURE__ */ new Date(0) }, { where: c(d("key", this.opts.key), d("holder", this.opts.holder)) });
79
78
  } catch {}
80
79
  }
81
- }, N = (e) => e == null ? "null" : e instanceof Date ? JSON.stringify(e.toISOString()) : typeof e == "bigint" ? JSON.stringify(e.toString()) : Array.isArray(e) ? `[${e.map(N).join(",")}]` : typeof e == "object" ? `{${Object.entries(e).filter(([, e]) => e !== void 0).sort(([e], [t]) => e < t ? -1 : +(e > t)).map(([e, t]) => `${JSON.stringify(e)}:${N(t)}`).join(",")}}` : JSON.stringify(e) ?? "null", P = (e) => y("sha256").update([
82
- e.pipe,
83
- e.op,
84
- e.key,
85
- N(e.next),
86
- N(e.prev)
87
- ].join("\0")).digest("hex").slice(0, 32), F = (e, t) => `${e}:${t}`, I = (e) => {
88
- let t = e.lastIndexOf(":");
89
- if (t <= 0) return null;
90
- let n = Number(e.slice(t + 1));
91
- return !Number.isInteger(n) || n < 0 ? null : {
92
- digest: e.slice(0, t),
93
- occurrence: n
94
- };
95
- }, L = class {
96
- #e = /* @__PURE__ */ new Map();
97
- #t;
98
- #n;
99
- constructor(e) {
100
- this.#t = e.windowMs, this.#n = Math.max(1, e.maxEntries);
101
- }
102
- get size() {
103
- return this.#e.size;
104
- }
105
- next(e, t) {
106
- let n = this.#e.get(e);
107
- if (n === void 0) return this.#e.set(e, {
108
- count: 1,
109
- lastMs: t
110
- }), this.#r(), 0;
111
- n.lastMs = t;
112
- let r = n.count;
113
- return n.count += 1, this.#e.delete(e), this.#e.set(e, n), r;
114
- }
115
- seedFromChangeKeys(e, t) {
116
- for (let n of e) {
117
- let e = I(n);
118
- if (e === null) continue;
119
- let r = this.#e.get(e.digest), i = Math.max(r?.count ?? 0, e.occurrence + 1);
120
- this.#e.set(e.digest, {
121
- count: i,
122
- lastMs: r?.lastMs ?? t
123
- });
124
- }
125
- this.#r();
126
- }
127
- prune(e) {
128
- let t = e - this.#t;
129
- for (let [e, n] of this.#e) n.lastMs < t && this.#e.delete(e);
130
- }
131
- #r() {
132
- for (; this.#e.size > this.#n;) {
133
- let e = this.#e.keys().next();
134
- if (e.done === !0) return;
135
- this.#e.delete(e.value);
136
- }
137
- }
138
- }, R = class {
80
+ }, P = class {
139
81
  #e = [];
140
82
  #t = 0;
141
83
  #n;
@@ -168,7 +110,7 @@ var x = class extends i.TaggedError()("CdcConfigError", { message: i.String }) {
168
110
  for (let [e, i] of this.#e.entries()) i.observedAtMs >= t ? (e < this.#t && (r += 1), n.push(i)) : i.enqueued || (this.#i += 1);
169
111
  this.#e = n, this.#t = r;
170
112
  }
171
- }, z = (n, r, i) => t.suspend(() => {
113
+ }, F = (n, r, i) => t.suspend(() => {
172
114
  let a = new AbortController();
173
115
  return t.try(() => n.deliver(r, { signal: a.signal })).pipe(t.flatMap((e) => t.isEffect(e) ? e : t.tryPromise(() => e))).pipe(t.timeoutFail({
174
116
  duration: e.millis(i.timeoutMs),
@@ -176,21 +118,21 @@ var x = class extends i.TaggedError()("CdcConfigError", { message: i.String }) {
176
118
  sink: n.name,
177
119
  reason: `delivery timed out after ${i.timeoutMs}ms`
178
120
  })
179
- }), t.onError(() => t.sync(() => a.abort())), t.mapError((e) => e instanceof S ? e : B(n.name, e)), t.withSpan("cdcOut.deliver", { attributes: {
121
+ }), t.onError(() => t.sync(() => a.abort())), t.mapError((e) => e instanceof S ? e : I(n.name, e)), t.withSpan("cdcOut.deliver", { attributes: {
180
122
  pipe: i.pipe,
181
123
  sink: n.name,
182
124
  records: r.length
183
125
  } }));
184
- }), B = (e, t) => {
126
+ }), I = (e, t) => {
185
127
  let n = typeof t == "object" && t && "error" in t ? t.error : t;
186
128
  return new S({
187
129
  sink: e,
188
130
  reason: n instanceof Error ? n.message : String(n)
189
131
  });
190
- }, V = (e, t) => {
132
+ }, L = (e, t) => {
191
133
  let n = Math.min(t.baseMs * 2 ** Math.max(0, e - 1), t.maxMs);
192
134
  return Math.round(n * (.5 + .5 * (t.random ?? Math.random)()));
193
- }, H = class {
135
+ }, ae = class {
194
136
  opts;
195
137
  now;
196
138
  stuckClaimMs;
@@ -215,9 +157,9 @@ var x = class extends i.TaggedError()("CdcConfigError", { message: i.String }) {
215
157
  status: "pending",
216
158
  claimedBy: null,
217
159
  claimedAt: null
218
- }, { where: s(l("pipe", n.key), l("status", "delivering"), h("claimedAt", new Date(r - this.stuckClaimMs))) }), (await e.query({
160
+ }, { where: c(d("pipe", n.key), d("status", "delivering"), g("claimedAt", new Date(r - this.stuckClaimMs))) }), (await e.query({
219
161
  table: "_voltro_cdcout_outbox",
220
- predicate: s(l("pipe", n.key), l("status", "delivering")),
162
+ predicate: c(d("pipe", n.key), d("status", "delivering")),
221
163
  order: [],
222
164
  take: 1,
223
165
  skip: void 0,
@@ -225,7 +167,7 @@ var x = class extends i.TaggedError()("CdcConfigError", { message: i.String }) {
225
167
  })).length > 0) return;
226
168
  let i = await e.query({
227
169
  table: C,
228
- predicate: s(l("pipe", n.key), l("status", "pending")),
170
+ predicate: c(d("pipe", n.key), d("status", "pending")),
229
171
  order: [{
230
172
  column: "id",
231
173
  direction: "asc"
@@ -237,7 +179,7 @@ var x = class extends i.TaggedError()("CdcConfigError", { message: i.String }) {
237
179
  if (i.length === 0) return;
238
180
  let a = [];
239
181
  for (let e of i) {
240
- if (j(e.nextAttemptAt) > r) break;
182
+ if (M(e.nextAttemptAt) > r) break;
241
183
  a.push(e);
242
184
  }
243
185
  if (a.length === 0) return;
@@ -246,10 +188,10 @@ var x = class extends i.TaggedError()("CdcConfigError", { message: i.String }) {
246
188
  status: "delivering",
247
189
  claimedBy: this.opts.replicaId,
248
190
  claimedAt: new Date(r)
249
- }, { where: s(l("pipe", n.key), f("id", o), l("status", "pending")) }) === 0) return;
250
- let c = await e.query({
191
+ }, { where: c(d("pipe", n.key), m("id", o), d("status", "pending")) }) === 0) return;
192
+ let s = await e.query({
251
193
  table: C,
252
- predicate: s(l("pipe", n.key), f("id", o), l("status", "delivering"), l("claimedBy", this.opts.replicaId)),
194
+ predicate: c(d("pipe", n.key), m("id", o), d("status", "delivering"), d("claimedBy", this.opts.replicaId)),
253
195
  order: [{
254
196
  column: "id",
255
197
  direction: "asc"
@@ -258,39 +200,39 @@ var x = class extends i.TaggedError()("CdcConfigError", { message: i.String }) {
258
200
  skip: void 0,
259
201
  projection: void 0
260
202
  });
261
- if (c.length === 0) return;
262
- let u = c.map((e) => ({
203
+ if (s.length === 0) return;
204
+ let ee = s.map((e) => ({
263
205
  table: e.sourceTable,
264
206
  op: e.op,
265
207
  key: e.key,
266
208
  data: e.payload,
267
209
  deliveryKey: e.id
268
- })), d = await t.runPromise(b(t.either(z(n.sink, u, {
210
+ })), l = await t.runPromise(b(t.either(F(n.sink, ee, {
269
211
  timeoutMs: this.opts.deliveryTimeoutMs,
270
212
  pipe: n.key
271
- })))), p = this.now();
272
- if (d._tag === "Right") {
213
+ })))), u = this.now();
214
+ if (l._tag === "Right") {
273
215
  await e.updateMany(C, {
274
216
  status: "delivered",
275
- deliveredAt: new Date(p),
217
+ deliveredAt: new Date(u),
276
218
  claimedBy: null,
277
219
  claimedAt: null,
278
220
  lastError: null
279
- }, { where: s(l("pipe", n.key), f("id", c.map((e) => e.id)), l("status", "delivering"), l("claimedBy", this.opts.replicaId)) }), n.stats.delivered += c.length, n.stats.lastDeliveryAtMs = p;
221
+ }, { where: c(d("pipe", n.key), m("id", s.map((e) => e.id)), d("status", "delivering"), d("claimedBy", this.opts.replicaId)) }), n.stats.delivered += s.length, n.stats.lastDeliveryAtMs = u;
280
222
  return;
281
223
  }
282
- let m = d.left.message;
283
- for (let t of c) {
224
+ let f = l.left.message;
225
+ for (let t of s) {
284
226
  let r = t.attempts + 1;
285
227
  if (r >= this.opts.maxAttempts) await e.update(C, t.id, {
286
228
  status: "dead",
287
229
  attempts: r,
288
230
  claimedBy: null,
289
231
  claimedAt: null,
290
- lastError: m
232
+ lastError: f
291
233
  }), n.stats.dead += 1;
292
234
  else {
293
- let n = V(r, {
235
+ let n = L(r, {
294
236
  baseMs: this.opts.backoffBaseMs,
295
237
  maxMs: this.opts.backoffMaxMs,
296
238
  ...this.opts.random === void 0 ? {} : { random: this.opts.random }
@@ -298,10 +240,10 @@ var x = class extends i.TaggedError()("CdcConfigError", { message: i.String }) {
298
240
  await e.update(C, t.id, {
299
241
  status: "pending",
300
242
  attempts: r,
301
- nextAttemptAt: new Date(p + n),
243
+ nextAttemptAt: new Date(u + n),
302
244
  claimedBy: null,
303
245
  claimedAt: null,
304
- lastError: m
246
+ lastError: f
305
247
  });
306
248
  }
307
249
  n.stats.failed += 1;
@@ -309,13 +251,13 @@ var x = class extends i.TaggedError()("CdcConfigError", { message: i.String }) {
309
251
  this.opts.warn?.("cdc-out delivery failed (durable retry scheduled)", {
310
252
  pipe: n.key,
311
253
  sink: n.sink.name,
312
- records: c.length,
313
- attempts: c[0] === void 0 ? 1 : c[0].attempts + 1,
254
+ records: s.length,
255
+ attempts: s[0] === void 0 ? 1 : s[0].attempts + 1,
314
256
  maxAttempts: this.opts.maxAttempts,
315
- reason: m
257
+ reason: f
316
258
  });
317
259
  }
318
- }, U = (e = "memory") => {
260
+ }, R = (e = "memory") => {
319
261
  let t = [];
320
262
  return {
321
263
  name: e,
@@ -329,7 +271,7 @@ var x = class extends i.TaggedError()("CdcConfigError", { message: i.String }) {
329
271
  return t.flat();
330
272
  }
331
273
  };
332
- }, W = (e, t) => {
274
+ }, z = (e, t) => {
333
275
  let n;
334
276
  try {
335
277
  n = new URL(e).host;
@@ -356,14 +298,14 @@ var x = class extends i.TaggedError()("CdcConfigError", { message: i.String }) {
356
298
  });
357
299
  }
358
300
  };
359
- }, ie = 100, ae = 5, oe = 200, se = 3e4, ce = 250, le = 1e4, ue = 15e3, de = 72, fe = 6e4, pe = 1e4, me = 1e3, G = (e) => {
301
+ }, oe = 100, se = 5, ce = 200, le = 3e4, ue = 250, de = 1e4, B = 15e3, fe = 72, pe = 6e4, me = 1e4, he = 1e3, V = (e) => {
360
302
  throw new x({ message: e });
361
- }, he = (e) => {
362
- (e.sinks === void 0 || e.sinks.length === 0) && G("cdcOutPlugin: at least one sink config is required");
303
+ }, H = (e) => {
304
+ (e.sinks === void 0 || e.sinks.length === 0) && V("cdcOutPlugin: at least one sink config is required");
363
305
  let t = /* @__PURE__ */ new Set();
364
306
  e.sinks.forEach((e, n) => {
365
- (typeof e.table != "string" || e.table.length === 0) && G(`cdcOutPlugin: sinks[${n}].table is required`), e.table.startsWith("_voltro_cdcout") && G(`cdcOutPlugin: sinks[${n}].table '${e.table}' is the plugin's own delivery state — it cannot be mirrored`), t.has(e.table) && G(`cdcOutPlugin: duplicate sink config for table '${e.table}' — one sink config per table per instance; for multiple sinks on one table wire a second cdcOutPlugin({ name: '…' }) instance`), t.add(e.table), (e.sink === void 0 || typeof e.sink.deliver != "function") && G(`cdcOutPlugin: sinks[${n}] (table '${e.table}') needs a sink with a deliver(batch) function`), e.batchSize !== void 0 && e.batchSize <= 0 && G(`cdcOutPlugin: sinks[${n}] (table '${e.table}') batchSize must be > 0`);
366
- }), e.name !== void 0 && !/^[a-zA-Z0-9-]+$/.test(e.name) && G(`cdcOutPlugin: name '${e.name}' must match [a-zA-Z0-9-]+ (it lands in the inspect URL)`);
307
+ (typeof e.table != "string" || e.table.length === 0) && V(`cdcOutPlugin: sinks[${n}].table is required`), e.table.startsWith("_voltro_cdcout") && V(`cdcOutPlugin: sinks[${n}].table '${e.table}' is the plugin's own delivery state — it cannot be mirrored`), t.has(e.table) && V(`cdcOutPlugin: duplicate sink config for table '${e.table}' — one sink config per table per instance; for multiple sinks on one table wire a second cdcOutPlugin({ name: '…' }) instance`), t.add(e.table), (e.sink === void 0 || typeof e.sink.deliver != "function") && V(`cdcOutPlugin: sinks[${n}] (table '${e.table}') needs a sink with a deliver(batch) function`), e.batchSize !== void 0 && e.batchSize <= 0 && V(`cdcOutPlugin: sinks[${n}] (table '${e.table}') batchSize must be > 0`);
308
+ }), e.name !== void 0 && !/^[a-zA-Z0-9-]+$/.test(e.name) && V(`cdcOutPlugin: name '${e.name}' must match [a-zA-Z0-9-]+ (it lands in the inspect URL)`);
367
309
  let n = [
368
310
  ["maxAttempts", e.maxAttempts],
369
311
  ["backoffBaseMs", e.backoffBaseMs],
@@ -376,10 +318,10 @@ var x = class extends i.TaggedError()("CdcConfigError", { message: i.String }) {
376
318
  ["handoffBufferMs", e.handoffBufferMs],
377
319
  ["handoffBufferSize", e.handoffBufferSize]
378
320
  ];
379
- for (let [e, t] of n) t !== void 0 && t < 1 && G(`cdcOutPlugin: ${e} must be >= 1`);
380
- let r = e.leaseTtlMs ?? ue;
381
- e.dedupWindowMs !== void 0 && e.dedupWindowMs <= r && G(`cdcOutPlugin: dedupWindowMs (${e.dedupWindowMs}) must exceed leaseTtlMs (${r}) — a claim that expires inside a leadership handoff lets two replicas enqueue the same change`);
382
- }, ge = (e, t) => {
321
+ for (let [e, t] of n) t !== void 0 && t < 1 && V(`cdcOutPlugin: ${e} must be >= 1`);
322
+ let r = e.leaseTtlMs ?? B;
323
+ e.dedupWindowMs !== void 0 && e.dedupWindowMs <= r && V(`cdcOutPlugin: dedupWindowMs (${e.dedupWindowMs}) must exceed leaseTtlMs (${r}) — a claim that expires inside a leadership handoff lets two replicas enqueue the same change`);
324
+ }, U = (e, t) => {
383
325
  if (t.filter !== void 0 && !t.filter(e)) return null;
384
326
  if (e.op === "delete") {
385
327
  let t = String(e.old?.id ?? "");
@@ -399,7 +341,7 @@ var x = class extends i.TaggedError()("CdcConfigError", { message: i.String }) {
399
341
  key: r,
400
342
  data: t.map ? t.map(n) : n
401
343
  };
402
- }, K = (e, t) => `${e}:${t.table}:${t.sink.name}`, q = (e, t, n) => ({
344
+ }, W = (e, t) => `${e}:${t.table}:${t.sink.name}`, G = (e, t, n) => ({
403
345
  id: O(),
404
346
  pipe: t,
405
347
  sourceTable: e.table,
@@ -414,10 +356,10 @@ var x = class extends i.TaggedError()("CdcConfigError", { message: i.String }) {
414
356
  createdAt: new Date(n),
415
357
  deliveredAt: null,
416
358
  lastError: null
417
- }), J = async (e, t, n, r) => {
418
- let i = K(r?.instanceName ?? "default", t), a = (r?.now ?? Date.now)(), o = n.flatMap((e) => {
359
+ }), K = async (e, t, n, r) => {
360
+ let i = W(r?.instanceName ?? "default", t), a = (r?.now ?? Date.now)(), o = n.flatMap((e) => {
419
361
  let n = String(e.id ?? "");
420
- return n === "" ? [] : [q({
362
+ return n === "" ? [] : [G({
421
363
  table: t.table,
422
364
  op: "insert",
423
365
  key: n,
@@ -425,19 +367,19 @@ var x = class extends i.TaggedError()("CdcConfigError", { message: i.String }) {
425
367
  }, i, a)];
426
368
  });
427
369
  return o.length > 0 && await e.insertMany(C, o), o.length;
428
- }, _e = "@voltro/plugin-cdc-out", Y = (i) => {
429
- he(i);
430
- let u = i.name ?? "default", d = o({
431
- base: _e,
370
+ }, q = "@voltro/plugin-cdc-out", J = (i) => {
371
+ H(i);
372
+ let f = i.name ?? "default", p = o({
373
+ base: q,
432
374
  alias: i.alias,
433
375
  instance: i.name
434
- }), p = `cdcout-${Math.random().toString(36).slice(2, 10)}`, m = i.maxAttempts ?? ae, g = i.sweepIntervalMs ?? ce, _ = i.leaseTtlMs ?? ue, v = i.dedupWindowMs ?? Math.max(fe, 4 * _), y = i.handoffBufferMs ?? v, b = i.handoffBufferSize ?? pe, x = /* @__PURE__ */ new Map();
435
- for (let e of i.sinks) x.set(e.table, {
376
+ }), h = `cdcout-${Math.random().toString(36).slice(2, 10)}`, te = i.maxAttempts ?? se, _ = i.sweepIntervalMs ?? ue, v = i.leaseTtlMs ?? B, y = i.dedupWindowMs ?? Math.max(pe, 4 * v), b = i.handoffBufferMs ?? y, x = i.handoffBufferSize ?? me, S = /* @__PURE__ */ new Map();
377
+ for (let e of i.sinks) S.set(e.table, {
436
378
  config: e,
437
379
  enginePipe: {
438
- key: K(u, e),
380
+ key: W(f, e),
439
381
  sink: e.sink,
440
- batchSize: e.batchSize ?? ie,
382
+ batchSize: e.batchSize ?? oe,
441
383
  busy: !1,
442
384
  stats: {
443
385
  delivered: 0,
@@ -447,38 +389,38 @@ var x = class extends i.TaggedError()("CdcConfigError", { message: i.String }) {
447
389
  }
448
390
  }
449
391
  });
450
- let S = new M({
451
- key: u,
452
- holder: p,
453
- ttlMs: _
454
- }), w = new H({
455
- pipes: [...x.values()].map((e) => e.enginePipe),
456
- replicaId: p,
457
- maxAttempts: m,
458
- backoffBaseMs: i.backoffBaseMs ?? oe,
459
- backoffMaxMs: i.backoffMaxMs ?? se,
460
- deliveryTimeoutMs: i.deliveryTimeoutMs ?? le,
461
- isLeader: () => S.isLeader,
462
- warn: (e, t) => D(e, t)
463
- }), E = { current: null }, D = () => void 0, O = 0, j, N, I = new L({
464
- windowMs: 2 * v,
465
- maxEntries: Math.max(b, 1e3) * 4
466
- }), z = new R({
467
- windowMs: y,
468
- maxEntries: b
469
- }), B = !1, V = 0, U = !1, W = null, G = 0, J = !1, Y = !1, X = [], Z = 0;
470
- ee({
392
+ let w = new N({
393
+ key: f,
394
+ holder: h,
395
+ ttlMs: v
396
+ }), E = new ae({
397
+ pipes: [...S.values()].map((e) => e.enginePipe),
398
+ replicaId: h,
399
+ maxAttempts: te,
400
+ backoffBaseMs: i.backoffBaseMs ?? ce,
401
+ backoffMaxMs: i.backoffMaxMs ?? le,
402
+ deliveryTimeoutMs: i.deliveryTimeoutMs ?? de,
403
+ isLeader: () => w.isLeader,
404
+ warn: (e, t) => O(e, t)
405
+ }), D = { current: null }, O = () => void 0, M = 0, F, I, L = new s({
406
+ windowMs: 2 * y,
407
+ maxEntries: Math.max(x, 1e3) * 4
408
+ }), R = new P({
409
+ windowMs: b,
410
+ maxEntries: x
411
+ }), z = !1, V = 0, K = !1, J = null, Y = 0, ge = !1, _e = !1, X = [], Z = 0;
412
+ ne({
471
413
  source: "plugin",
472
414
  table: C,
473
415
  timeColumn: "createdAt",
474
- ttlMs: i.retentionHours === void 0 ? te(process.env.CDCOUT_RETENTION_HOURS, de) : i.retentionHours * 36e5,
475
- where: f("status", ["delivered", "dead"]),
416
+ ttlMs: i.retentionHours === void 0 ? re(process.env.CDCOUT_RETENTION_HOURS, fe) : i.retentionHours * 36e5,
417
+ where: m("status", ["delivered", "dead"]),
476
418
  label: "cdc-out outbox (delivered/dead)"
477
- }), ee({
419
+ }), ne({
478
420
  source: "plugin",
479
421
  table: T,
480
422
  timeColumn: "seenAt",
481
- ttlMs: v,
423
+ ttlMs: y,
482
424
  label: "cdc-out enqueue claims (dedup window)"
483
425
  });
484
426
  let ve = /* @__PURE__ */ new Set();
@@ -503,62 +445,62 @@ var x = class extends i.TaggedError()("CdcConfigError", { message: i.String }) {
503
445
  return await Promise.all(be.map(async (r) => {
504
446
  let i = await e.query({
505
447
  table: C,
506
- predicate: s(l("pipe", t), l("status", r)),
448
+ predicate: c(d("pipe", t), d("status", r)),
507
449
  order: [],
508
450
  take: void 0,
509
451
  skip: void 0,
510
452
  projection: void 0,
511
- aggregations: [c("n")]
453
+ aggregations: [u("n")]
512
454
  });
513
455
  n[r] = Number(i[0]?.n ?? 0);
514
456
  })), n;
515
457
  }, Se = (e) => {
516
458
  let t = Date.now();
517
- t - O > 6e4 && (O = t, D("cdc-out: no DataStore bound — change events are NOT being mirrored", { table: e }));
459
+ t - M > 6e4 && (M = t, O("cdc-out: no DataStore bound — change events are NOT being mirrored", { table: e }));
518
460
  }, Ce = async (e, t, n) => (await e.insertIgnore(T, {
519
- id: k(),
461
+ id: ie(),
520
462
  pipe: t,
521
463
  changeKey: n,
522
- claimedBy: p,
464
+ claimedBy: h,
523
465
  seenAt: /* @__PURE__ */ new Date()
524
- }, { conflictColumns: ["pipe", "changeKey"] })).claimedBy === p, Q = async () => {
525
- if (B) return;
526
- let e = E.current;
466
+ }, { conflictColumns: ["pipe", "changeKey"] })).claimedBy === h, Q = async () => {
467
+ if (z) return;
468
+ let e = D.current;
527
469
  if (e !== null) {
528
- B = !0;
470
+ z = !0;
529
471
  try {
530
472
  for (;;) {
531
- let t = z.nextPending();
532
- if (t === void 0 || !S.isLeader) return;
533
- await Ce(e, t.pipe, t.changeKey) && await e.insert(C, q(t.change, t.pipe, Date.now())), t.enqueued = !0;
473
+ let t = R.nextPending();
474
+ if (t === void 0 || !w.isLeader) return;
475
+ await Ce(e, t.pipe, t.changeKey) && await e.insert(C, G(t.change, t.pipe, Date.now())), t.enqueued = !0;
534
476
  }
535
477
  } finally {
536
- B = !1;
478
+ z = !1;
537
479
  }
538
480
  }
539
481
  }, we = (e) => {
540
- if (J = !0, !U) {
541
- for (X.push(e); X.length > b;) X.shift(), Z += 1;
482
+ if (ge = !0, !K) {
483
+ for (X.push(e); X.length > x;) X.shift(), Z += 1;
542
484
  return;
543
485
  }
544
- z.append({
486
+ R.append({
545
487
  pipe: e.pipe,
546
- changeKey: F(e.digest, I.next(e.digest, e.observedAtMs)),
488
+ changeKey: l(e.digest, L.next(e.digest, e.observedAtMs)),
547
489
  change: e.change,
548
490
  observedAtMs: e.observedAtMs,
549
491
  enqueued: !1
550
492
  });
551
493
  }, Te = (e, t) => {
552
- I.seedFromChangeKeys(e, t), U = !0;
494
+ L.seedFromChangeKeys(e, t), K = !0;
553
495
  for (let e of X.splice(0, X.length)) we(e);
554
496
  }, $ = async () => {
555
- if (U) return;
556
- let e = E.current;
497
+ if (K) return;
498
+ let e = D.current;
557
499
  if (e !== null) {
558
- if (W === null) {
559
- if (G !== 0 && Date.now() - G < me) return;
560
- let t = !J;
561
- W = (async () => {
500
+ if (J === null) {
501
+ if (Y !== 0 && Date.now() - Y < he) return;
502
+ let t = !ge;
503
+ J = (async () => {
562
504
  let n = await e.query({
563
505
  table: T,
564
506
  predicate: void 0,
@@ -566,28 +508,28 @@ var x = class extends i.TaggedError()("CdcConfigError", { message: i.String }) {
566
508
  column: "seenAt",
567
509
  direction: "desc"
568
510
  }],
569
- take: Math.max(b, 1e3),
511
+ take: Math.max(x, 1e3),
570
512
  skip: void 0,
571
513
  projection: ["changeKey"]
572
514
  });
573
- !t && !Y && (Y = !0, D("cdc-out: occurrence counters start from this replica's own stream — the claims snapshot was only reachable after it began observing, and counting it could duplicate a change", { replicaId: p })), Te(t ? n.flatMap((e) => typeof e.changeKey == "string" ? [e.changeKey] : []) : [], Date.now());
515
+ !t && !_e && (_e = !0, O("cdc-out: occurrence counters start from this replica's own stream — the claims snapshot was only reachable after it began observing, and counting it could duplicate a change", { replicaId: h })), Te(t ? n.flatMap((e) => typeof e.changeKey == "string" ? [e.changeKey] : []) : [], Date.now());
574
516
  })().catch((e) => {
575
- G = Date.now(), D("cdc-out: could not seed change-occurrence counters — enqueue is held until it lands", { reason: String(e) });
517
+ Y = Date.now(), O("cdc-out: could not seed change-occurrence counters — enqueue is held until it lands", { reason: String(e) });
576
518
  }).finally(() => {
577
- W = null;
519
+ J = null;
578
520
  });
579
521
  }
580
- await W;
522
+ await J;
581
523
  }
582
524
  }, Ee = (e) => {
583
- let t = e - y;
525
+ let t = e - b;
584
526
  for (; X.length > 0 && X[0].observedAtMs < t;) X.shift(), Z += 1;
585
527
  }, De = async (e, t) => {
586
- t - V < Math.max(1e3, Math.floor(v / 4)) || (V = t, await e.deleteMany(T, { where: h("seenAt", new Date(t - v)) }));
528
+ t - V < Math.max(1e3, Math.floor(y / 4)) || (V = t, await e.deleteMany(T, { where: g("seenAt", new Date(t - y)) }));
587
529
  };
588
530
  return a({
589
- name: d,
590
- baseName: _e,
531
+ name: p,
532
+ baseName: q,
591
533
  description: "Declarative reverse-ETL: mirror table changes outward to external sinks (webhook, Kafka via @voltro/plugin-queue's kafkaSink, plus a CdcSink interface for custom sinks) through a durable outbox — ordered per pipe, at-least-once from enqueue, retried with backoff, dead-lettered.",
592
534
  permissions: ye,
593
535
  declaredEnv: [{
@@ -598,23 +540,23 @@ var x = class extends i.TaggedError()("CdcConfigError", { message: i.String }) {
598
540
  example: "72"
599
541
  }],
600
542
  extendSchema: { tables: [
601
- A,
602
- re,
603
- ne
543
+ k,
544
+ j,
545
+ A
604
546
  ] },
605
547
  bindDataStore: (e) => {
606
- E.current = e, $();
548
+ D.current = e, $();
607
549
  },
608
550
  inspectEndpoints: [{
609
551
  method: "GET",
610
552
  path: "/sinks",
611
553
  description: "Configured sinks + per-pipe outbox counts and delivery stats.",
612
554
  handler: () => t.tryPromise(async () => {
613
- let e = E.current;
555
+ let e = D.current;
614
556
  return {
615
557
  kind: "json",
616
558
  data: {
617
- sinks: await Promise.all([...x.values()].map(async ({ config: t, enginePipe: n }) => {
559
+ sinks: await Promise.all([...S.values()].map(async ({ config: t, enginePipe: n }) => {
618
560
  let r = e === null ? {
619
561
  pending: 0,
620
562
  delivering: 0,
@@ -631,14 +573,14 @@ var x = class extends i.TaggedError()("CdcConfigError", { message: i.String }) {
631
573
  };
632
574
  })),
633
575
  handoff: {
634
- leader: S.isLeader,
635
- seeded: U,
576
+ leader: w.isLeader,
577
+ seeded: K,
636
578
  awaitingSeed: X.length,
637
- buffered: z.size,
638
- pending: z.pendingCount(),
639
- dropped: z.droppedPending + Z,
640
- windowMs: y,
641
- dedupWindowMs: v
579
+ buffered: R.size,
580
+ pending: R.pendingCount(),
581
+ dropped: R.droppedPending + Z,
582
+ windowMs: b,
583
+ dedupWindowMs: y
642
584
  }
643
585
  }
644
586
  };
@@ -648,7 +590,7 @@ var x = class extends i.TaggedError()("CdcConfigError", { message: i.String }) {
648
590
  path: "/dead-letter",
649
591
  description: "Outbox rows that exhausted maxAttempts (newest first, capped at 100).",
650
592
  handler: () => t.tryPromise(async () => {
651
- let e = E.current;
593
+ let e = D.current;
652
594
  return e === null ? {
653
595
  kind: "json",
654
596
  data: { rows: [] }
@@ -656,7 +598,7 @@ var x = class extends i.TaggedError()("CdcConfigError", { message: i.String }) {
656
598
  kind: "json",
657
599
  data: { rows: (await e.query({
658
600
  table: C,
659
- predicate: l("status", "dead"),
601
+ predicate: d("status", "dead"),
660
602
  order: [{
661
603
  column: "id",
662
604
  direction: "desc"
@@ -678,12 +620,12 @@ var x = class extends i.TaggedError()("CdcConfigError", { message: i.String }) {
678
620
  })
679
621
  }],
680
622
  onChangeEvent: (e) => {
681
- let n = x.get(e.table);
623
+ let n = S.get(e.table);
682
624
  if (n === void 0) return t.void;
683
- let r = ge(e, n.config);
625
+ let r = U(e, n.config);
684
626
  if (r === null) return t.void;
685
627
  if (e.changeScope === "fleet") {
686
- let i = Date.now(), a = P({
628
+ let i = Date.now(), a = ee({
687
629
  pipe: n.enginePipe.key,
688
630
  op: r.op,
689
631
  key: r.key,
@@ -695,44 +637,44 @@ var x = class extends i.TaggedError()("CdcConfigError", { message: i.String }) {
695
637
  digest: a,
696
638
  change: r,
697
639
  observedAtMs: i
698
- }), E.current === null ? (Se(e.table), t.void) : t.promise(() => $()).pipe(t.flatMap(() => S.isLeader ? t.tryPromise(() => Q()) : t.void));
640
+ }), D.current === null ? (Se(e.table), t.void) : t.promise(() => $()).pipe(t.flatMap(() => w.isLeader ? t.tryPromise(() => Q()) : t.void));
699
641
  }
700
642
  if (e.origin === "injected") return t.void;
701
- let i = E.current;
643
+ let i = D.current;
702
644
  if (i === null) return Se(e.table), t.void;
703
- let a = q(r, n.enginePipe.key, Date.now());
645
+ let a = G(r, n.enginePipe.key, Date.now());
704
646
  return t.tryPromise(() => i.insert(C, a).then(() => void 0));
705
647
  },
706
648
  onActivate: (n) => t.sync(() => {
707
- D = (e, t) => n.logger.warn(e, t);
649
+ O = (e, t) => n.logger.warn(e, t);
708
650
  let i = t.suspend(() => {
709
- let e = E.current;
651
+ let e = D.current;
710
652
  return e === null ? t.void : t.promise(async () => {
711
653
  await $();
712
- let t = S.isLeader;
713
- await S.heartbeat(e) && !t && await Q().catch((e) => {
714
- D("cdc-out: handoff drain failed (retried on the next sweep)", { reason: String(e) });
654
+ let t = w.isLeader;
655
+ await w.heartbeat(e) && !t && await Q().catch((e) => {
656
+ O("cdc-out: handoff drain failed (retried on the next sweep)", { reason: String(e) });
715
657
  });
716
658
  });
717
- }).pipe(t.repeat(r.spaced(e.millis(Math.max(1, Math.floor(_ / 3)))))), a = t.suspend(() => {
718
- let e = E.current;
659
+ }).pipe(t.repeat(r.spaced(e.millis(Math.max(1, Math.floor(v / 3)))))), a = t.suspend(() => {
660
+ let e = D.current;
719
661
  return e === null ? t.void : t.tryPromise(async () => {
720
662
  let t = Date.now();
721
- await $(), Ee(t), z.prune(t), I.prune(t), await De(e, t), S.isLeader && await Q(), await w.tick(e);
722
- }).pipe(t.catchAll((e) => t.sync(() => D("cdc-out worker sweep failed", { reason: String(e) }))));
723
- }).pipe(t.repeat(r.spaced(e.millis(g))));
724
- N = t.runFork(i), j = t.runFork(a), n.logger.info("cdc-out active", {
725
- tables: [...x.keys()],
726
- replicaId: p,
663
+ await $(), Ee(t), R.prune(t), L.prune(t), await De(e, t), w.isLeader && await Q(), await E.tick(e);
664
+ }).pipe(t.catchAll((e) => t.sync(() => O("cdc-out worker sweep failed", { reason: String(e) }))));
665
+ }).pipe(t.repeat(r.spaced(e.millis(_))));
666
+ I = t.runFork(i), F = t.runFork(a), n.logger.info("cdc-out active", {
667
+ tables: [...S.keys()],
668
+ replicaId: h,
727
669
  outbox: C
728
670
  });
729
671
  }),
730
672
  onDeactivate: () => t.gen(function* () {
731
- j !== void 0 && (yield* n.interrupt(j)), N !== void 0 && (yield* n.interrupt(N)), j = void 0, N = void 0;
732
- let e = E.current;
733
- e !== null && (yield* t.promise(() => S.release(e)));
673
+ F !== void 0 && (yield* n.interrupt(F)), I !== void 0 && (yield* n.interrupt(I)), F = void 0, I = void 0;
674
+ let e = D.current;
675
+ e !== null && (yield* t.promise(() => w.release(e)));
734
676
  })
735
677
  });
736
678
  };
737
679
  //#endregion
738
- export { T as CDCOUT_CLAIMS_TABLE, w as CDCOUT_LEASES_TABLE, C as CDCOUT_OUTBOX_TABLE, x as CdcConfigError, S as CdcDeliveryError, M as CdcLeaseManager, H as CdcOutEngine, R as HandoffBuffer, L as OccurrenceCounter, N as canonicalJson, ne as cdcOutClaimsTable, re as cdcOutLeasesTable, A as cdcOutOutboxTable, Y as cdcOutPlugin, P as changeDigest, F as composeChangeKey, z as deliverOnce, J as enqueueBackfill, ge as mapChange, U as memorySink, k as nextClaimId, O as nextOutboxId, q as outboxRowFor, I as parseChangeKey, K as pipeKey, V as retryDelayMs, he as validateCdcOutConfig, W as webhookSink };
680
+ export { T as CDCOUT_CLAIMS_TABLE, w as CDCOUT_LEASES_TABLE, C as CDCOUT_OUTBOX_TABLE, x as CdcConfigError, S as CdcDeliveryError, N as CdcLeaseManager, ae as CdcOutEngine, P as HandoffBuffer, A as cdcOutClaimsTable, j as cdcOutLeasesTable, k as cdcOutOutboxTable, J as cdcOutPlugin, F as deliverOnce, K as enqueueBackfill, U as mapChange, R as memorySink, ie as nextClaimId, O as nextOutboxId, G as outboxRowFor, W as pipeKey, L as retryDelayMs, H as validateCdcOutConfig, z as webhookSink };