@voltro/runtime 0.86.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.
Files changed (70) hide show
  1. package/CHANGELOG.md +5321 -0
  2. package/README.md +43 -0
  3. package/THIRD-PARTY-NOTICES.md +860 -221
  4. package/dist/analyticsSink.js +8 -14
  5. package/dist/apiKeys.js +82 -78
  6. package/dist/apiRoleBucketSampler.js +64 -0
  7. package/dist/apiRoleWorkloadHistory.js +56 -0
  8. package/dist/apiRoleWorkloadMetrics.js +78 -0
  9. package/dist/apiRoleWorkloadStorage.js +95 -0
  10. package/dist/approvals.js +67 -74
  11. package/dist/authMiddlewareLayer.js +7 -6
  12. package/dist/bindEvent.js +48 -28
  13. package/dist/bootListener.js +106 -39
  14. package/dist/bootStoreCodec.js +38 -37
  15. package/dist/circuitBreaker.js +1 -72
  16. package/dist/composeAnalytics.js +31 -10
  17. package/dist/computedQueryCache.js +231 -0
  18. package/dist/connectionsFacade.js +32 -30
  19. package/dist/coordinatedSchedule.js +84 -78
  20. package/dist/dataStore.js +154 -121
  21. package/dist/declaredTablesGuard.js +2 -1
  22. package/dist/dispatcher.js +403 -328
  23. package/dist/effectStore.js +2 -2
  24. package/dist/engineClients.js +24 -0
  25. package/dist/externalizedTasks.js +19 -6
  26. package/dist/fieldCipher.js +109 -40
  27. package/dist/fieldKeyRegistry.js +295 -0
  28. package/dist/fieldKeyRekey.js +286 -0
  29. package/dist/fieldKeyScan.js +136 -0
  30. package/dist/fieldKeyring.js +77 -0
  31. package/dist/hot.d.ts +56 -0
  32. package/dist/hot.js +3 -0
  33. package/dist/hotDataStore.js +148 -0
  34. package/dist/hotDataStoreSnapshot.js +98 -0
  35. package/dist/hotDataStoreTables.js +18 -0
  36. package/dist/http-range.d.ts +53 -0
  37. package/dist/http-range.js +52 -0
  38. package/dist/httpClientSsrf.js +12 -37
  39. package/dist/httpClientTracing.js +12 -9
  40. package/dist/httpCompression.js +17 -17
  41. package/dist/httpInputStream.js +82 -0
  42. package/dist/httpRouteMounts.js +1 -0
  43. package/dist/idempotencyStore.js +92 -52
  44. package/dist/index.d.ts +1164 -451
  45. package/dist/index.js +143 -132
  46. package/dist/kvFacade.js +23 -17
  47. package/dist/memoryQueryJoins.js +16 -16
  48. package/dist/memorySetOperations.js +43 -0
  49. package/dist/metrics.js +38 -24
  50. package/dist/outbox.js +1 -1
  51. package/dist/readRecorder.js +35 -34
  52. package/dist/readScopePredicate.js +12 -0
  53. package/dist/replicatedDataStore.js +22 -4
  54. package/dist/rowFilter.js +56 -39
  55. package/dist/rpcConnections.js +8 -8
  56. package/dist/rpcServer.js +522 -449
  57. package/dist/rpcWorkloadAdmission.js +50 -0
  58. package/dist/rywPositionStore.js +82 -47
  59. package/dist/scheduler.js +6 -1
  60. package/dist/servePipeline.js +215 -146
  61. package/dist/setReadScopes.js +28 -0
  62. package/dist/snapshotValueCodec.js +46 -0
  63. package/dist/sourceResolution.js +34 -24
  64. package/dist/storeMiddleware.js +211 -209
  65. package/dist/subscriber.js +1 -0
  66. package/dist/tenantScope.js +8 -20
  67. package/dist/traceparent.js +8 -19
  68. package/dist/webSocketGatewayRuntime.js +111 -71
  69. package/dist/workflowRuntime.js +9 -9
  70. package/package.json +19 -7
@@ -7,144 +7,179 @@ import { crdtIncrementalCell as o } from "./crdtMerge.js";
7
7
  import { resolveDependentTables as s } from "./dependencyGraph.js";
8
8
  import { isEventRelevant as c, resolveRelevantFields as l } from "./relevantFields.js";
9
9
  import { recordSubscriptionActive as u } from "./metrics.js";
10
- import { applyRowFilterToDescriptor as d, rowFilterRegistered as f } from "./rowFilter.js";
11
- import { createDispatchWireStrip as p, stripForbiddenForWire as m } from "./wireColumnStrip.js";
12
- import { buildSubjectKey as h } from "./rywPositionStore.js";
13
- import { runWithRoutingContext as g } from "./replicatedDataStore.js";
10
+ import { buildSubjectKey as d } from "./rywPositionStore.js";
11
+ import { runWithRoutingContext as f } from "./replicatedDataStore.js";
12
+ import { applyRowFilterToDescriptor as p, rowFilterRegistered as m } from "./rowFilter.js";
13
+ import { createDispatchWireStrip as h, stripForbiddenForWire as g } from "./wireColumnStrip.js";
14
14
  import { committedChangeBatchOf as _ } from "@voltro/database";
15
- import { randomUUID as v } from "node:crypto";
16
- import { diffRows as y, isIdKeyed as b } from "@voltro/protocol";
17
- import { createLogger as x } from "@voltro/logger";
15
+ import { createHash as v, randomUUID as y } from "node:crypto";
16
+ import { diffRows as b, isIdKeyed as x } from "@voltro/protocol";
17
+ import { createLogger as S } from "@voltro/logger";
18
18
  //#region src/dispatcher.ts
