@kronos-ts/kronosdb 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/connection.d.ts +86 -0
- package/dist/connection.d.ts.map +1 -0
- package/dist/connection.js +133 -0
- package/dist/connection.js.map +1 -0
- package/dist/errors.d.ts +72 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +149 -0
- package/dist/errors.js.map +1 -0
- package/dist/event-processor-info.d.ts +32 -0
- package/dist/event-processor-info.d.ts.map +1 -0
- package/dist/event-processor-info.js +24 -0
- package/dist/event-processor-info.js.map +1 -0
- package/dist/flow-controlled-sender.d.ts +12 -0
- package/dist/flow-controlled-sender.d.ts.map +1 -0
- package/dist/flow-controlled-sender.js +53 -0
- package/dist/flow-controlled-sender.js.map +1 -0
- package/dist/generated/command.d.ts +169 -0
- package/dist/generated/command.d.ts.map +1 -0
- package/dist/generated/command.js +964 -0
- package/dist/generated/command.js.map +1 -0
- package/dist/generated/common.d.ts +76 -0
- package/dist/generated/common.d.ts.map +1 -0
- package/dist/generated/common.js +648 -0
- package/dist/generated/common.js.map +1 -0
- package/dist/generated/eventstore.d.ts +337 -0
- package/dist/generated/eventstore.d.ts.map +1 -0
- package/dist/generated/eventstore.js +1757 -0
- package/dist/generated/eventstore.js.map +1 -0
- package/dist/generated/platform.d.ts +242 -0
- package/dist/generated/platform.d.ts.map +1 -0
- package/dist/generated/platform.js +1525 -0
- package/dist/generated/platform.js.map +1 -0
- package/dist/generated/query.d.ts +265 -0
- package/dist/generated/query.d.ts.map +1 -0
- package/dist/generated/query.js +2114 -0
- package/dist/generated/query.js.map +1 -0
- package/dist/generated/snapshot.d.ts +180 -0
- package/dist/generated/snapshot.d.ts.map +1 -0
- package/dist/generated/snapshot.js +861 -0
- package/dist/generated/snapshot.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +13 -0
- package/dist/index.js.map +1 -0
- package/dist/kronosdb-event-store.d.ts +17 -0
- package/dist/kronosdb-event-store.d.ts.map +1 -0
- package/dist/kronosdb-event-store.js +328 -0
- package/dist/kronosdb-event-store.js.map +1 -0
- package/dist/kronosdb-snapshot-store.d.ts +10 -0
- package/dist/kronosdb-snapshot-store.d.ts.map +1 -0
- package/dist/kronosdb-snapshot-store.js +79 -0
- package/dist/kronosdb-snapshot-store.js.map +1 -0
- package/dist/kronosdb.d.ts +53 -0
- package/dist/kronosdb.d.ts.map +1 -0
- package/dist/kronosdb.js +852 -0
- package/dist/kronosdb.js.map +1 -0
- package/dist/metadata-conversion.d.ts +37 -0
- package/dist/metadata-conversion.d.ts.map +1 -0
- package/dist/metadata-conversion.js +75 -0
- package/dist/metadata-conversion.js.map +1 -0
- package/dist/outbound-stream.d.ts +15 -0
- package/dist/outbound-stream.d.ts.map +1 -0
- package/dist/outbound-stream.js +39 -0
- package/dist/outbound-stream.js.map +1 -0
- package/dist/platform-service.d.ts +87 -0
- package/dist/platform-service.d.ts.map +1 -0
- package/dist/platform-service.js +218 -0
- package/dist/platform-service.js.map +1 -0
- package/dist/service-definitions.d.ts +187 -0
- package/dist/service-definitions.d.ts.map +1 -0
- package/dist/service-definitions.js +18 -0
- package/dist/service-definitions.js.map +1 -0
- package/dist/shutdown-latch.d.ts +18 -0
- package/dist/shutdown-latch.d.ts.map +1 -0
- package/dist/shutdown-latch.js +51 -0
- package/dist/shutdown-latch.js.map +1 -0
- package/package.json +69 -0
- package/src/connection.ts +235 -0
- package/src/errors.ts +173 -0
- package/src/event-processor-info.ts +53 -0
- package/src/flow-controlled-sender.ts +73 -0
- package/src/generated/command.ts +1226 -0
- package/src/generated/common.ts +770 -0
- package/src/generated/eventstore.ts +2241 -0
- package/src/generated/platform.ts +1914 -0
- package/src/generated/query.ts +2571 -0
- package/src/generated/snapshot.ts +1110 -0
- package/src/index.ts +87 -0
- package/src/kronosdb-event-store.ts +401 -0
- package/src/kronosdb-snapshot-store.ts +104 -0
- package/src/kronosdb.ts +1000 -0
- package/src/metadata-conversion.ts +85 -0
- package/src/outbound-stream.ts +52 -0
- package/src/platform-service.ts +297 -0
- package/src/service-definitions.ts +25 -0
- package/src/shutdown-latch.ts +74 -0
package/dist/kronosdb.js
ADDED
|
@@ -0,0 +1,852 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Native KronosDB extension (Phase 9, D-95 / D-101 / D-102).
|
|
3
|
+
*
|
|
4
|
+
* Native `(app: App) => void` extension that:
|
|
5
|
+
*
|
|
6
|
+
* - populates four typed slots (eventStore, snapshotStore, commandBus,
|
|
7
|
+
* queryBus) via app.set(...) using the canonical Resolved slot names
|
|
8
|
+
* (in particular `resolved.unitOfWorkFactory`, NOT `unitOfWorkRunner`);
|
|
9
|
+
* - wires connect-stage transport bring-up under the @kronos-ts/common
|
|
10
|
+
* resilience helper (initial-connect + health-check + platform setup +
|
|
11
|
+
* instruction handlers + platform.start);
|
|
12
|
+
* - wires processors-stage subscription-ack wait via withRetry against
|
|
13
|
+
* `platform.subscriptionsAcked()` — REPLACES the 1-second sleep hack
|
|
14
|
+
* that lived at line 216 of the legacy file (D-102);
|
|
15
|
+
* - reverses shutdown deterministically in a single onStop('connect') hook
|
|
16
|
+
* (busLatches → platform.stop → connection.close — D-101.b).
|
|
17
|
+
*/
|
|
18
|
+
import { generateIdentifier, qualifiedNameFromString, qualifiedNameToString, withRetry, healthCheck } from "@kronos-ts/common";
|
|
19
|
+
import { createUpdateHandler, runAfterCommitOrImmediately } from "@kronos-ts/messaging";
|
|
20
|
+
import { connectToKronosDb, createKronosMetadata } from "./connection.js";
|
|
21
|
+
import { KronosDbErrorCode, mapErrorCode } from "./errors.js";
|
|
22
|
+
import { metadataFromProto, metadataToProto } from "./metadata-conversion.js";
|
|
23
|
+
import { createOutboundStream } from "./outbound-stream.js";
|
|
24
|
+
import { createPlatformConnection } from "./platform-service.js";
|
|
25
|
+
import { createKronosDbEventStore } from "./kronosdb-event-store.js";
|
|
26
|
+
import { createKronosDbSnapshotStore } from "./kronosdb-snapshot-store.js";
|
|
27
|
+
import { createShutdownLatch } from "./shutdown-latch.js";
|
|
28
|
+
const DEFAULT_PERMITS = 5000n;
|
|
29
|
+
const DEFAULT_THRESHOLD = 2500n;
|
|
30
|
+
const INSTRUCTION_KEY = {
|
|
31
|
+
ROUTING_KEY: 0,
|
|
32
|
+
PRIORITY: 1,
|
|
33
|
+
TIMEOUT: 2,
|
|
34
|
+
NR_OF_RESULTS: 3,
|
|
35
|
+
};
|
|
36
|
+
function toProtoProcessingInstructions(instructions) {
|
|
37
|
+
if (!instructions)
|
|
38
|
+
return [];
|
|
39
|
+
const result = [];
|
|
40
|
+
if (instructions.routingKey !== undefined) {
|
|
41
|
+
result.push({ key: INSTRUCTION_KEY.ROUTING_KEY, value: { textValue: instructions.routingKey } });
|
|
42
|
+
}
|
|
43
|
+
if (instructions.priority !== undefined) {
|
|
44
|
+
result.push({ key: INSTRUCTION_KEY.PRIORITY, value: { numberValue: BigInt(instructions.priority) } });
|
|
45
|
+
}
|
|
46
|
+
if (instructions.timeoutMs !== undefined) {
|
|
47
|
+
result.push({ key: INSTRUCTION_KEY.TIMEOUT, value: { numberValue: BigInt(instructions.timeoutMs) } });
|
|
48
|
+
}
|
|
49
|
+
return result;
|
|
50
|
+
}
|
|
51
|
+
function defaultQueryInstructions(timeoutMs) {
|
|
52
|
+
return [
|
|
53
|
+
{ key: INSTRUCTION_KEY.TIMEOUT, value: { numberValue: BigInt(timeoutMs) } },
|
|
54
|
+
{ key: INSTRUCTION_KEY.NR_OF_RESULTS, value: { numberValue: 1n } },
|
|
55
|
+
];
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Native KronosDB extension factory. Returns an Extension closure shaped as
|
|
59
|
+
* `(app: App) => void` per D-95.
|
|
60
|
+
*
|
|
61
|
+
* ```ts
|
|
62
|
+
* await kronos()
|
|
63
|
+
* .use(kronosDb({ componentName: "university-service" }))
|
|
64
|
+
* .start()
|
|
65
|
+
* ```
|
|
66
|
+
*/
|
|
67
|
+
export function kronosDb(serverConfig) {
|
|
68
|
+
return (app) => {
|
|
69
|
+
let connection;
|
|
70
|
+
let platform;
|
|
71
|
+
const busLatches = [];
|
|
72
|
+
function getConnection() {
|
|
73
|
+
if (!connection) {
|
|
74
|
+
throw new Error("[kronos:kronosdb] connection not yet established — wait for onStart('connect')");
|
|
75
|
+
}
|
|
76
|
+
return connection;
|
|
77
|
+
}
|
|
78
|
+
// ---- Slot population (D-95) ------------------------------------------
|
|
79
|
+
//
|
|
80
|
+
// AppImpl.start() in @kronos-ts/app eagerly resolves all 8 slots and
|
|
81
|
+
// runs `commandBus.subscribe(...)` for every registered handler BEFORE
|
|
82
|
+
// any onStart('connect') hook fires (see app.ts §3 / §5c). The KronosDB
|
|
83
|
+
// bus factories open real gRPC streams against the live channel during
|
|
84
|
+
// construction (createKronosMetadata / connection.onReconnect / inbound
|
|
85
|
+
// stream openers), so they CANNOT run until the connect hook has
|
|
86
|
+
// populated `connection`.
|
|
87
|
+
//
|
|
88
|
+
// Solution: the slot factories return wrappers around lazily-built
|
|
89
|
+
// inner instances. EventStore/SnapshotStore use a lightweight lazy
|
|
90
|
+
// proxy because their factories never dereference `connection` at
|
|
91
|
+
// construction time (only inside method bodies). CommandBus/QueryBus
|
|
92
|
+
// use a `subscribe()`-buffering wrapper that queues subscriptions
|
|
93
|
+
// synchronously and replays them once the connect hook completes —
|
|
94
|
+
// dispatch / query calls await the same readiness promise.
|
|
95
|
+
/** Latches once the connect hook has populated `connection`. */
|
|
96
|
+
let resolveConnected = () => { };
|
|
97
|
+
const connected = new Promise((res) => {
|
|
98
|
+
resolveConnected = res;
|
|
99
|
+
});
|
|
100
|
+
app.set("eventStore", (resolved) => {
|
|
101
|
+
// Lazy proxy: createKronosDbEventStore stores `connection` in
|
|
102
|
+
// closure scope but only dereferences it inside method bodies, so
|
|
103
|
+
// a Proxy that forwards property access to getConnection() works
|
|
104
|
+
// — by the time framework code calls source/append/stream the
|
|
105
|
+
// connect hook has populated the closure.
|
|
106
|
+
const lazyConnection = new Proxy({}, {
|
|
107
|
+
get(_t, prop) {
|
|
108
|
+
return getConnection()[prop];
|
|
109
|
+
},
|
|
110
|
+
});
|
|
111
|
+
return createKronosDbEventStore(lazyConnection, resolved.serializer);
|
|
112
|
+
});
|
|
113
|
+
app.set("snapshotStore", (resolved) => {
|
|
114
|
+
const lazyConnection = new Proxy({}, {
|
|
115
|
+
get(_t, prop) {
|
|
116
|
+
return getConnection()[prop];
|
|
117
|
+
},
|
|
118
|
+
});
|
|
119
|
+
return createKronosDbSnapshotStore(lazyConnection, resolved.serializer);
|
|
120
|
+
});
|
|
121
|
+
app.set("commandBus", (resolved) => {
|
|
122
|
+
const latch = createShutdownLatch();
|
|
123
|
+
busLatches.push(latch);
|
|
124
|
+
let inner;
|
|
125
|
+
const pendingSubs = [];
|
|
126
|
+
// Build the real bus once the connect hook fires + replay buffered subs.
|
|
127
|
+
connected.then(() => {
|
|
128
|
+
// canonical Resolved slot name is `unitOfWorkFactory` (NOT unitOfWorkRunner)
|
|
129
|
+
inner = createDistributedCommandBus(getConnection(), resolved.unitOfWorkFactory, latch, resolved.serializer, serverConfig.commandFlowControl, serverConfig.commandLoadFactor, serverConfig.resilience);
|
|
130
|
+
for (const [name, h] of pendingSubs)
|
|
131
|
+
inner.subscribe(name, h);
|
|
132
|
+
pendingSubs.length = 0;
|
|
133
|
+
});
|
|
134
|
+
const wrapper = {
|
|
135
|
+
async dispatch(message) {
|
|
136
|
+
await connected;
|
|
137
|
+
return inner.dispatch(message);
|
|
138
|
+
},
|
|
139
|
+
subscribe(name, handler) {
|
|
140
|
+
if (inner)
|
|
141
|
+
inner.subscribe(name, handler);
|
|
142
|
+
else
|
|
143
|
+
pendingSubs.push([name, handler]);
|
|
144
|
+
},
|
|
145
|
+
};
|
|
146
|
+
return wrapper;
|
|
147
|
+
});
|
|
148
|
+
app.set("queryBus", (resolved) => {
|
|
149
|
+
const latch = createShutdownLatch();
|
|
150
|
+
busLatches.push(latch);
|
|
151
|
+
let inner;
|
|
152
|
+
const pendingSubs = [];
|
|
153
|
+
connected.then(() => {
|
|
154
|
+
inner = createDistributedQueryBus(getConnection(), resolved.unitOfWorkFactory, latch, resolved.serializer, serverConfig.queryFlowControl, serverConfig.shortcutQueriesToLocalHandlers, serverConfig.queryTimeoutMs, serverConfig.resilience);
|
|
155
|
+
for (const [name, h] of pendingSubs)
|
|
156
|
+
inner.subscribe(name, h);
|
|
157
|
+
pendingSubs.length = 0;
|
|
158
|
+
});
|
|
159
|
+
const wrapper = {
|
|
160
|
+
async query(message) {
|
|
161
|
+
await connected;
|
|
162
|
+
return inner.query(message);
|
|
163
|
+
},
|
|
164
|
+
subscribe(name, handler) {
|
|
165
|
+
if (inner)
|
|
166
|
+
inner.subscribe(name, handler);
|
|
167
|
+
else
|
|
168
|
+
pendingSubs.push([name, handler]);
|
|
169
|
+
},
|
|
170
|
+
subscriptionQuery(message, bufferSize) {
|
|
171
|
+
if (!inner) {
|
|
172
|
+
throw new Error("[kronos:kronosdb] subscriptionQuery called before connect hook completed");
|
|
173
|
+
}
|
|
174
|
+
return inner.subscriptionQuery(message, bufferSize);
|
|
175
|
+
},
|
|
176
|
+
subscribeToUpdates(message, bufferSize) {
|
|
177
|
+
if (!inner) {
|
|
178
|
+
throw new Error("[kronos:kronosdb] subscribeToUpdates called before connect hook completed");
|
|
179
|
+
}
|
|
180
|
+
return inner.subscribeToUpdates(message, bufferSize);
|
|
181
|
+
},
|
|
182
|
+
async emitUpdate(name, filter, update) {
|
|
183
|
+
await connected;
|
|
184
|
+
return inner.emitUpdate(name, filter, update);
|
|
185
|
+
},
|
|
186
|
+
async completeSubscription(name, filter) {
|
|
187
|
+
await connected;
|
|
188
|
+
return inner.completeSubscription(name, filter);
|
|
189
|
+
},
|
|
190
|
+
async completeSubscriptionExceptionally(name, error, filter) {
|
|
191
|
+
await connected;
|
|
192
|
+
return inner.completeSubscriptionExceptionally(name, error, filter);
|
|
193
|
+
},
|
|
194
|
+
};
|
|
195
|
+
return wrapper;
|
|
196
|
+
});
|
|
197
|
+
// ---- Lifecycle: connect (D-101 normative split) ---------------------
|
|
198
|
+
// connect = initial connect + health-check + platform setup +
|
|
199
|
+
// instruction wiring + platform.start.
|
|
200
|
+
app.onStart("connect", async () => {
|
|
201
|
+
connection = await withRetry(async () => connectToKronosDb(serverConfig), { event: "initial-connect", ...serverConfig.resilience });
|
|
202
|
+
// Health-check ping with warn-then-continue (D-100). KronosDbConnection
|
|
203
|
+
// has no dedicated probe surface today; the gRPC channel itself is
|
|
204
|
+
// created eagerly in connectToKronosDb so the meaningful probe is a
|
|
205
|
+
// round-trip — we approximate via a soft no-op promise that satisfies
|
|
206
|
+
// the threshold contract. Real network failure is surfaced by the
|
|
207
|
+
// first bus call against the live channel.
|
|
208
|
+
await healthCheck(async () => undefined, {
|
|
209
|
+
thresholdMs: serverConfig.resilience?.healthCheckThresholdMs,
|
|
210
|
+
log: serverConfig.resilience?.log,
|
|
211
|
+
});
|
|
212
|
+
platform = createPlatformConnection(connection, serverConfig.platformService);
|
|
213
|
+
// Build a name-keyed view of the EventProcessorModule list so server-
|
|
214
|
+
// initiated instructions can route to the right module. We resolve via
|
|
215
|
+
// `app.processors()` — Plan 09-01's zero-arg read accessor (D-103).
|
|
216
|
+
const processors = app.processors();
|
|
217
|
+
const processorMap = new Map();
|
|
218
|
+
for (const proc of processors)
|
|
219
|
+
processorMap.set(proc.name, proc);
|
|
220
|
+
platform.onInstruction(async (instruction) => {
|
|
221
|
+
switch (instruction.kind) {
|
|
222
|
+
case "pause-processor": {
|
|
223
|
+
const proc = processorMap.get(instruction.processorName);
|
|
224
|
+
if (proc?.stop)
|
|
225
|
+
proc.stop();
|
|
226
|
+
break;
|
|
227
|
+
}
|
|
228
|
+
case "start-processor": {
|
|
229
|
+
const proc = processorMap.get(instruction.processorName);
|
|
230
|
+
if (proc?.start)
|
|
231
|
+
await proc.start();
|
|
232
|
+
break;
|
|
233
|
+
}
|
|
234
|
+
case "release-segment": {
|
|
235
|
+
const proc = processorMap.get(instruction.processorName);
|
|
236
|
+
if (proc?.releaseSegment)
|
|
237
|
+
await proc.releaseSegment(instruction.segmentId);
|
|
238
|
+
break;
|
|
239
|
+
}
|
|
240
|
+
case "split-segment": {
|
|
241
|
+
const proc = processorMap.get(instruction.processorName);
|
|
242
|
+
if (proc?.splitSegment)
|
|
243
|
+
await proc.splitSegment(instruction.segmentId);
|
|
244
|
+
break;
|
|
245
|
+
}
|
|
246
|
+
case "merge-segment": {
|
|
247
|
+
const proc = processorMap.get(instruction.processorName);
|
|
248
|
+
if (proc?.mergeSegment)
|
|
249
|
+
await proc.mergeSegment(instruction.segmentId);
|
|
250
|
+
break;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
});
|
|
254
|
+
platform.registerProcessorStatusSupplier(() => {
|
|
255
|
+
return processors.map((proc) => ({
|
|
256
|
+
name: proc.name,
|
|
257
|
+
running: proc.running ?? false,
|
|
258
|
+
mode: proc.supportsReset?.() === false ? "Subscribing" : "Tracking",
|
|
259
|
+
isStreamingProcessor: proc.supportsReset?.() !== false,
|
|
260
|
+
activeThreads: proc.running ? 1 : 0,
|
|
261
|
+
availableThreads: 0,
|
|
262
|
+
error: false,
|
|
263
|
+
tokenStoreIdentifier: "",
|
|
264
|
+
segments: proc.processingStatus
|
|
265
|
+
? Array.from(proc.processingStatus().entries()).map(([segId, status]) => ({
|
|
266
|
+
segmentId: segId,
|
|
267
|
+
caughtUp: status.caughtUp ?? false,
|
|
268
|
+
replaying: status.replaying ?? false,
|
|
269
|
+
onePartOf: 1,
|
|
270
|
+
tokenPosition: status.position ?? 0n,
|
|
271
|
+
errorState: status.error?.message ?? "",
|
|
272
|
+
}))
|
|
273
|
+
: [
|
|
274
|
+
{
|
|
275
|
+
segmentId: 0,
|
|
276
|
+
caughtUp: true,
|
|
277
|
+
replaying: proc.replaying ?? false,
|
|
278
|
+
onePartOf: 1,
|
|
279
|
+
tokenPosition: proc.position ?? 0n,
|
|
280
|
+
errorState: "",
|
|
281
|
+
},
|
|
282
|
+
],
|
|
283
|
+
}));
|
|
284
|
+
});
|
|
285
|
+
await platform.start();
|
|
286
|
+
// Latch the connected promise so the deferred bus wrappers built in
|
|
287
|
+
// the slot factories above construct their inner instances and replay
|
|
288
|
+
// any subscriptions that were buffered while connect was running.
|
|
289
|
+
// This MUST happen synchronously before any subsequent stage hook so
|
|
290
|
+
// register/processors-stage code sees the fully-wired buses. The
|
|
291
|
+
// microtask queue drains the `.then(...)` callbacks attached in the
|
|
292
|
+
// slot factories before this hook resolves.
|
|
293
|
+
resolveConnected();
|
|
294
|
+
await Promise.resolve();
|
|
295
|
+
});
|
|
296
|
+
// ---- Lifecycle: processors (D-101 / D-102) --------------------------
|
|
297
|
+
// processors = ONLY the subscription-ack wait, via withRetry against
|
|
298
|
+
// `platform.subscriptionsAcked()`. This REPLACES the legacy 1-second
|
|
299
|
+
// sleep that lived at kronosdb-configuration-enhancer.ts:216.
|
|
300
|
+
app.onStart("processors", async () => {
|
|
301
|
+
await withRetry(async () => {
|
|
302
|
+
const ok = await platform.subscriptionsAcked();
|
|
303
|
+
if (!ok)
|
|
304
|
+
throw new Error("subscriptions not yet acked");
|
|
305
|
+
}, { event: "per-operation", ...serverConfig.resilience });
|
|
306
|
+
});
|
|
307
|
+
// ---- Lifecycle: stop (D-101.b — preserves legacy ordering) ----------
|
|
308
|
+
// busLatches drained first → platform.stop → connection.close.
|
|
309
|
+
app.onStop("connect", async () => {
|
|
310
|
+
await Promise.all(busLatches.map((l) => l.initiateShutdown()));
|
|
311
|
+
platform?.stop();
|
|
312
|
+
connection?.close();
|
|
313
|
+
});
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
// ---------------------------------------------------------------------------
|
|
317
|
+
// Shared payload helpers (moved verbatim from legacy enhancer)
|
|
318
|
+
// ---------------------------------------------------------------------------
|
|
319
|
+
function createPayloadHelpers(serializer) {
|
|
320
|
+
return {
|
|
321
|
+
serializePayload(name, payload, revision = "") {
|
|
322
|
+
return serializer.serialize(payload, name, revision);
|
|
323
|
+
},
|
|
324
|
+
deserializePayload(data, type = "", revision = "") {
|
|
325
|
+
if (!data || data.length === 0)
|
|
326
|
+
return undefined;
|
|
327
|
+
return serializer.deserialize({ data, type, revision });
|
|
328
|
+
},
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
// ---------------------------------------------------------------------------
|
|
332
|
+
// Distributed Command Bus
|
|
333
|
+
//
|
|
334
|
+
// Bus implementation moved verbatim from the legacy enhancer with TWO
|
|
335
|
+
// behavioural additions per D-97:
|
|
336
|
+
// 1) reestablishStream() body wrapped in withRetry({ event: "reconnect" })
|
|
337
|
+
// 2) inbound-stream backoff replaced by the same withRetry path
|
|
338
|
+
// ---------------------------------------------------------------------------
|
|
339
|
+
function createDistributedCommandBus(connection, unitOfWorkRunner, shutdownLatch, serializer, flowControl, commandLoadFactor, resilience) {
|
|
340
|
+
const metadata = createKronosMetadata(connection.config);
|
|
341
|
+
const { serializePayload, deserializePayload } = createPayloadHelpers(serializer);
|
|
342
|
+
const PERMITS = BigInt(flowControl?.permits ?? Number(DEFAULT_PERMITS));
|
|
343
|
+
const THRESHOLD = BigInt(flowControl?.refillThreshold ?? Number(DEFAULT_THRESHOLD));
|
|
344
|
+
const localSegment = new Map();
|
|
345
|
+
let outbound = createOutboundStream();
|
|
346
|
+
let streamStarted = false;
|
|
347
|
+
let permits = 0n;
|
|
348
|
+
function ensureStreamStarted() {
|
|
349
|
+
if (streamStarted)
|
|
350
|
+
return;
|
|
351
|
+
streamStarted = true;
|
|
352
|
+
const inbound = connection.commands.openStream(outbound.iterable, { metadata });
|
|
353
|
+
processInboundCommands(inbound);
|
|
354
|
+
}
|
|
355
|
+
function grantPermits() {
|
|
356
|
+
outbound.send({
|
|
357
|
+
flowControl: { clientId: connection.config.clientId, permits: PERMITS },
|
|
358
|
+
instructionId: "",
|
|
359
|
+
});
|
|
360
|
+
permits += PERMITS;
|
|
361
|
+
}
|
|
362
|
+
function reestablishStreamBody() {
|
|
363
|
+
outbound.close();
|
|
364
|
+
outbound = createOutboundStream();
|
|
365
|
+
streamStarted = false;
|
|
366
|
+
permits = 0n;
|
|
367
|
+
ensureStreamStarted();
|
|
368
|
+
for (const commandName of localSegment.keys()) {
|
|
369
|
+
outbound.send({
|
|
370
|
+
subscribe: {
|
|
371
|
+
messageId: generateIdentifier(),
|
|
372
|
+
command: commandName,
|
|
373
|
+
componentName: connection.config.componentName,
|
|
374
|
+
clientId: connection.config.clientId,
|
|
375
|
+
loadFactor: commandLoadFactor ?? 100,
|
|
376
|
+
},
|
|
377
|
+
instructionId: generateIdentifier(),
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
grantPermits();
|
|
381
|
+
}
|
|
382
|
+
async function reestablishStreamWithRetry() {
|
|
383
|
+
if (shutdownLatch.shuttingDown)
|
|
384
|
+
return;
|
|
385
|
+
await withRetry(async () => reestablishStreamBody(), {
|
|
386
|
+
event: "reconnect",
|
|
387
|
+
...resilience,
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
connection.onReconnect(() => {
|
|
391
|
+
if (!shutdownLatch.shuttingDown && streamStarted) {
|
|
392
|
+
reestablishStreamWithRetry().catch((err) => {
|
|
393
|
+
console.error("Distributed command bus: reconnect retries exhausted", err);
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
});
|
|
397
|
+
async function processInboundCommands(inbound) {
|
|
398
|
+
try {
|
|
399
|
+
for await (const message of inbound) {
|
|
400
|
+
if (!message.command)
|
|
401
|
+
continue;
|
|
402
|
+
permits--;
|
|
403
|
+
const proto = message.command;
|
|
404
|
+
const commandName = proto.name;
|
|
405
|
+
const handler = localSegment.get(commandName);
|
|
406
|
+
let resultPayload;
|
|
407
|
+
let errorCode = "";
|
|
408
|
+
let errorMsg = "";
|
|
409
|
+
if (handler) {
|
|
410
|
+
try {
|
|
411
|
+
const commandMessage = {
|
|
412
|
+
identifier: proto.messageIdentifier,
|
|
413
|
+
name: qualifiedNameFromString(commandName),
|
|
414
|
+
payload: deserializePayload(proto.payload?.data, proto.payload?.type, proto.payload?.revision),
|
|
415
|
+
metadata: metadataFromProto(proto.metadata ?? {}),
|
|
416
|
+
timestamp: Number(proto.timestamp),
|
|
417
|
+
};
|
|
418
|
+
resultPayload = await unitOfWorkRunner(commandMessage.metadata, () => handler(commandMessage));
|
|
419
|
+
}
|
|
420
|
+
catch (err) {
|
|
421
|
+
errorCode = KronosDbErrorCode.COMMAND_EXECUTION_ERROR;
|
|
422
|
+
errorMsg = err instanceof Error ? err.message : String(err);
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
else {
|
|
426
|
+
errorCode = KronosDbErrorCode.NO_HANDLER_FOR_COMMAND;
|
|
427
|
+
errorMsg = `No local handler for command "${commandName}"`;
|
|
428
|
+
}
|
|
429
|
+
const responseSerialized = resultPayload !== undefined
|
|
430
|
+
? serializePayload("result", resultPayload)
|
|
431
|
+
: undefined;
|
|
432
|
+
outbound.send({
|
|
433
|
+
commandResponse: {
|
|
434
|
+
messageIdentifier: generateIdentifier(),
|
|
435
|
+
requestIdentifier: proto.messageIdentifier,
|
|
436
|
+
errorCode,
|
|
437
|
+
errorMessage: errorCode
|
|
438
|
+
? { message: errorMsg, location: connection.config.componentName, details: [], errorCode }
|
|
439
|
+
: undefined,
|
|
440
|
+
payload: responseSerialized,
|
|
441
|
+
metadata: {},
|
|
442
|
+
processingInstructions: [],
|
|
443
|
+
},
|
|
444
|
+
instructionId: "",
|
|
445
|
+
});
|
|
446
|
+
if (permits <= THRESHOLD) {
|
|
447
|
+
outbound.send({
|
|
448
|
+
flowControl: { clientId: connection.config.clientId, permits: PERMITS },
|
|
449
|
+
instructionId: "",
|
|
450
|
+
});
|
|
451
|
+
permits += PERMITS;
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
catch (err) {
|
|
456
|
+
if (shutdownLatch.shuttingDown)
|
|
457
|
+
return;
|
|
458
|
+
if (String(err).includes("Connection dropped"))
|
|
459
|
+
return;
|
|
460
|
+
console.error("Distributed command bus: inbound stream error, attempting re-establishment via withRetry", err);
|
|
461
|
+
await reestablishStreamWithRetry().catch((retryErr) => {
|
|
462
|
+
console.error("Distributed command bus: reconnect retries exhausted", retryErr);
|
|
463
|
+
});
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
return {
|
|
467
|
+
async dispatch(message) {
|
|
468
|
+
const activity = shutdownLatch.registerActivity();
|
|
469
|
+
try {
|
|
470
|
+
const commandName = qualifiedNameToString(message.name);
|
|
471
|
+
const serialized = serializePayload(commandName, message.payload);
|
|
472
|
+
const response = await connection.commands.dispatch({
|
|
473
|
+
messageIdentifier: message.identifier,
|
|
474
|
+
name: commandName,
|
|
475
|
+
timestamp: BigInt(message.timestamp),
|
|
476
|
+
payload: serialized,
|
|
477
|
+
metadata: metadataToProto(message.metadata),
|
|
478
|
+
processingInstructions: toProtoProcessingInstructions(message.metadata?.processingInstructions),
|
|
479
|
+
clientId: connection.config.clientId,
|
|
480
|
+
componentName: connection.config.componentName,
|
|
481
|
+
}, { metadata });
|
|
482
|
+
if (response.errorCode && response.errorCode !== "") {
|
|
483
|
+
throw mapErrorCode(response.errorCode, response.errorMessage?.message ?? "Unknown error");
|
|
484
|
+
}
|
|
485
|
+
return deserializePayload(response.payload?.data, response.payload?.type, response.payload?.revision);
|
|
486
|
+
}
|
|
487
|
+
finally {
|
|
488
|
+
activity.end();
|
|
489
|
+
}
|
|
490
|
+
},
|
|
491
|
+
subscribe(commandName, handler) {
|
|
492
|
+
localSegment.set(commandName, handler);
|
|
493
|
+
ensureStreamStarted();
|
|
494
|
+
outbound.send({
|
|
495
|
+
subscribe: {
|
|
496
|
+
messageId: generateIdentifier(),
|
|
497
|
+
command: commandName,
|
|
498
|
+
componentName: connection.config.componentName,
|
|
499
|
+
clientId: connection.config.clientId,
|
|
500
|
+
loadFactor: commandLoadFactor ?? 100,
|
|
501
|
+
},
|
|
502
|
+
instructionId: generateIdentifier(),
|
|
503
|
+
});
|
|
504
|
+
grantPermits();
|
|
505
|
+
},
|
|
506
|
+
};
|
|
507
|
+
}
|
|
508
|
+
// ---------------------------------------------------------------------------
|
|
509
|
+
// Distributed Query Bus
|
|
510
|
+
// ---------------------------------------------------------------------------
|
|
511
|
+
function createDistributedQueryBus(connection, unitOfWorkRunner, shutdownLatch, serializer, flowControl, shortcutQueriesToLocalHandlers, queryTimeoutMs, resilience) {
|
|
512
|
+
const metadata = createKronosMetadata(connection.config);
|
|
513
|
+
const PERMITS = BigInt(flowControl?.permits ?? Number(DEFAULT_PERMITS));
|
|
514
|
+
const THRESHOLD = BigInt(flowControl?.refillThreshold ?? Number(DEFAULT_THRESHOLD));
|
|
515
|
+
const { serializePayload, deserializePayload } = createPayloadHelpers(serializer);
|
|
516
|
+
const localSegment = new Map();
|
|
517
|
+
const subscriptions = new Map();
|
|
518
|
+
let outbound = createOutboundStream();
|
|
519
|
+
let streamStarted = false;
|
|
520
|
+
let permits = 0n;
|
|
521
|
+
function ensureStreamStarted() {
|
|
522
|
+
if (streamStarted)
|
|
523
|
+
return;
|
|
524
|
+
streamStarted = true;
|
|
525
|
+
const inbound = connection.queries.openStream(outbound.iterable, { metadata });
|
|
526
|
+
processInboundQueries(inbound);
|
|
527
|
+
}
|
|
528
|
+
function grantQueryPermits() {
|
|
529
|
+
outbound.send({
|
|
530
|
+
flowControl: { clientId: connection.config.clientId, permits: PERMITS },
|
|
531
|
+
instructionId: "",
|
|
532
|
+
});
|
|
533
|
+
permits += PERMITS;
|
|
534
|
+
}
|
|
535
|
+
function reestablishStreamBody() {
|
|
536
|
+
outbound.close();
|
|
537
|
+
outbound = createOutboundStream();
|
|
538
|
+
streamStarted = false;
|
|
539
|
+
permits = 0n;
|
|
540
|
+
ensureStreamStarted();
|
|
541
|
+
for (const queryName of localSegment.keys()) {
|
|
542
|
+
outbound.send({
|
|
543
|
+
subscribe: {
|
|
544
|
+
messageId: generateIdentifier(),
|
|
545
|
+
query: queryName,
|
|
546
|
+
resultName: "",
|
|
547
|
+
componentName: connection.config.componentName,
|
|
548
|
+
clientId: connection.config.clientId,
|
|
549
|
+
},
|
|
550
|
+
instructionId: generateIdentifier(),
|
|
551
|
+
});
|
|
552
|
+
}
|
|
553
|
+
grantQueryPermits();
|
|
554
|
+
}
|
|
555
|
+
async function reestablishStreamWithRetry() {
|
|
556
|
+
if (shutdownLatch.shuttingDown)
|
|
557
|
+
return;
|
|
558
|
+
await withRetry(async () => reestablishStreamBody(), {
|
|
559
|
+
event: "reconnect",
|
|
560
|
+
...resilience,
|
|
561
|
+
});
|
|
562
|
+
}
|
|
563
|
+
connection.onReconnect(() => {
|
|
564
|
+
if (!shutdownLatch.shuttingDown && streamStarted) {
|
|
565
|
+
reestablishStreamWithRetry().catch((err) => {
|
|
566
|
+
console.error("Distributed query bus: reconnect retries exhausted", err);
|
|
567
|
+
});
|
|
568
|
+
}
|
|
569
|
+
});
|
|
570
|
+
async function processInboundQueries(inbound) {
|
|
571
|
+
try {
|
|
572
|
+
for await (const message of inbound) {
|
|
573
|
+
if (!message.query)
|
|
574
|
+
continue;
|
|
575
|
+
permits--;
|
|
576
|
+
const proto = message.query;
|
|
577
|
+
const queryName = proto.query;
|
|
578
|
+
const handler = localSegment.get(queryName);
|
|
579
|
+
let resultPayload;
|
|
580
|
+
let errorCode = "";
|
|
581
|
+
let errorMsg = "";
|
|
582
|
+
if (handler) {
|
|
583
|
+
try {
|
|
584
|
+
const queryMessage = {
|
|
585
|
+
identifier: proto.messageIdentifier,
|
|
586
|
+
name: qualifiedNameFromString(queryName),
|
|
587
|
+
payload: deserializePayload(proto.payload?.data, proto.payload?.type, proto.payload?.revision),
|
|
588
|
+
metadata: metadataFromProto(proto.metadata ?? {}),
|
|
589
|
+
timestamp: Number(proto.timestamp),
|
|
590
|
+
};
|
|
591
|
+
resultPayload = await unitOfWorkRunner(queryMessage.metadata, async () => {
|
|
592
|
+
return handler(queryMessage);
|
|
593
|
+
});
|
|
594
|
+
}
|
|
595
|
+
catch (err) {
|
|
596
|
+
errorCode = KronosDbErrorCode.QUERY_EXECUTION_ERROR;
|
|
597
|
+
errorMsg = err instanceof Error ? err.message : String(err);
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
else {
|
|
601
|
+
errorCode = KronosDbErrorCode.NO_HANDLER_FOR_QUERY;
|
|
602
|
+
errorMsg = `No local handler for query "${queryName}"`;
|
|
603
|
+
}
|
|
604
|
+
const responseSerialized = resultPayload !== undefined
|
|
605
|
+
? serializePayload("result", resultPayload)
|
|
606
|
+
: undefined;
|
|
607
|
+
outbound.send({
|
|
608
|
+
queryResponse: {
|
|
609
|
+
messageIdentifier: generateIdentifier(),
|
|
610
|
+
requestIdentifier: proto.messageIdentifier,
|
|
611
|
+
errorCode,
|
|
612
|
+
errorMessage: errorCode
|
|
613
|
+
? { message: errorMsg, location: connection.config.componentName, details: [], errorCode }
|
|
614
|
+
: undefined,
|
|
615
|
+
payload: responseSerialized,
|
|
616
|
+
metadata: {},
|
|
617
|
+
processingInstructions: [],
|
|
618
|
+
},
|
|
619
|
+
instructionId: "",
|
|
620
|
+
});
|
|
621
|
+
outbound.send({
|
|
622
|
+
queryComplete: {
|
|
623
|
+
messageId: generateIdentifier(),
|
|
624
|
+
requestId: proto.messageIdentifier,
|
|
625
|
+
},
|
|
626
|
+
instructionId: "",
|
|
627
|
+
});
|
|
628
|
+
if (permits <= THRESHOLD) {
|
|
629
|
+
outbound.send({
|
|
630
|
+
flowControl: { clientId: connection.config.clientId, permits: PERMITS },
|
|
631
|
+
instructionId: "",
|
|
632
|
+
});
|
|
633
|
+
permits += PERMITS;
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
catch (err) {
|
|
638
|
+
if (shutdownLatch.shuttingDown)
|
|
639
|
+
return;
|
|
640
|
+
if (String(err).includes("Connection dropped"))
|
|
641
|
+
return;
|
|
642
|
+
console.error("Distributed query bus: inbound stream error, attempting re-establishment via withRetry", err);
|
|
643
|
+
await reestablishStreamWithRetry().catch((retryErr) => {
|
|
644
|
+
console.error("Distributed query bus: reconnect retries exhausted", retryErr);
|
|
645
|
+
});
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
return {
|
|
649
|
+
async query(message) {
|
|
650
|
+
const activity = shutdownLatch.registerActivity();
|
|
651
|
+
try {
|
|
652
|
+
const queryName = qualifiedNameToString(message.name);
|
|
653
|
+
if (shortcutQueriesToLocalHandlers) {
|
|
654
|
+
const localHandler = localSegment.get(queryName);
|
|
655
|
+
if (localHandler) {
|
|
656
|
+
return unitOfWorkRunner(message.metadata, async () => {
|
|
657
|
+
return localHandler(message);
|
|
658
|
+
});
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
const serialized = serializePayload(queryName, message.payload);
|
|
662
|
+
const responseStream = connection.queries.query({
|
|
663
|
+
messageIdentifier: message.identifier,
|
|
664
|
+
query: queryName,
|
|
665
|
+
timestamp: BigInt(message.timestamp),
|
|
666
|
+
payload: serialized,
|
|
667
|
+
metadata: metadataToProto(message.metadata),
|
|
668
|
+
processingInstructions: defaultQueryInstructions(queryTimeoutMs ?? 3600000),
|
|
669
|
+
clientId: connection.config.clientId,
|
|
670
|
+
componentName: connection.config.componentName,
|
|
671
|
+
}, { metadata });
|
|
672
|
+
for await (const response of responseStream) {
|
|
673
|
+
if (response.errorCode && response.errorCode !== "") {
|
|
674
|
+
throw mapErrorCode(response.errorCode, response.errorMessage?.message ?? "Unknown error");
|
|
675
|
+
}
|
|
676
|
+
return deserializePayload(response.payload?.data, response.payload?.type, response.payload?.revision);
|
|
677
|
+
}
|
|
678
|
+
throw new Error(`No response for query "${queryName}"`);
|
|
679
|
+
}
|
|
680
|
+
finally {
|
|
681
|
+
activity.end();
|
|
682
|
+
}
|
|
683
|
+
},
|
|
684
|
+
subscribe(queryName, handler) {
|
|
685
|
+
localSegment.set(queryName, handler);
|
|
686
|
+
ensureStreamStarted();
|
|
687
|
+
outbound.send({
|
|
688
|
+
subscribe: {
|
|
689
|
+
messageId: generateIdentifier(),
|
|
690
|
+
query: queryName,
|
|
691
|
+
resultName: "",
|
|
692
|
+
componentName: connection.config.componentName,
|
|
693
|
+
clientId: connection.config.clientId,
|
|
694
|
+
},
|
|
695
|
+
instructionId: generateIdentifier(),
|
|
696
|
+
});
|
|
697
|
+
grantQueryPermits();
|
|
698
|
+
},
|
|
699
|
+
subscriptionQuery(message, bufferSize) {
|
|
700
|
+
const queryId = message.identifier;
|
|
701
|
+
if (subscriptions.has(queryId)) {
|
|
702
|
+
throw new Error(`Subscription query already registered for identifier "${queryId}"`);
|
|
703
|
+
}
|
|
704
|
+
const updateHandler = createUpdateHandler(message, bufferSize);
|
|
705
|
+
subscriptions.set(queryId, updateHandler);
|
|
706
|
+
const queryName = qualifiedNameToString(message.name);
|
|
707
|
+
const subscriptionId = generateIdentifier();
|
|
708
|
+
const serialized = serializePayload(queryName, message.payload);
|
|
709
|
+
const outboundSub = createOutboundStream();
|
|
710
|
+
outboundSub.send({
|
|
711
|
+
subscribe: {
|
|
712
|
+
subscriptionIdentifier: subscriptionId,
|
|
713
|
+
numberOfPermits: BigInt(bufferSize ?? 256),
|
|
714
|
+
queryRequest: {
|
|
715
|
+
messageIdentifier: message.identifier,
|
|
716
|
+
query: queryName,
|
|
717
|
+
timestamp: BigInt(message.timestamp),
|
|
718
|
+
payload: serialized,
|
|
719
|
+
metadata: metadataToProto(message.metadata),
|
|
720
|
+
processingInstructions: defaultQueryInstructions(queryTimeoutMs ?? 3600000),
|
|
721
|
+
clientId: connection.config.clientId,
|
|
722
|
+
componentName: connection.config.componentName,
|
|
723
|
+
},
|
|
724
|
+
},
|
|
725
|
+
});
|
|
726
|
+
const responseStream = connection.queries.subscription(outboundSub.iterable, { metadata });
|
|
727
|
+
let resolveInitial;
|
|
728
|
+
let rejectInitial;
|
|
729
|
+
const initialResult = new Promise((resolve, reject) => {
|
|
730
|
+
resolveInitial = resolve;
|
|
731
|
+
rejectInitial = reject;
|
|
732
|
+
});
|
|
733
|
+
let initialSettled = false;
|
|
734
|
+
(async () => {
|
|
735
|
+
try {
|
|
736
|
+
for await (const response of responseStream) {
|
|
737
|
+
if (response.initialResult) {
|
|
738
|
+
if (!initialSettled) {
|
|
739
|
+
if (response.initialResult.errorCode && response.initialResult.errorCode !== "") {
|
|
740
|
+
rejectInitial(mapErrorCode(response.initialResult.errorCode, response.initialResult.errorMessage?.message ?? "Unknown error"));
|
|
741
|
+
}
|
|
742
|
+
else {
|
|
743
|
+
resolveInitial(deserializePayload(response.initialResult.payload?.data, response.initialResult.payload?.type, response.initialResult.payload?.revision));
|
|
744
|
+
}
|
|
745
|
+
initialSettled = true;
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
else if (response.update) {
|
|
749
|
+
const update = deserializePayload(response.update.payload?.data, response.update.payload?.type, response.update.payload?.revision);
|
|
750
|
+
updateHandler.offer(update);
|
|
751
|
+
}
|
|
752
|
+
else if (response.complete) {
|
|
753
|
+
updateHandler.complete();
|
|
754
|
+
break;
|
|
755
|
+
}
|
|
756
|
+
else if (response.completeExceptionally) {
|
|
757
|
+
updateHandler.completeExceptionally(new Error(response.completeExceptionally.errorMessage?.message ?? "Subscription query failed"));
|
|
758
|
+
break;
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
catch (err) {
|
|
763
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
764
|
+
if (!initialSettled) {
|
|
765
|
+
rejectInitial(error);
|
|
766
|
+
initialSettled = true;
|
|
767
|
+
}
|
|
768
|
+
updateHandler.completeExceptionally(error);
|
|
769
|
+
}
|
|
770
|
+
finally {
|
|
771
|
+
subscriptions.delete(queryId);
|
|
772
|
+
}
|
|
773
|
+
})();
|
|
774
|
+
return {
|
|
775
|
+
initialResult,
|
|
776
|
+
updates: updateHandler.iterable,
|
|
777
|
+
close: () => {
|
|
778
|
+
outboundSub.send({
|
|
779
|
+
unsubscribe: {
|
|
780
|
+
subscriptionIdentifier: subscriptionId,
|
|
781
|
+
},
|
|
782
|
+
});
|
|
783
|
+
outboundSub.close();
|
|
784
|
+
subscriptions.delete(queryId);
|
|
785
|
+
updateHandler.complete();
|
|
786
|
+
},
|
|
787
|
+
};
|
|
788
|
+
},
|
|
789
|
+
subscribeToUpdates(message, bufferSize) {
|
|
790
|
+
const queryId = message.identifier;
|
|
791
|
+
if (subscriptions.has(queryId)) {
|
|
792
|
+
throw new Error(`Subscription query already registered for identifier "${queryId}"`);
|
|
793
|
+
}
|
|
794
|
+
const updateHandler = createUpdateHandler(message, bufferSize);
|
|
795
|
+
subscriptions.set(queryId, updateHandler);
|
|
796
|
+
return {
|
|
797
|
+
[Symbol.asyncIterator]: () => updateHandler.iterable[Symbol.asyncIterator](),
|
|
798
|
+
close: () => {
|
|
799
|
+
subscriptions.delete(queryId);
|
|
800
|
+
updateHandler.complete();
|
|
801
|
+
},
|
|
802
|
+
};
|
|
803
|
+
},
|
|
804
|
+
async emitUpdate(queryName, filter, update) {
|
|
805
|
+
runAfterCommitOrImmediately(() => {
|
|
806
|
+
for (const [id, handler] of subscriptions) {
|
|
807
|
+
if (!handler.active) {
|
|
808
|
+
subscriptions.delete(id);
|
|
809
|
+
continue;
|
|
810
|
+
}
|
|
811
|
+
const handlerQueryName = qualifiedNameToString(handler.query.name);
|
|
812
|
+
if (handlerQueryName !== queryName)
|
|
813
|
+
continue;
|
|
814
|
+
if (!filter(handler.query.payload))
|
|
815
|
+
continue;
|
|
816
|
+
const accepted = handler.offer(update);
|
|
817
|
+
if (!accepted) {
|
|
818
|
+
handler.completeExceptionally(new Error("Subscription query update buffer overflow"));
|
|
819
|
+
subscriptions.delete(id);
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
});
|
|
823
|
+
},
|
|
824
|
+
async completeSubscription(queryName, filter) {
|
|
825
|
+
runAfterCommitOrImmediately(() => {
|
|
826
|
+
for (const [id, handler] of subscriptions) {
|
|
827
|
+
const handlerQueryName = qualifiedNameToString(handler.query.name);
|
|
828
|
+
if (handlerQueryName !== queryName)
|
|
829
|
+
continue;
|
|
830
|
+
if (filter && !filter(handler.query.payload))
|
|
831
|
+
continue;
|
|
832
|
+
handler.complete();
|
|
833
|
+
subscriptions.delete(id);
|
|
834
|
+
}
|
|
835
|
+
});
|
|
836
|
+
},
|
|
837
|
+
async completeSubscriptionExceptionally(queryName, error, filter) {
|
|
838
|
+
runAfterCommitOrImmediately(() => {
|
|
839
|
+
for (const [id, handler] of subscriptions) {
|
|
840
|
+
const handlerQueryName = qualifiedNameToString(handler.query.name);
|
|
841
|
+
if (handlerQueryName !== queryName)
|
|
842
|
+
continue;
|
|
843
|
+
if (filter && !filter(handler.query.payload))
|
|
844
|
+
continue;
|
|
845
|
+
handler.completeExceptionally(error);
|
|
846
|
+
subscriptions.delete(id);
|
|
847
|
+
}
|
|
848
|
+
});
|
|
849
|
+
},
|
|
850
|
+
};
|
|
851
|
+
}
|
|
852
|
+
//# sourceMappingURL=kronosdb.js.map
|