19
- var S = /* @__PURE__ */ new Set(), C = x({ scope: "voltro:dispatcher" }), w = (e) => Array.isArray(e) ? e.length : e == null ? 0 : 1, T = (e) => typeof e == "object" && !!e && (e._tag === "ScopeError" || e._tag === "Unauthenticated" || e._tag === "RequestedTenantRefused"), E = 1e3, D = 3e4, O = Object.freeze({
19
+ var C = /* @__PURE__ */ new Set(), w = S({ scope: "voltro:dispatcher" }), T = (e) => Array.isArray(e) ? e.length : e == null ? 0 : 1, E = (e, n) => ({
20
+ ...e,
21
+ key: `${e.key}:${v("sha256").update(JSON.stringify(t(n))).digest("base64url")}`
22
+ }), D = (e) => typeof e == "object" && !!e && (e._tag === "ScopeError" || e._tag === "Unauthenticated" || e._tag === "RequestedTenantRefused"), O = 1e3, k = 3e4, A = Object.freeze({
20
23
  _tag: "QueryRecomputeFailed",
21
24
  message: "The live query could not be refreshed. Retrying automatically."
22
- }), k = class {
25
+ }), j = class {
23
26
  deps;
24
27
  subscriptionRegistry;
25
28
  matcher;
26
29
  subscriptions = /* @__PURE__ */ new Map();
27
30
  unsubscribeChange;
31
+ closed = !1;
32
+ operations = /* @__PURE__ */ new Set();
33
+ beginOperation() {
34
+ let e, t = new Promise((t) => {
35
+ e = t;
36
+ });
37
+ return this.operations.add(t), () => {
38
+ this.operations.delete(t), e();
39
+ };
40
+ }
41
+ interests = /* @__PURE__ */ new Map();
28
42
  dependentByTable = /* @__PURE__ */ new Map();
29
43
  computedSubs = /* @__PURE__ */ new Map();
30
44
  computedByTable = /* @__PURE__ */ new Map();
31
45
  reactive;
32
46
  rawReadWarned = /* @__PURE__ */ new Set();
47
+ readFences = /* @__PURE__ */ new WeakMap();
33
48
  changeEpoch = 0;
34
49
  committedReads = /* @__PURE__ */ new WeakMap();
35
50
  crdtColumnsFor(e) {
36
- return this.deps.crdtColumnsOf?.(e) ?? S;
51
+ return this.deps.crdtColumnsOf?.(e) ?? C;
37
52
  }
38
53
  constructor(t) {
39
- this.deps = t, this.matcher = new e(), this.subscriptionRegistry = new n(this.matcher), this.reactive = r(t.reactive), this.unsubscribeChange = t.store.onChange((e) => {
40
- let t = _(e), n = t === void 0 ? void 0 : this.committedReads.get(t);
41
- n === void 0 && (this.changeEpoch++, t !== void 0 && (n = {
42
- epoch: this.changeEpoch,
43
- reads: /* @__PURE__ */ new Map()
44
- }, this.committedReads.set(t, n))), this.handleChange(e, !1, n);
45
- });
54
+ this.deps = t, this.matcher = new e(), this.subscriptionRegistry = new n(this.matcher), this.reactive = r(t.reactive);
55
+ }
56
+ measureLoop(e) {
57
+ return this.deps.measureSync ? this.deps.measureSync(e) : e();
58
+ }
59
+ retainInterest(e, t) {
60
+ if (this.closed) throw Error("Dispatcher is closed");
61
+ this.interests.set(e, t);
62
+ try {
63
+ this.unsubscribeChange ??= this.deps.store.onChange((e) => {
64
+ let t = _(e), n = t === void 0 ? void 0 : this.committedReads.get(t);
65
+ n === void 0 && (this.changeEpoch++, t !== void 0 && (n = {
66
+ epoch: this.changeEpoch,
67
+ reads: /* @__PURE__ */ new Map()
68
+ }, this.committedReads.set(t, n))), this.closed || this.handleChange(e, !1, n);
69
+ });
70
+ } catch (t) {
71
+ throw this.interests.delete(e), t;
72
+ }
73
+ }
74
+ removeInterest(e) {
75
+ let t = this.interests.get(e);
76
+ if (t === void 0) return;
77
+ this.interests.delete(e);
78
+ let n = this.computedSubs.get(e);
79
+ n?.retryTimer !== void 0 && (clearTimeout(n.retryTimer), n.retryTimer = void 0);
80
+ let r = this.subscriptions.get(e) ?? n;
81
+ this.subscriptions.delete(e), this.computedSubs.delete(e), r !== void 0 && u(r.label, -1), this.subscriptionRegistry.unsubscribe(e);
82
+ for (let n of t) for (let t of [this.dependentByTable, this.computedByTable]) {
83
+ let r = t.get(n);
84
+ r?.delete(e), r?.size === 0 && t.delete(n);
85
+ }
86
+ this.interests.size === 0 && (this.unsubscribeChange?.(), this.unsubscribeChange = void 0);
46
87
  }
47
88
  warnUndeclaredRawReads(e, t) {
48
89
  if (t.length !== 0 && !this.rawReadWarned.has(e)) {
49
90
  this.rawReadWarned.add(e);
50
- for (let n of t) C.warn(`${e}: a raw SQL read in this live query declares no dependsOn, so no write can invalidate it — every subscriber keeps its first result until it reconnects. Declare the tables it reads: ctx.store.raw(fragment, { dependsOn: ['orders'] }) — or on the fragment itself. The read: ${n.sql}`, {
91
+ for (let n of t) w.warn(`${e}: a raw SQL read in this live query declares no dependsOn, so no write can invalidate it — every subscriber keeps its first result until it reconnects. Declare the tables it reads: ctx.store.raw(fragment, { dependsOn: ['orders'] }) — or on the fragment itself. The read: ${n.sql}`, {
51
92
  label: e,
52
93
  sql: n.sql
53
94
  });
54
95
  }
55
96
  }
56
- async subscribe(e, t, n, r, a, o, c, p, _ = !1) {
57
- let y = `sub_${v()}`, b = a ?? `subscription.${e.table}`, x = s(e);
58
- if (p === void 0 && f()) throw Error(`subscription "${b}" was opened without a row-filter resolver while a row filter is registered — this would read every row the filter exists to hide. Pass the resolver from \`makeDefaultRefilter(subject)\`, or pass one returning \`NO_ROW_FILTER\` to read unfiltered deliberately.`);
59
- let S = l(e);
60
- e.predicate !== void 0 && this.subscriptionRegistry.subscribe(y, e.table, e.predicate, r);
61
- let C = e.predicate !== void 0 && (e.dependentTables?.length ?? 0) === 0 && e.eager === void 0 && e.setOp === void 0 && e.ctes === void 0 && e.alias === void 0 && (e.joins?.length ?? 0) === 0 && x.size === 1 && x.has(e.table);
62
- for (let t of x) {
63
- if (C && t === e.table) continue;
64
- let n = this.dependentByTable.get(t);
65
- n === void 0 && (n = /* @__PURE__ */ new Set(), this.dependentByTable.set(t, n)), n.add(y);
66
- }
67
- let w = Date.now(), T = Array.from(x), E = () => {
68
- this.subscriptionRegistry.unsubscribe(y);
69
- for (let e of x) {
70
- let t = this.dependentByTable.get(e);
71
- t !== void 0 && (t.delete(y), t.size === 0 && this.dependentByTable.delete(e));
72
- }
73
- }, D, O;
97
+ async subscribe(e, t, n, r, a, o, c, h, _ = !1) {
98
+ let v = `sub_${y()}`, b = a ?? `subscription.${e.table}`, x = s(e);
99
+ this.retainInterest(v, x);
100
+ let S = this.beginOperation();
74
101
  try {
75
- D = p === void 0 ? e : d(await p(), e);
76
- let t = _ ? await g({
77
- subjectKey: h(n.subject.id, n.clientId),
78
- forcePrimary: !0
79
- }, () => this.deps.store.query(D)) : this.deps.cache !== void 0 && o !== void 0 ? await this.deps.cache.wrap(o, T, () => this.deps.store.query(D)) : await this.deps.store.query(D);
80
- if (c !== void 0) {
81
- let e = await c();
82
- if (e) throw e;
102
+ if (h === void 0 && m()) throw Error(`subscription "${b}" was opened without a row-filter resolver while a row filter is registered — this would read every row the filter exists to hide. Pass the resolver from \`makeDefaultRefilter(subject)\`, or pass one returning \`NO_ROW_FILTER\` to read unfiltered deliberately.`);
103
+ let a = l(e);
104
+ e.predicate !== void 0 && this.subscriptionRegistry.subscribe(v, e.table, e.predicate, r);
105
+ let s = e.predicate !== void 0 && (e.dependentTables?.length ?? 0) === 0 && e.eager === void 0 && e.setOp === void 0 && e.ctes === void 0 && e.alias === void 0 && (e.joins?.length ?? 0) === 0 && x.size === 1 && x.has(e.table);
106
+ for (let t of x) {
107
+ if (s && t === e.table) continue;
108
+ let n = this.dependentByTable.get(t);
109
+ n === void 0 && (n = /* @__PURE__ */ new Set(), this.dependentByTable.set(t, n)), n.add(v);
110
+ }
111
+ let y = Date.now(), S = Array.from(x), C = () => this.removeInterest(v), w, T;
112
+ try {
113
+ if (w = h === void 0 ? e : p(await h(), e), this.closed) throw Error("Dispatcher is closed");
114
+ let t = _ ? await f({
115
+ subjectKey: d(n.subject.id, n.clientId),
116
+ forcePrimary: !0
117
+ }, () => this.deps.store.query(w)) : this.deps.cache !== void 0 && o !== void 0 ? await this.deps.cache.wrap(E(o, w), S, () => f({
118
+ subjectKey: d(n.subject.id, n.clientId),
119
+ forcePrimary: !0
120
+ }, () => this.deps.store.query(w))) : await this.deps.store.query(w);
121
+ if (this.closed) throw Error("Dispatcher is closed");
122
+ if (c !== void 0) {
123
+ let e = await c();
124
+ if (e) throw e;
125
+ }
126
+ if (this.closed) throw Error("Dispatcher is closed");
127
+ T = g(w.table, t, n.subject);
128
+ } catch (e) {
129
+ throw C(), e;
83
130
  }
84
- O = m(D.table, t, n.subject);
131
+ this.warnUndeclaredRawReads(b, i().filter((e) => e.dependsOn.length === 0)), this.subscriptions.set(v, {
132
+ id: v,
133
+ descriptor: w,
134
+ baseDescriptor: e,
135
+ context: n,
136
+ emit: t,
137
+ label: b,
138
+ dependentTables: x,
139
+ relevantFields: a,
140
+ lastDelivered: T,
141
+ cacheBinding: o,
142
+ readFromPrimary: _,
143
+ reauthorize: c,
144
+ refilter: h,
145
+ memoKey: void 0
146
+ }), u(b, 1), t({
147
+ _tag: "snapshot",
148
+ revision: 0,
149
+ data: T
150
+ });
151
+ let D = Date.now();
152
+ return this.deps.recordDelivery?.({
153
+ traceId: n.traceId,
154
+ parentSpanId: n.spanId,
155
+ label: b,
156
+ kind: "snapshot",
157
+ startMs: y,
158
+ endMs: D,
159
+ durationMs: D - y,
160
+ rowCount: T.length
161
+ }), () => this.removeInterest(v);
85
162
  } catch (e) {
86
- throw E(), e;
163
+ throw this.removeInterest(v), e;
164
+ } finally {
165
+ S();
87
166
  }
88
- this.warnUndeclaredRawReads(b, i().filter((e) => e.dependsOn.length === 0)), this.subscriptions.set(y, {
89
- id: y,
90
- descriptor: D,
91
- baseDescriptor: e,
92
- context: n,
93
- emit: t,
94
- label: b,
95
- dependentTables: x,
96
- relevantFields: S,
97
- lastDelivered: O,
98
- cacheBinding: o,
99
- readFromPrimary: _,
100
- reauthorize: c,
101
- refilter: p,
102
- memoKey: void 0
103
- }), u(b, 1), t({
104
- _tag: "snapshot",
105
- revision: 0,
106
- data: O
107
- });
108
- let k = Date.now();
109
- return this.deps.recordDelivery?.({
110
- traceId: n.traceId,
111
- parentSpanId: n.spanId,
112
- label: b,
113
- kind: "snapshot",
114
- startMs: w,
115
- endMs: k,
116
- durationMs: k - w,
117
- rowCount: O.length
118
- }), () => {
119
- this.subscriptions.delete(y), u(b, -1), E();
120
- };
121
167
  }
122
168
  revokeSubscription(e, t, n = "guards no longer satisfied") {
123
- this.subscriptions.delete(e.id), u(e.label, -1), this.subscriptionRegistry.unsubscribe(e.id);
124
- for (let t of e.dependentTables) {
125
- let n = this.dependentByTable.get(t);
126
- n !== void 0 && (n.delete(e.id), n.size === 0 && this.dependentByTable.delete(t));
127
- }
128
- C.warn(`subscription revoked mid-stream — ${n}`, {
169
+ this.subscriptions.get(e.id) === e && (this.removeInterest(e.id), w.warn(`subscription revoked mid-stream — ${n}`, {
129
170
  id: e.id,
130
171
  label: e.label,
131
172
  traceId: e.context.traceId
132
173
  }), e.emit({
133
174
  _tag: "error",
134
175
  error: t
135
- });
176
+ }));
136
177
  }
137
178
  removeComputed(e) {
138
- if (this.computedSubs.get(e.id) !== e) return !1;
139
- this.computedSubs.delete(e.id), e.retryTimer !== void 0 && clearTimeout(e.retryTimer), e.retryTimer = void 0, u(e.label, -1);
140
- for (let t of e.tables) {
141
- let n = this.computedByTable.get(t);
142
- n !== void 0 && (n.delete(e.id), n.size === 0 && this.computedByTable.delete(t));
143
- }
144
- return !0;
179
+ return this.computedSubs.get(e.id) === e && (this.removeInterest(e.id), !0);
145
180
  }
146
181
  revokeComputed(e, t) {
147
- this.removeComputed(e) && (C.warn("computed subscription revoked mid-stream — guards no longer satisfied", {
182
+ this.removeComputed(e) && (w.warn("computed subscription revoked mid-stream — guards no longer satisfied", {
148
183
  id: e.id,
149
184
  label: e.label,
150
185
  traceId: e.context.traceId
@@ -154,108 +189,114 @@ var S = /* @__PURE__ */ new Set(), C = x({ scope: "voltro:dispatcher" }), w = (e
154
189
  }));
155
190
  }
156
191
  async subscribeComputed(e, t, n, r) {
157
- let o = `csub_${v()}`, s = r ?? "subscription.computed", c = i(), l = new Set(e.dependentTables);
192
+ let o = `csub_${y()}`, s = r ?? "subscription.computed", c = i(), l = new Set(e.dependentTables);
158
193
  for (let e of c) for (let t of e.dependsOn) l.add(t);
159
194
  let d = Array.from(l);
160
- this.warnUndeclaredRawReads(s, c.filter((e) => e.dependsOn.length === 0));
161
- let f = {
162
- id: o,
163
- recompute: () => a(e.recompute),
164
- tenantId: e.tenantId,
165
- emit: t,
166
- context: n,
167
- label: s,
168
- tables: d,
169
- lastJson: A(e.value),
170
- revision: 0,
171
- failed: !1,
172
- retryAttempts: 0,
173
- retryTimer: void 0,
174
- running: !1,
175
- rerunRequested: !1
176
- };
177
- this.computedSubs.set(o, f), u(f.label, 1);
178
- for (let e of d) {
179
- let t = this.computedByTable.get(e);
180
- t === void 0 && (t = /* @__PURE__ */ new Set(), this.computedByTable.set(e, t)), t.add(o);
195
+ this.retainInterest(o, l);
196
+ try {
197
+ this.warnUndeclaredRawReads(s, c.filter((e) => e.dependsOn.length === 0));
198
+ let r = {
199
+ id: o,
200
+ recompute: () => a(e.recompute),
201
+ tenantId: e.tenantId,
202
+ emit: t,
203
+ context: n,
204
+ label: s,
205
+ tables: d,
206
+ lastJson: M(e.value),
207
+ revision: 0,
208
+ failed: !1,
209
+ retryAttempts: 0,
210
+ retryTimer: void 0,
211
+ running: !1,
212
+ rerunRequested: !1
213
+ };
214
+ this.computedSubs.set(o, r), u(r.label, 1);
215
+ for (let e of d) {
216
+ let t = this.computedByTable.get(e);
217
+ t === void 0 && (t = /* @__PURE__ */ new Set(), this.computedByTable.set(e, t)), t.add(o);
218
+ }
219
+ let i = Date.now();
220
+ return t({
221
+ _tag: "snapshot",
222
+ revision: 0,
223
+ data: e.value
224
+ }), this.deps.recordDelivery?.({
225
+ traceId: n.traceId,
226
+ parentSpanId: n.spanId,
227
+ label: r.label,
228
+ kind: "snapshot",
229
+ startMs: i,
230
+ endMs: Date.now(),
231
+ durationMs: Date.now() - i,
232
+ rowCount: T(e.value)
233
+ }), () => {
234
+ this.removeComputed(r);
235
+ };
236
+ } catch (e) {
237
+ throw this.removeInterest(o), e;
181
238
  }
182
- let p = Date.now();
183
- return t({
184
- _tag: "snapshot",
185
- revision: 0,
186
- data: e.value
187
- }), this.deps.recordDelivery?.({
188
- traceId: n.traceId,
189
- parentSpanId: n.spanId,
190
- label: f.label,
191
- kind: "snapshot",
192
- startMs: p,
193
- endMs: Date.now(),
194
- durationMs: Date.now() - p,
195
- rowCount: w(e.value)
196
- }), () => {
197
- this.removeComputed(f);
198
- };
199
239
  }
200
240
  async recomputeComputed(e) {
201
- if (this.computedSubs.get(e.id) === e) {
202
- if (e.running) e.rerunRequested = !0;
203
- else {
204
- e.retryTimer !== void 0 && clearTimeout(e.retryTimer), e.retryTimer = void 0, e.running = !0;
205
- try {
206
- do {
207
- e.rerunRequested = !1;
208
- let t = Date.now(), n = await e.recompute();
209
- if (this.computedSubs.get(e.id) !== e) return;
210
- if (e.rerunRequested) continue;
211
- let r = A(n), i = e.failed;
212
- if (e.failed = !1, e.retryAttempts = 0, r === e.lastJson && !i) return;
213
- e.lastJson = r, e.revision += 1;
214
- let a = Date.now();
215
- e.emit({
216
- _tag: "snapshot",
217
- revision: e.revision,
218
- data: n
219
- }), this.deps.recordDelivery?.({
220
- traceId: e.context.traceId,
221
- parentSpanId: e.context.spanId,
222
- label: e.label,
223
- kind: "delta",
224
- startMs: t,
225
- endMs: a,
226
- durationMs: a - t,
227
- rowCount: w(n)
228
- }), this.deps.recordCost !== void 0 && this.deps.recordCost({
229
- tenantId: e.tenantId,
230
- subscriptionId: e.id,
231
- unit: "recompute",
232
- amount: 1,
233
- traceId: e.context.traceId
234
- });
235
- } while (e.rerunRequested);
236
- } catch (t) {
237
- if (this.computedSubs.get(e.id) !== e) return;
238
- if (T(t)) {
239
- this.revokeComputed(e, t);
240
- return;
241
- }
242
- e.failed || (e.failed = !0, e.emit({
243
- _tag: "error",
244
- error: O
245
- }), C.warn("computed recompute failed; retrying with last value marked stale", {
246
- id: e.id,
247
- label: e.label,
248
- traceId: e.context.traceId
249
- }, t));
250
- let n = Math.min(D, E * 2 ** Math.min(e.retryAttempts, 5));
251
- if (e.retryAttempts += 1, this.computedSubs.get(e.id) !== e) return;
252
- e.retryTimer = setTimeout(() => {
253
- e.retryTimer = void 0, this.recomputeComputed(e);
254
- }, n), e.retryTimer.unref();
255
- } finally {
256
- e.running = !1;
257
- }
241
+ if (this.closed || this.computedSubs.get(e.id) !== e) return;
242
+ if (e.running) {
243
+ e.rerunRequested = !0;
244
+ return;
245
+ }
246
+ e.retryTimer !== void 0 && clearTimeout(e.retryTimer), e.retryTimer = void 0, e.running = !0;
247
+ let t = this.beginOperation();
248
+ try {
249
+ do {
250
+ e.rerunRequested = !1;
251
+ let t = Date.now(), n = await e.recompute();
252
+ if (this.computedSubs.get(e.id) !== e) return;
253
+ if (e.rerunRequested) continue;
254
+ let r = this.measureLoop(() => M(n)), i = e.failed;
255
+ if (e.failed = !1, e.retryAttempts = 0, r === e.lastJson && !i) return;
256
+ e.lastJson = r, e.revision += 1;
257
+ let a = Date.now();
258
+ this.measureLoop(() => e.emit({
259
+ _tag: "snapshot",
260
+ revision: e.revision,
261
+ data: n
262
+ })), this.deps.recordDelivery?.({
263
+ traceId: e.context.traceId,
264
+ parentSpanId: e.context.spanId,
265
+ label: e.label,
266
+ kind: "delta",
267
+ startMs: t,
268
+ endMs: a,
269
+ durationMs: a - t,
270
+ rowCount: T(n)
271
+ }), this.deps.recordCost !== void 0 && this.deps.recordCost({
272
+ tenantId: e.tenantId,
273
+ subscriptionId: e.id,
274
+ unit: "recompute",
275
+ amount: 1,
276
+ traceId: e.context.traceId
277
+ });
278
+ } while (e.rerunRequested);
279
+ } catch (t) {
280
+ if (this.computedSubs.get(e.id) !== e) return;
281
+ if (D(t)) {
282
+ this.revokeComputed(e, t);
283
+ return;
258
284
  }
285
+ e.failed || (e.failed = !0, e.emit({
286
+ _tag: "error",
287
+ error: A
288
+ }), w.warn("computed recompute failed; retrying with last value marked stale", {
289
+ id: e.id,
290
+ label: e.label,
291
+ traceId: e.context.traceId
292
+ }, t));
293
+ let n = Math.min(k, O * 2 ** Math.min(e.retryAttempts, 5));
294
+ if (e.retryAttempts += 1, this.computedSubs.get(e.id) !== e) return;
295
+ e.retryTimer = setTimeout(() => {
296
+ e.retryTimer = void 0, this.recomputeComputed(e);
297
+ }, n), e.retryTimer.unref();
298
+ } finally {
299
+ e.running = !1, t();
259
300
  }
260
301
  }
261
302
  subscriptionSnapshot() {
@@ -268,15 +309,23 @@ var S = /* @__PURE__ */ new Set(), C = x({ scope: "voltro:dispatcher" }), w = (e
268
309
  return this.subscriptionRegistry.onChange(e);
269
310
  }
270
311
  close() {
271
- this.unsubscribeChange();
272
- for (let e of this.computedSubs.values()) this.removeComputed(e);
312
+ this.closed = !0;
313
+ for (let e of this.interests.keys()) this.removeInterest(e);
314
+ this.unsubscribeChange?.(), this.unsubscribeChange = void 0;
315
+ }
316
+ async closeAndDrain() {
317
+ try {
318
+ this.close();
319
+ } finally {
320
+ for (; this.operations.size > 0;) await Promise.all(this.operations);
321
+ }
273
322
  }
274
323
  async refreshAll(e) {
275
324
  let t = /* @__PURE__ */ new Set();
276
325
  for (let e of this.subscriptions.values()) t.add(e.descriptor.table);
277
326
  for (let e of this.computedByTable.keys()) t.add(e);
278
327
  if (t.size !== 0) {
279
- C.warn("refreshing every live subscription", {
328
+ w.warn("refreshing every live subscription", {
280
329
  reason: e,
281
330
  tables: t.size,
282
331
  subscriptions: this.subscriptions.size
@@ -290,158 +339,184 @@ var S = /* @__PURE__ */ new Set(), C = x({ scope: "voltro:dispatcher" }), w = (e
290
339
  }
291
340
  }
292
341
  async handleChange(e, n = !1, r) {
293
- n && this.changeEpoch++;
294
- let i = this.computedByTable.get(e.table);
295
- if (i !== void 0 && i.size > 0) for (let e of i) {
296
- let t = this.computedSubs.get(e);
297
- t !== void 0 && this.recomputeComputed(t);
298
- }
299
- let a = n || e.oversized === "tombstone" || e.oversized === "unrecovered", s = a ? [] : this.subscriptionRegistry.notify(e), l = /* @__PURE__ */ new Set();
300
- for (let e of s) l.add(e.subscriberId);
301
- if (a) for (let [t, n] of this.subscriptions) n.descriptor.table === e.table && l.add(t);
302
- let u = this.dependentByTable.get(e.table);
303
- if (u !== void 0) for (let e of u) l.add(e);
304
- if (C.trace("handleChange", {
305
- table: e.table,
306
- op: e.op,
307
- matcher: s.length,
308
- dependent: u?.size ?? 0,
309
- triggered: l.size
310
- }), l.size === 0) return;
311
- let f = Date.now(), _ = 0, v = /* @__PURE__ */ new Map(), x = l.size > 1 ? p() : void 0, w = /* @__PURE__ */ new Map(), T = /* @__PURE__ */ new Map(), E = async (i) => {
312
- let a = this.subscriptions.get(i);
313
- if (!a) {
314
- C.trace("missing subscription", { subscriberId: i });
315
- return;
316
- }
317
- if (!n && !c(e, a.relevantFields)) {
318
- _++;
319
- return;
342
+ if (this.closed) return;
343
+ let i = this.beginOperation();
344
+ try {
345
+ n && this.changeEpoch++;
346
+ let i = this.computedByTable.get(e.table);
347
+ if (i !== void 0 && i.size > 0) for (let e of i) {
348
+ let t = this.computedSubs.get(e);
349
+ t !== void 0 && this.recomputeComputed(t);
320
350
  }
321
- let s = Date.now();
322
- try {
323
- if (a.reauthorize !== void 0) {
324
- let e = await a.reauthorize();
325
- if (e) {
326
- this.revokeSubscription(a, e);
327
- return;
328
- }
351
+ let a = n || e.oversized === "tombstone" || e.oversized === "unrecovered", s = a ? [] : this.measureLoop(() => this.subscriptionRegistry.notify(e)), l = /* @__PURE__ */ new Set();
352
+ for (let e of s) l.add(e.subscriberId);
353
+ if (a) for (let [t, n] of this.subscriptions) n.descriptor.table === e.table && l.add(t);
354
+ let u = this.dependentByTable.get(e.table);
355
+ if (u !== void 0) for (let e of u) l.add(e);
356
+ if (w.trace("handleChange", {
357
+ table: e.table,
358
+ op: e.op,
359
+ matcher: s.length,
360
+ dependent: u?.size ?? 0,
361
+ triggered: l.size
362
+ }), l.size === 0) return;
363
+ let m = Date.now(), _ = 0, v = /* @__PURE__ */ new Map(), y = l.size > 1 ? h() : void 0, S = /* @__PURE__ */ new Map(), T = /* @__PURE__ */ new Map(), D = async (i) => {
364
+ let a = this.subscriptions.get(i);
365
+ if (!a) {
366
+ w.trace("missing subscription", { subscriberId: i });
367
+ return;
329
368
  }
330
- let n, c;
331
- if (a.refilter === void 0) n = a.descriptor, a.memoKey ??= JSON.stringify(t(n)), c = a.memoKey;
332
- else {
333
- let e;
334
- try {
335
- e = await a.refilter();
336
- } catch (e) {
337
- this.revokeSubscription(a, e, "row filter unavailable");
338
- return;
369
+ if (!n && !c(e, a.relevantFields)) {
370
+ _++;
371
+ return;
372
+ }
373
+ let s = Date.now();
374
+ try {
375
+ if (a.reauthorize !== void 0) {
376
+ let e = await a.reauthorize();
377
+ if (this.subscriptions.get(a.id) !== a) return;
378
+ if (e) {
379
+ this.revokeSubscription(a, e);
380
+ return;
381
+ }
339
382
  }
340
- try {
341
- n = d(e, a.baseDescriptor);
342
- } catch (e) {
343
- this.revokeSubscription(a, e, "row filter declaration violated");
344
- return;
383
+ let n, c;
384
+ if (a.refilter === void 0) n = a.descriptor, a.memoKey ??= JSON.stringify(t(n)), c = a.memoKey;
385
+ else {
386
+ let e;
387
+ try {
388
+ if (e = await a.refilter(), this.subscriptions.get(a.id) !== a) return;
389
+ } catch (e) {
390
+ this.revokeSubscription(a, e, "row filter unavailable");
391
+ return;
392
+ }
393
+ try {
394
+ n = p(e, a.baseDescriptor);
395
+ } catch (e) {
396
+ this.revokeSubscription(a, e, "row filter declaration violated");
397
+ return;
398
+ }
399
+ c = JSON.stringify(t(n));
345
400
  }
346
- c = JSON.stringify(t(n));
347
- }
348
- c = `${a.readFromPrimary ? "primary" : "policy"}:${c}`;
349
- let l = r?.epoch === this.changeEpoch ? r.reads : v, u = l.get(c);
350
- u === void 0 && (u = a.readFromPrimary ? g({
351
- subjectKey: h(a.context.subject.id, a.context.clientId),
352
- forcePrimary: !0
353
- }, () => this.deps.store.query(n)) : this.deps.store.query(n), l.set(c, u));
354
- let p = await u;
355
- if (a.reauthorize !== void 0) {
356
- let e = await a.reauthorize();
357
- if (e) {
358
- this.revokeSubscription(a, e);
401
+ c = `${a.readFromPrimary ? "primary" : "policy"}:${c}`;
402
+ let l = r?.epoch === this.changeEpoch ? r.reads : v, u = l.get(c);
403
+ if (u === void 0) {
404
+ let e = () => a.readFromPrimary ? f({
405
+ subjectKey: d(a.context.subject.id, a.context.clientId),
406
+ forcePrimary: !0
407
+ }, () => this.deps.store.query(n)) : this.deps.store.query(n), t = this.deps.cache;
408
+ if (t !== void 0 && a.cacheBinding !== void 0) {
409
+ let n = t.fence();
410
+ u = n.then(e, e), this.readFences.set(u, n);
411
+ } else u = e();
412
+ l.set(c, u);
413
+ }
414
+ let h = await u;
415
+ if (this.subscriptions.get(a.id) !== a) return;
416
+ if (a.reauthorize !== void 0) {
417
+ let e = await a.reauthorize();
418
+ if (this.subscriptions.get(a.id) !== a) return;
419
+ if (e) {
420
+ this.revokeSubscription(a, e);
421
+ return;
422
+ }
423
+ }
424
+ let _ = this.measureLoop(() => y?.(n.table, h, a.context.subject)), D = _?.rows ?? this.measureLoop(() => g(n.table, h, a.context.subject));
425
+ _ !== void 0 && (c = JSON.stringify([c, _.visibilityKey]));
426
+ let O = a.lastDelivered, k = T.get(c);
427
+ k === void 0 && (k = /* @__PURE__ */ new Map(), T.set(c, k));
428
+ let A = k.get(O);
429
+ if (A === void 0 && (A = !this.measureLoop(() => N(D, O)), k.set(O, A)), !A) {
430
+ w.trace("suppressed no-op delta", {
431
+ subscriberId: i,
432
+ table: e.table
433
+ });
359
434
  return;
360
435
  }
361
- }
362
- let _ = x?.(n.table, p, a.context.subject), E = _?.rows ?? m(n.table, p, a.context.subject);
363
- _ !== void 0 && (c = JSON.stringify([c, _.visibilityKey]));
364
- let D = a.lastDelivered, O = T.get(c);
365
- O === void 0 && (O = /* @__PURE__ */ new Map(), T.set(c, O));
366
- let k = O.get(D);
367
- if (k === void 0 && (k = !j(E, D), O.set(D, k)), !k) {
368
- C.trace("suppressed no-op delta", {
369
- subscriberId: i,
370
- table: e.table
371
- });
372
- return;
373
- }
374
- if (a.lastDelivered = E, this.deps.cache !== void 0 && a.cacheBinding !== void 0 && this.deps.cache.put(a.cacheBinding, Array.from(a.dependentTables), E), b(D) && b(E)) {
375
- let e = w.get(c);
376
- e === void 0 && (e = /* @__PURE__ */ new Map(), w.set(c, e));
377
- let t = e.get(D);
378
- if (t === void 0) {
379
- let n = this.deps.crdtColumnsOf?.(a.descriptor.table) ?? S;
380
- t = n.size === 0 ? y(D, E) : y(D, E, { crdt: {
381
- columns: n,
382
- incremental: o
383
- } }), e.set(D, t);
436
+ a.lastDelivered = D;
437
+ let j = this.readFences.get(u);
438
+ if (this.deps.cache !== void 0 && a.cacheBinding !== void 0 && j !== void 0) {
439
+ let e = this.deps.cache, t = E(a.cacheBinding, n), r = Array.from(a.dependentTables);
440
+ j.then((n) => {
441
+ e.put(t, r, h, n);
442
+ }, () => void 0);
384
443
  }
385
- a.emit({
386
- _tag: "delta",
387
- revision: f,
388
- emittedAt: f,
389
- patch: t
444
+ if (x(O) && x(D)) {
445
+ let e = S.get(c);
446
+ e === void 0 && (e = /* @__PURE__ */ new Map(), S.set(c, e));
447
+ let t = e.get(O);
448
+ if (t === void 0) {
449
+ let n = this.deps.crdtColumnsOf?.(a.descriptor.table) ?? C;
450
+ t = this.measureLoop(() => n.size === 0 ? b(O, D) : b(O, D, { crdt: {
451
+ columns: n,
452
+ incremental: o
453
+ } })), e.set(O, t);
454
+ }
455
+ let n = t;
456
+ this.measureLoop(() => a.emit({
457
+ _tag: "delta",
458
+ revision: m,
459
+ emittedAt: m,
460
+ patch: n
461
+ }));
462
+ } else this.measureLoop(() => a.emit({
463
+ _tag: "snapshot",
464
+ revision: m,
465
+ data: D
466
+ }));
467
+ let M = Date.now();
468
+ this.deps.recordDelivery?.({
469
+ traceId: a.context.traceId,
470
+ parentSpanId: a.context.spanId,
471
+ label: a.label,
472
+ kind: "delta",
473
+ startMs: s,
474
+ endMs: M,
475
+ durationMs: M - s,
476
+ rowCount: D.length
477
+ }), this.deps.recordCost !== void 0 && this.deps.recordCost({
478
+ tenantId: a.context.subject.tenantId,
479
+ subscriptionId: a.id,
480
+ unit: "recompute",
481
+ amount: 1,
482
+ traceId: a.context.traceId
390
483
  });
391
- } else a.emit({
392
- _tag: "snapshot",
393
- revision: f,
394
- data: E
395
- });
396
- let A = Date.now();
397
- this.deps.recordDelivery?.({
398
- traceId: a.context.traceId,
399
- parentSpanId: a.context.spanId,
400
- label: a.label,
401
- kind: "delta",
402
- startMs: s,
403
- endMs: A,
404
- durationMs: A - s,
405
- rowCount: E.length
406
- }), this.deps.recordCost !== void 0 && this.deps.recordCost({
407
- tenantId: a.context.subject.tenantId,
408
- subscriptionId: a.id,
409
- unit: "recompute",
410
- amount: 1,
411
- traceId: a.context.traceId
412
- });
413
- } catch (t) {
414
- C.warn("reactive re-query failed; subscriber left on last snapshot", {
415
- subscriberId: i,
416
- label: a.label,
417
- table: e.table,
418
- traceId: a.context.traceId
419
- }, t);
420
- }
421
- }, D = Array.from(l), O = Math.max(1, Math.min(this.reactive.deliveryConcurrency, D.length)), k = 0, A = async () => {
422
- for (;;) {
423
- let e = k++;
424
- if (e >= D.length) return;
425
- await E(D[e]);
426
- }
427
- };
428
- await Promise.all(Array.from({ length: O }, () => A())), _ > 0 && C.trace("handleChange/prefiltered", {
429
- table: e.table,
430
- op: e.op,
431
- skipped: _
432
- });
484
+ } catch (t) {
485
+ w.warn("reactive re-query failed; subscriber left on last snapshot", {
486
+ subscriberId: i,
487
+ label: a.label,
488
+ table: e.table,
489
+ traceId: a.context.traceId
490
+ }, t);
491
+ }
492
+ }, O = Array.from(l), k = Math.max(1, Math.min(this.reactive.deliveryConcurrency, O.length)), A = 0, j = async () => {
493
+ for (;;) {
494
+ let e = A++;
495
+ if (e >= O.length) return;
496
+ await D(O[e]);
497
+ }
498
+ }, M = (await Promise.allSettled(Array.from({ length: k }, () => j()))).find((e) => e.status === "rejected");
499
+ if (M?.status === "rejected") throw M.reason;
500
+ _ > 0 && w.trace("handleChange/prefiltered", {
501
+ table: e.table,
502
+ op: e.op,
503
+ skipped: _
504
+ });
505
+ } finally {
506
+ i();
507
+ }
433
508
  }
434
- }, A = (e) => {
509
+ }, M = (e) => {
435
510
  try {
436
511
  return JSON.stringify(e) ?? "null";
437
512
  } catch {
438
513
  return String(e);
439
514
  }
440
- }, j = (e, t) => {
515
+ }, N = (e, t) => {
441
516
  if (e === t) return !0;
442
517
  if (e.length !== t.length) return !1;
443
518
  for (let n = 0; n < e.length; n++) if (JSON.stringify(e[n]) !== JSON.stringify(t[n])) return !1;
444
519
  return !0;
445
520
  };
446
521
  //#endregion
447
- export { k as Dispatcher, w as deliveredRowCount };
522
+ export { j as Dispatcher, T as deliveredRowCount, E as snapshotEntryFor };