@spine-event-engine/server 2.0.0-snapshot.2 → 2.0.0-snapshot.21
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/README.md +225 -518
- package/REFERENCE.md +498 -49
- package/dist/browser/browser-server.d.ts +95 -0
- package/dist/browser/browser-server.d.ts.map +1 -0
- package/dist/{server → browser}/browser-server.js +93 -7
- package/dist/browser/browser-server.js.map +1 -0
- package/dist/browser/durable-subscription-bindings.d.ts.map +1 -0
- package/dist/browser/durable-subscription-bindings.js.map +1 -0
- package/dist/browser/index.d.ts +12 -0
- package/dist/browser/index.d.ts.map +1 -0
- package/dist/browser/index.js +24 -0
- package/dist/browser/index.js.map +1 -0
- package/dist/browser/options.d.ts +183 -0
- package/dist/browser/options.d.ts.map +1 -0
- package/dist/browser/options.js +15 -0
- package/dist/browser/options.js.map +1 -0
- package/dist/bus/command-bus.d.ts +2 -0
- package/dist/bus/command-bus.d.ts.map +1 -1
- package/dist/bus/command-bus.js +36 -1
- package/dist/bus/command-bus.js.map +1 -1
- package/dist/bus/event-dispatcher-registry.d.ts +2 -2
- package/dist/bus/event-dispatcher-registry.js +3 -3
- package/dist/bus/event-dispatcher-registry.js.map +1 -1
- package/dist/context/bounded-context.d.ts +238 -83
- package/dist/context/bounded-context.d.ts.map +1 -1
- package/dist/context/bounded-context.js +1083 -286
- package/dist/context/bounded-context.js.map +1 -1
- package/dist/context/effective-tenant.d.ts +9 -0
- package/dist/context/effective-tenant.d.ts.map +1 -0
- package/dist/context/effective-tenant.js +58 -0
- package/dist/context/effective-tenant.js.map +1 -0
- package/dist/context/entity-inbox.d.ts.map +1 -1
- package/dist/context/entity-inbox.js +24 -17
- package/dist/context/entity-inbox.js.map +1 -1
- package/dist/context/local-inbox-handoff.d.ts +7 -3
- package/dist/context/local-inbox-handoff.d.ts.map +1 -1
- package/dist/context/local-inbox-handoff.js +32 -26
- package/dist/context/local-inbox-handoff.js.map +1 -1
- package/dist/context/projection-handoff.d.ts.map +1 -1
- package/dist/context/projection-handoff.js +36 -31
- package/dist/context/projection-handoff.js.map +1 -1
- package/dist/context/tenant-index.d.ts.map +1 -1
- package/dist/context/tenant-index.js +72 -2
- package/dist/context/tenant-index.js.map +1 -1
- package/dist/delivery/delivery-ports.d.ts +14 -4
- package/dist/delivery/delivery-ports.d.ts.map +1 -1
- package/dist/delivery/delivery.d.ts +19 -21
- package/dist/delivery/delivery.d.ts.map +1 -1
- package/dist/delivery/delivery.js +541 -158
- package/dist/delivery/delivery.js.map +1 -1
- package/dist/delivery/inbox-storage.d.ts +12 -3
- package/dist/delivery/inbox-storage.d.ts.map +1 -1
- package/dist/delivery/inbox-storage.js +160 -67
- package/dist/delivery/inbox-storage.js.map +1 -1
- package/dist/delivery/inbox.d.ts +9 -0
- package/dist/delivery/inbox.d.ts.map +1 -1
- package/dist/delivery/inbox.js +11 -0
- package/dist/delivery/inbox.js.map +1 -1
- package/dist/delivery/sharded-work-registry.d.ts.map +1 -1
- package/dist/delivery/sharded-work-registry.js +2 -1
- package/dist/delivery/sharded-work-registry.js.map +1 -1
- package/dist/entity/entity-metadata.d.ts +14 -5
- package/dist/entity/entity-metadata.d.ts.map +1 -1
- package/dist/entity/entity-metadata.js +38 -2
- package/dist/entity/entity-metadata.js.map +1 -1
- package/dist/entity/entity-storage-descriptor.d.ts +25 -5
- package/dist/entity/entity-storage-descriptor.d.ts.map +1 -1
- package/dist/entity/entity-storage-descriptor.js +69 -23
- package/dist/entity/entity-storage-descriptor.js.map +1 -1
- package/dist/entity/entity-transaction.d.ts +46 -42
- package/dist/entity/entity-transaction.d.ts.map +1 -1
- package/dist/entity/entity-transaction.js +130 -35
- package/dist/entity/entity-transaction.js.map +1 -1
- package/dist/entity/entity.d.ts +243 -98
- package/dist/entity/entity.d.ts.map +1 -1
- package/dist/entity/entity.js +405 -226
- package/dist/entity/entity.js.map +1 -1
- package/dist/entity/spec-scanner.d.ts +13 -1
- package/dist/entity/spec-scanner.d.ts.map +1 -1
- package/dist/entity/spec-scanner.js +20 -3
- package/dist/entity/spec-scanner.js.map +1 -1
- package/dist/handler/command-registration-readiness.d.ts +6 -6
- package/dist/handler/command-registration-readiness.d.ts.map +1 -1
- package/dist/handler/command-registration-readiness.js +4 -2
- package/dist/handler/command-registration-readiness.js.map +1 -1
- package/dist/handler/declared-rejections.d.ts +11 -0
- package/dist/handler/declared-rejections.d.ts.map +1 -0
- package/dist/handler/declared-rejections.js +34 -0
- package/dist/handler/declared-rejections.js.map +1 -0
- package/dist/handler/event-registration-readiness.d.ts +13 -56
- package/dist/handler/event-registration-readiness.d.ts.map +1 -1
- package/dist/handler/event-registration-readiness.js +23 -43
- package/dist/handler/event-registration-readiness.js.map +1 -1
- package/dist/handler/generated-handler-registry.d.ts +72 -33
- package/dist/handler/generated-handler-registry.d.ts.map +1 -1
- package/dist/handler/generated-handler-registry.js +421 -43
- package/dist/handler/generated-handler-registry.js.map +1 -1
- package/dist/handler/generated-registry-discovery.d.ts.map +1 -1
- package/dist/handler/generated-registry-discovery.js +2 -4
- package/dist/handler/generated-registry-discovery.js.map +1 -1
- package/dist/handler/handler-decorators.d.ts +91 -37
- package/dist/handler/handler-decorators.d.ts.map +1 -1
- package/dist/handler/handler-decorators.js +124 -31
- package/dist/handler/handler-decorators.js.map +1 -1
- package/dist/handler/handler-metadata.d.ts +105 -60
- package/dist/handler/handler-metadata.d.ts.map +1 -1
- package/dist/handler/handler-metadata.js +275 -57
- package/dist/handler/handler-metadata.js.map +1 -1
- package/dist/handler/registration-readiness-metadata.d.ts +4 -0
- package/dist/handler/registration-readiness-metadata.d.ts.map +1 -1
- package/dist/handler/registration-readiness-metadata.js +84 -2
- package/dist/handler/registration-readiness-metadata.js.map +1 -1
- package/dist/handler/standalone.d.ts +25 -0
- package/dist/handler/standalone.d.ts.map +1 -0
- package/dist/handler/standalone.js +38 -0
- package/dist/handler/standalone.js.map +1 -0
- package/dist/index.d.ts +9 -9
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -3
- package/dist/index.js.map +1 -1
- package/dist/repository/command-routing.d.ts +63 -10
- package/dist/repository/command-routing.d.ts.map +1 -1
- package/dist/repository/command-routing.js +41 -8
- package/dist/repository/command-routing.js.map +1 -1
- package/dist/repository/event-routing.d.ts +64 -9
- package/dist/repository/event-routing.d.ts.map +1 -1
- package/dist/repository/event-routing.js +42 -7
- package/dist/repository/event-routing.js.map +1 -1
- package/dist/repository/primitive-id.d.ts +12 -23
- package/dist/repository/primitive-id.d.ts.map +1 -1
- package/dist/repository/primitive-id.js +2 -27
- package/dist/repository/primitive-id.js.map +1 -1
- package/dist/repository/repository.d.ts +317 -87
- package/dist/repository/repository.d.ts.map +1 -1
- package/dist/repository/repository.js +3746 -856
- package/dist/repository/repository.js.map +1 -1
- package/dist/repository/state-update-routing.d.ts +67 -8
- package/dist/repository/state-update-routing.d.ts.map +1 -1
- package/dist/repository/state-update-routing.js +45 -6
- package/dist/repository/state-update-routing.js.map +1 -1
- package/dist/runtime/runtime.js +1 -1
- package/dist/runtime/runtime.js.map +1 -1
- package/dist/runtime/signal-metadata.d.ts +22 -47
- package/dist/runtime/signal-metadata.d.ts.map +1 -1
- package/dist/runtime/signal-metadata.js +34 -69
- package/dist/runtime/signal-metadata.js.map +1 -1
- package/dist/runtime/signal-publisher.d.ts +144 -0
- package/dist/runtime/signal-publisher.d.ts.map +1 -0
- package/dist/runtime/signal-publisher.js +248 -0
- package/dist/runtime/signal-publisher.js.map +1 -0
- package/dist/runtime/standalone-handler-runtime.d.ts +130 -0
- package/dist/runtime/standalone-handler-runtime.d.ts.map +1 -0
- package/dist/runtime/standalone-handler-runtime.js +382 -0
- package/dist/runtime/standalone-handler-runtime.js.map +1 -0
- package/dist/server/environment-attachment.d.ts.map +1 -1
- package/dist/server/environment-attachment.js +2 -1
- package/dist/server/environment-attachment.js.map +1 -1
- package/dist/server/environment-delivery-worker.d.ts.map +1 -1
- package/dist/server/environment-delivery-worker.js +21 -12
- package/dist/server/environment-delivery-worker.js.map +1 -1
- package/dist/server/managed-server-application.d.ts +4 -1
- package/dist/server/managed-server-application.d.ts.map +1 -1
- package/dist/server/managed-server-application.js +2 -2
- package/dist/server/managed-server-application.js.map +1 -1
- package/dist/server/node-coordinator.js +2 -2
- package/dist/server/node-coordinator.js.map +1 -1
- package/dist/server/server-log.d.ts.map +1 -1
- package/dist/server/server-log.js +4 -0
- package/dist/server/server-log.js.map +1 -1
- package/dist/server/server.d.ts +9 -177
- package/dist/server/server.d.ts.map +1 -1
- package/dist/server/server.js +428 -178
- package/dist/server/server.js.map +1 -1
- package/dist/services/query-reader.d.ts +19 -0
- package/dist/services/query-reader.d.ts.map +1 -0
- package/dist/services/query-reader.js +43 -0
- package/dist/services/query-reader.js.map +1 -0
- package/dist/services/registered-targets.d.ts +73 -0
- package/dist/services/registered-targets.d.ts.map +1 -0
- package/dist/services/registered-targets.js +127 -0
- package/dist/services/registered-targets.js.map +1 -0
- package/dist/services/spine-services.d.ts +26 -12
- package/dist/services/spine-services.d.ts.map +1 -1
- package/dist/services/spine-services.js +1047 -969
- package/dist/services/spine-services.js.map +1 -1
- package/dist/spi/delivery.d.ts +5 -0
- package/dist/spi/delivery.d.ts.map +1 -0
- package/dist/spi/delivery.js +18 -0
- package/dist/spi/delivery.js.map +1 -0
- package/dist/spi/handler-registry.d.ts +8 -0
- package/dist/spi/handler-registry.d.ts.map +1 -0
- package/dist/spi/handler-registry.js +15 -0
- package/dist/spi/handler-registry.js.map +1 -0
- package/dist/stand/stand.d.ts +73 -1
- package/dist/stand/stand.d.ts.map +1 -1
- package/dist/stand/stand.js +221 -48
- package/dist/stand/stand.js.map +1 -1
- package/dist/stand/subscription-observer.d.ts.map +1 -1
- package/dist/stand/subscription-observer.js +5 -12
- package/dist/stand/subscription-observer.js.map +1 -1
- package/dist/stand/subscription-registry.js +1 -1
- package/dist/stand/subscription-registry.js.map +1 -1
- package/dist/testing/index.d.ts +25 -3
- package/dist/testing/index.d.ts.map +1 -1
- package/dist/testing/index.js +23 -3
- package/dist/testing/index.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +33 -23
- package/dist/handler/build-time-handler-analyzer.d.ts +0 -150
- package/dist/handler/build-time-handler-analyzer.d.ts.map +0 -1
- package/dist/handler/build-time-handler-analyzer.js +0 -1101
- package/dist/handler/build-time-handler-analyzer.js.map +0 -1
- package/dist/handler/generated-registry-writer.d.ts +0 -58
- package/dist/handler/generated-registry-writer.d.ts.map +0 -1
- package/dist/handler/generated-registry-writer.js +0 -458
- package/dist/handler/generated-registry-writer.js.map +0 -1
- package/dist/handler/handler-codegen.d.ts +0 -48
- package/dist/handler/handler-codegen.d.ts.map +0 -1
- package/dist/handler/handler-codegen.js +0 -214
- package/dist/handler/handler-codegen.js.map +0 -1
- package/dist/server/browser-server.d.ts +0 -49
- package/dist/server/browser-server.d.ts.map +0 -1
- package/dist/server/browser-server.js.map +0 -1
- package/dist/server/durable-subscription-bindings.d.ts.map +0 -1
- package/dist/server/durable-subscription-bindings.js.map +0 -1
- /package/dist/{server → browser}/durable-subscription-bindings.d.ts +0 -0
- /package/dist/{server → browser}/durable-subscription-bindings.js +0 -0
|
@@ -14,13 +14,15 @@
|
|
|
14
14
|
import { clone, create } from "@bufbuild/protobuf";
|
|
15
15
|
import { AnySchema } from "@bufbuild/protobuf/wkt";
|
|
16
16
|
import { randomUUID } from "node:crypto";
|
|
17
|
+
import { TenantIdSchema } from "@spine-event-engine/proto";
|
|
17
18
|
import { WorkerIdSchema } from "@spine-event-engine/proto/delivery";
|
|
18
19
|
import { AlreadyPickedUp, DeliveryMonitor, FailedPickUp, FailedReception, } from "./delivery-monitor.js";
|
|
19
20
|
import { Inbox, InboxTargets } from "./inbox.js";
|
|
20
|
-
import { InboxStorage } from "./inbox-storage.js";
|
|
21
|
+
import { InboxStorage, InboxStorageClock } from "./inbox-storage.js";
|
|
21
22
|
import { ShardIndex } from "./shard-index.js";
|
|
22
23
|
import { ShardedWorkRegistry } from "./sharded-work-registry.js";
|
|
23
24
|
import { withDeliveryCommitFence } from "../repository/commit-fence.js";
|
|
25
|
+
const deliveryFailureLimit = 100;
|
|
24
26
|
/**
|
|
25
27
|
* Executes direct Inbox delivery for one complete worker identity.
|
|
26
28
|
*/
|
|
@@ -70,7 +72,7 @@ export class Delivery {
|
|
|
70
72
|
options.worker.nodeId?.value !== options.node) {
|
|
71
73
|
throw new Error("Delivery worker node must match the configured delivery node.");
|
|
72
74
|
}
|
|
73
|
-
this.context =
|
|
75
|
+
this.context = Delivery.#contextSnapshot(options.context);
|
|
74
76
|
this.storageFactory = options.storageFactory;
|
|
75
77
|
this.strategy = options.strategy ?? { shardCount: 1, shardFor: () => ShardIndex.single() };
|
|
76
78
|
this.worker =
|
|
@@ -137,18 +139,34 @@ export class Delivery {
|
|
|
137
139
|
* @returns The terminal direct-delivery result.
|
|
138
140
|
*/
|
|
139
141
|
async drainMessage(message, input) {
|
|
140
|
-
|
|
141
|
-
|
|
142
|
+
return this.#drainDirect(message, this.#directInput(input));
|
|
143
|
+
}
|
|
144
|
+
#directInput(input) {
|
|
145
|
+
return typeof input === "function" ? { onMessage: input } : input;
|
|
146
|
+
}
|
|
147
|
+
static #contextSnapshot(context) {
|
|
148
|
+
return Object.freeze(context.multitenant
|
|
149
|
+
? {
|
|
150
|
+
name: context.name,
|
|
151
|
+
multitenant: true,
|
|
152
|
+
tenantId: clone(TenantIdSchema, context.tenantId),
|
|
153
|
+
}
|
|
154
|
+
: { name: context.name, multitenant: false });
|
|
155
|
+
}
|
|
156
|
+
async #drainDirect(message, input) {
|
|
142
157
|
let acknowledged = false;
|
|
143
158
|
const run = await this.drain(message.shard, {
|
|
144
|
-
onMessage,
|
|
145
|
-
...(
|
|
146
|
-
? {}
|
|
147
|
-
: { acceptMessage: input.acceptMessage }),
|
|
159
|
+
onMessage: input.onMessage,
|
|
160
|
+
...(input.acceptMessage === undefined ? {} : { acceptMessage: input.acceptMessage }),
|
|
148
161
|
onDelivered: (next) => {
|
|
149
162
|
acknowledged ||=
|
|
150
163
|
next.id.value === message.id.value && next.id.shard.key() === message.id.shard.key();
|
|
151
|
-
|
|
164
|
+
input.onDelivered?.(next);
|
|
165
|
+
},
|
|
166
|
+
onDuplicateRemoved: (next) => {
|
|
167
|
+
acknowledged ||=
|
|
168
|
+
next.id.value === message.id.value && next.id.shard.key() === message.id.shard.key();
|
|
169
|
+
input.onDuplicateRemoved?.(next);
|
|
152
170
|
},
|
|
153
171
|
});
|
|
154
172
|
return Object.freeze({ run, acknowledged });
|
|
@@ -179,162 +197,405 @@ export class Delivery {
|
|
|
179
197
|
const released = await safelyValue(() => this.shards.release(session), false);
|
|
180
198
|
return result(released ? "STOPPED" : "FAILED");
|
|
181
199
|
}
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
200
|
+
return new DeliveryDrain({
|
|
201
|
+
inbox: this.inbox,
|
|
202
|
+
monitor: this.#monitor,
|
|
203
|
+
options,
|
|
204
|
+
pageSize: this.pageSize,
|
|
205
|
+
session,
|
|
206
|
+
shards: this.shards,
|
|
207
|
+
shard,
|
|
208
|
+
}).run();
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Processes inbox pages while retaining one shard session.
|
|
213
|
+
*/
|
|
214
|
+
class DeliveryDrain {
|
|
215
|
+
input;
|
|
216
|
+
#statistics = counts();
|
|
217
|
+
#failures = [];
|
|
218
|
+
#deduplication;
|
|
219
|
+
#blockedTargets = new Set();
|
|
220
|
+
#removedInPage = new Set();
|
|
221
|
+
#current;
|
|
222
|
+
#ownershipLost = false;
|
|
223
|
+
#failureLimitReached = false;
|
|
224
|
+
/**
|
|
225
|
+
* Creates a drain for one acquired shard session.
|
|
226
|
+
*
|
|
227
|
+
* @param input Supplies the inbox, callbacks, and shard session.
|
|
228
|
+
*/
|
|
229
|
+
constructor(input) {
|
|
230
|
+
this.input = input;
|
|
231
|
+
this.#current = input.session;
|
|
232
|
+
this.#deduplication = new DeliveryDeduplication(input.inbox);
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Executes delivery and releases the shard session.
|
|
236
|
+
*
|
|
237
|
+
* @returns The terminal delivery outcome and counts.
|
|
238
|
+
*/
|
|
239
|
+
async run() {
|
|
240
|
+
let run;
|
|
241
|
+
try {
|
|
242
|
+
run = (await this.#canStart()) ? await this.#readPages() : this.#complete("STOPPED");
|
|
243
|
+
}
|
|
244
|
+
catch (error) {
|
|
245
|
+
if (!(await this.#release()))
|
|
246
|
+
return this.#complete("FAILED");
|
|
247
|
+
throw error;
|
|
248
|
+
}
|
|
249
|
+
return (await this.#release()) ? run : this.#complete("FAILED");
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Removes the session and reports completion when release succeeds.
|
|
253
|
+
*
|
|
254
|
+
* @returns Whether the session was released.
|
|
255
|
+
*/
|
|
256
|
+
async #release() {
|
|
257
|
+
const released = await safelyValue(() => this.input.shards.release(this.#current, this.input.options.operation), false);
|
|
258
|
+
if (released)
|
|
259
|
+
await safely(() => this.#completeMonitoring());
|
|
260
|
+
return released;
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* Checks whether monitoring permits this drain to start.
|
|
264
|
+
*
|
|
265
|
+
* @returns Whether page processing may start.
|
|
266
|
+
*/
|
|
267
|
+
async #canStart() {
|
|
268
|
+
if (!(await safelyBoolean(() => this.input.monitor.shouldContinueAfter("DELIVERY"))))
|
|
269
|
+
return false;
|
|
270
|
+
return safely(() => this.input.monitor.onDeliveryStarted(this.input.shard));
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* Reads and processes inbox pages until the shard is drained or stopped.
|
|
274
|
+
*
|
|
275
|
+
* @returns The terminal delivery outcome.
|
|
276
|
+
*/
|
|
277
|
+
async #readPages() {
|
|
278
|
+
let after;
|
|
279
|
+
let deliveredInScan = false;
|
|
280
|
+
for (;;) {
|
|
281
|
+
const messages = await this.#readPage(after);
|
|
282
|
+
this.#removedInPage.clear();
|
|
283
|
+
if (messages.length === 0) {
|
|
284
|
+
if (!deliveredInScan)
|
|
285
|
+
return this.#complete("DRAINED");
|
|
286
|
+
after = undefined;
|
|
287
|
+
deliveredInScan = false;
|
|
288
|
+
continue;
|
|
192
289
|
}
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
if (
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
statuses: ["DELIVERED"],
|
|
206
|
-
limit: this.pageSize,
|
|
207
|
-
...(after === undefined ? {} : { after }),
|
|
208
|
-
...(options.operation ?? {}),
|
|
209
|
-
});
|
|
210
|
-
for (const message of delivered) {
|
|
211
|
-
if (options.operation?.signal?.aborted || !(await validate()))
|
|
212
|
-
return false;
|
|
213
|
-
const removed = await this.inbox.removeDelivered(message, current, options.operation);
|
|
214
|
-
if (options.operation?.signal?.aborted || (!removed && !(await validate())))
|
|
215
|
-
return false;
|
|
216
|
-
removedAny ||= removed;
|
|
217
|
-
}
|
|
218
|
-
const last = delivered.at(-1);
|
|
219
|
-
if (removedAny || last === undefined || delivered.length < this.pageSize)
|
|
220
|
-
break;
|
|
221
|
-
after = {
|
|
222
|
-
messageId: last.id.value,
|
|
223
|
-
whenReceived: last.whenReceived,
|
|
224
|
-
version: last.version,
|
|
225
|
-
};
|
|
290
|
+
const deliveredBefore = this.#statistics.delivered;
|
|
291
|
+
if (!(await this.#processPage(messages)))
|
|
292
|
+
return this.#complete("STOPPED");
|
|
293
|
+
if (!(await this.#cleanupPage(messages)))
|
|
294
|
+
return this.#complete("STOPPED");
|
|
295
|
+
deliveredInScan ||= this.#statistics.delivered !== deliveredBefore;
|
|
296
|
+
if (messages.length < this.input.pageSize) {
|
|
297
|
+
if (!deliveredInScan)
|
|
298
|
+
return this.#complete("DRAINED");
|
|
299
|
+
after = undefined;
|
|
300
|
+
deliveredInScan = false;
|
|
301
|
+
continue;
|
|
226
302
|
}
|
|
303
|
+
const survivor = messages.findLast((message) => !this.#removedInPage.has(message));
|
|
304
|
+
if (survivor !== undefined)
|
|
305
|
+
after = this.#continuation([survivor]);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* Reads the next inbox page after an optional continuation.
|
|
310
|
+
*
|
|
311
|
+
* @param after Supplies the prior page position, when present.
|
|
312
|
+
* @returns The ordered inbox messages in the next page.
|
|
313
|
+
*/
|
|
314
|
+
#readPage(after) {
|
|
315
|
+
return this.input.inbox.read(this.input.shard, {
|
|
316
|
+
limit: this.input.pageSize,
|
|
317
|
+
...(after === undefined ? {} : { after }),
|
|
318
|
+
...(this.input.options.operation ?? {}),
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* Processes every message in one page.
|
|
323
|
+
*
|
|
324
|
+
* @param messages Supplies the inbox page to process.
|
|
325
|
+
* @returns Whether page processing may continue.
|
|
326
|
+
*/
|
|
327
|
+
async #processPage(messages) {
|
|
328
|
+
const page = this.#deduplication.page(messages, this.#retentionTime());
|
|
329
|
+
for (const message of messages) {
|
|
330
|
+
if (!(await this.#processMessage(message, page)))
|
|
331
|
+
return false;
|
|
332
|
+
if (this.#failureLimitReached)
|
|
333
|
+
return false;
|
|
334
|
+
}
|
|
335
|
+
return true;
|
|
336
|
+
}
|
|
337
|
+
/**
|
|
338
|
+
* Processes one message unless it is skipped or its target is blocked.
|
|
339
|
+
*
|
|
340
|
+
* @param message Supplies the inbox message.
|
|
341
|
+
* @param page Provides duplicate checks for this page.
|
|
342
|
+
* @returns Whether processing may continue.
|
|
343
|
+
*/
|
|
344
|
+
async #processMessage(message, page) {
|
|
345
|
+
if (this.input.options.operation?.signal?.aborted)
|
|
346
|
+
return false;
|
|
347
|
+
if (this.#shouldSkip(message))
|
|
227
348
|
return true;
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
349
|
+
const target = this.#targetKey(message);
|
|
350
|
+
if (this.#blockedTargets.has(target))
|
|
351
|
+
return true;
|
|
352
|
+
this.#statistics.processed += 1;
|
|
353
|
+
if (!(await this.#mayProcess()))
|
|
354
|
+
return false;
|
|
355
|
+
if (page.isDuplicate(message))
|
|
356
|
+
return this.#removeDuplicate(message, target);
|
|
357
|
+
return this.#deliver(message, target);
|
|
358
|
+
}
|
|
359
|
+
/**
|
|
360
|
+
* Determines whether an inbox message is ineligible for endpoint delivery.
|
|
361
|
+
*
|
|
362
|
+
* @param message Supplies the message to check.
|
|
363
|
+
* @returns Whether the message should be skipped.
|
|
364
|
+
*/
|
|
365
|
+
#shouldSkip(message) {
|
|
366
|
+
return (message.status !== "TO_DELIVER" ||
|
|
367
|
+
!isEndpointMessage(message) ||
|
|
368
|
+
(this.input.options.acceptMessage !== undefined && !this.input.options.acceptMessage(message)));
|
|
369
|
+
}
|
|
370
|
+
/**
|
|
371
|
+
* Builds the key used to block further messages for one target.
|
|
372
|
+
*
|
|
373
|
+
* @param message Supplies the inbox message.
|
|
374
|
+
* @returns The target type and ID key.
|
|
375
|
+
*/
|
|
376
|
+
#targetKey(message) {
|
|
377
|
+
return `${message.inboxId.targetTypeUrl}:${InboxTargets.key(message.inboxId.targetId)}`;
|
|
378
|
+
}
|
|
379
|
+
/**
|
|
380
|
+
* Checks monitoring and verifies that the shard session remains current.
|
|
381
|
+
*
|
|
382
|
+
* @returns Whether this message may be processed.
|
|
383
|
+
*/
|
|
384
|
+
async #mayProcess() {
|
|
385
|
+
return ((await safelyBoolean(() => this.input.monitor.shouldContinueAfter("PAGE"))) &&
|
|
386
|
+
(await this.#validate()));
|
|
387
|
+
}
|
|
388
|
+
/**
|
|
389
|
+
* Removes a duplicate message and records a failure if removal fails.
|
|
390
|
+
*
|
|
391
|
+
* @param message Supplies the duplicate message.
|
|
392
|
+
* @param target Identifies the target to block after a failure.
|
|
393
|
+
* @returns Whether processing may continue.
|
|
394
|
+
*/
|
|
395
|
+
async #removeDuplicate(message, target) {
|
|
239
396
|
try {
|
|
240
|
-
if (!(await
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
continue;
|
|
266
|
-
const target = `${message.inboxId.targetTypeUrl}:${InboxTargets.key(message.inboxId.targetId)}`;
|
|
267
|
-
if (blockedTargets.has(target))
|
|
268
|
-
continue;
|
|
269
|
-
statistics.processed += 1;
|
|
270
|
-
if (!(await safelyBoolean(() => this.#monitor.shouldContinueAfter("PAGE"))))
|
|
271
|
-
return complete("STOPPED");
|
|
272
|
-
if (!(await validate()))
|
|
273
|
-
return complete("STOPPED");
|
|
274
|
-
try {
|
|
275
|
-
statistics.accepted += 1;
|
|
276
|
-
await dispatch(message);
|
|
277
|
-
if (options.operation?.signal?.aborted)
|
|
278
|
-
return complete("STOPPED");
|
|
279
|
-
if (!(await validate()))
|
|
280
|
-
return complete("STOPPED");
|
|
281
|
-
await markDelivered(message);
|
|
282
|
-
}
|
|
283
|
-
catch (error) {
|
|
284
|
-
statistics.failed += 1;
|
|
285
|
-
failures.push(Object.freeze({ message: snapshot(message), error }));
|
|
286
|
-
const reception = new FailedReception(message, error, async () => {
|
|
287
|
-
if (!(await validate()))
|
|
288
|
-
throw new Error("Shard ownership was lost.");
|
|
289
|
-
await markDelivered(message);
|
|
290
|
-
}, async () => {
|
|
291
|
-
await dispatch(message);
|
|
292
|
-
if (!(await validate()))
|
|
293
|
-
throw new Error("Shard ownership was lost.");
|
|
294
|
-
await markDelivered(message);
|
|
295
|
-
});
|
|
296
|
-
const action = await safelyValue(() => this.#monitor.onReceptionFailure(reception), reception.markDelivered());
|
|
297
|
-
if (!(await safely(() => action.execute())) &&
|
|
298
|
-
!(await safely(() => reception.markDelivered().execute()))) {
|
|
299
|
-
blockedTargets.add(target);
|
|
300
|
-
}
|
|
301
|
-
if (ownership.lost)
|
|
302
|
-
return complete("STOPPED");
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
if (!(await cleanupPage()))
|
|
306
|
-
return complete("STOPPED");
|
|
307
|
-
const last = messages.at(-1);
|
|
308
|
-
if (statistics.delivered !== deliveredBefore) {
|
|
309
|
-
after = undefined;
|
|
310
|
-
continue;
|
|
311
|
-
}
|
|
312
|
-
if (last === undefined || messages.length < this.pageSize)
|
|
313
|
-
break;
|
|
314
|
-
after = {
|
|
315
|
-
messageId: last.id.value,
|
|
316
|
-
whenReceived: last.whenReceived,
|
|
317
|
-
version: last.version,
|
|
318
|
-
};
|
|
319
|
-
// Reached a full page without progress: continue past it once. A later
|
|
320
|
-
// independent target may still be actionable; exhaustion ends the run.
|
|
321
|
-
}
|
|
322
|
-
return complete("DRAINED");
|
|
397
|
+
if (!(await this.input.inbox.removeDuplicate(message, this.#current, this.input.options.operation)))
|
|
398
|
+
throw new Error("Inbox duplicate was not removed.");
|
|
399
|
+
this.#removedInPage.add(message);
|
|
400
|
+
this.input.options.onDuplicateRemoved?.(message);
|
|
401
|
+
}
|
|
402
|
+
catch (error) {
|
|
403
|
+
this.#recordFailure(message, error);
|
|
404
|
+
this.#blockedTargets.add(target);
|
|
405
|
+
}
|
|
406
|
+
return true;
|
|
407
|
+
}
|
|
408
|
+
/**
|
|
409
|
+
* Delivers one message and records its outcome.
|
|
410
|
+
*
|
|
411
|
+
* @param message Supplies the inbox message.
|
|
412
|
+
* @param target Identifies the message target.
|
|
413
|
+
* @returns Whether processing may continue.
|
|
414
|
+
*/
|
|
415
|
+
async #deliver(message, target) {
|
|
416
|
+
try {
|
|
417
|
+
this.#statistics.accepted += 1;
|
|
418
|
+
await this.#dispatch(message);
|
|
419
|
+
if (this.input.options.operation?.signal?.aborted || !(await this.#validate()))
|
|
420
|
+
return false;
|
|
421
|
+
await this.#markDelivered(message);
|
|
323
422
|
}
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
if (
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
423
|
+
catch (error) {
|
|
424
|
+
await this.#handleReceptionFailure(message, target, error);
|
|
425
|
+
if (this.#ownershipLost)
|
|
426
|
+
return false;
|
|
427
|
+
}
|
|
428
|
+
return true;
|
|
429
|
+
}
|
|
430
|
+
/**
|
|
431
|
+
* Records endpoint failure and runs the configured recovery action.
|
|
432
|
+
*
|
|
433
|
+
* @param message Supplies the failed inbox message.
|
|
434
|
+
* @param target Identifies the message target.
|
|
435
|
+
* @param error Supplies the endpoint or acknowledgement failure.
|
|
436
|
+
* @returns Completion after the recovery action finishes.
|
|
437
|
+
*/
|
|
438
|
+
async #handleReceptionFailure(message, target, error) {
|
|
439
|
+
this.#recordFailure(message, error);
|
|
440
|
+
const reception = new FailedReception(message, error, () => this.#acknowledge(message), () => this.#repeat(message));
|
|
441
|
+
const action = await safelyValue(() => this.input.monitor.onReceptionFailure(reception), reception.markDelivered());
|
|
442
|
+
if (!(await safely(() => action.execute())) &&
|
|
443
|
+
!(await safely(() => reception.markDelivered().execute())))
|
|
444
|
+
this.#blockedTargets.add(target);
|
|
445
|
+
}
|
|
446
|
+
/**
|
|
447
|
+
* Marks a message delivered after checking the shard session.
|
|
448
|
+
*
|
|
449
|
+
* @param message Supplies the inbox message to acknowledge.
|
|
450
|
+
* @returns Completion after the message is marked delivered.
|
|
451
|
+
*/
|
|
452
|
+
async #acknowledge(message) {
|
|
453
|
+
if (!(await this.#validate()))
|
|
454
|
+
throw new Error("Shard ownership was lost.");
|
|
455
|
+
await this.#markDelivered(message);
|
|
456
|
+
}
|
|
457
|
+
/**
|
|
458
|
+
* Dispatches the message again and acknowledges it.
|
|
459
|
+
*
|
|
460
|
+
* @param message Supplies the inbox message to repeat.
|
|
461
|
+
* @returns Completion after dispatch and acknowledgement.
|
|
462
|
+
*/
|
|
463
|
+
async #repeat(message) {
|
|
464
|
+
await this.#dispatch(message);
|
|
465
|
+
await this.#acknowledge(message);
|
|
466
|
+
}
|
|
467
|
+
/**
|
|
468
|
+
* Dispatches a message while validating the shard session before the call.
|
|
469
|
+
*
|
|
470
|
+
* @param message Supplies the inbox message for the endpoint.
|
|
471
|
+
* @returns Completion after endpoint dispatch.
|
|
472
|
+
*/
|
|
473
|
+
async #dispatch(message) {
|
|
474
|
+
await withDeliveryCommitFence(async () => {
|
|
475
|
+
if (!(await this.#validate()))
|
|
476
|
+
throw new Error("Shard ownership was lost.");
|
|
477
|
+
}, () => Promise.resolve(this.input.options.onMessage(message)));
|
|
478
|
+
}
|
|
479
|
+
/**
|
|
480
|
+
* Marks a message delivered and updates local counts and callbacks.
|
|
481
|
+
*
|
|
482
|
+
* @param message Supplies the inbox message to acknowledge.
|
|
483
|
+
* @returns Completion after durable acknowledgement.
|
|
484
|
+
*/
|
|
485
|
+
async #markDelivered(message) {
|
|
486
|
+
if ((await this.input.inbox.markDelivered(message, this.input.options.operation)) === undefined)
|
|
487
|
+
throw new Error("Inbox message was not marked delivered.");
|
|
488
|
+
this.#deduplication.recordDelivered(message);
|
|
489
|
+
this.#statistics.delivered += 1;
|
|
490
|
+
this.input.options.onDelivered?.(message);
|
|
491
|
+
}
|
|
492
|
+
/**
|
|
493
|
+
* Removes expired delivered messages from one page when supported.
|
|
494
|
+
*
|
|
495
|
+
* @param messages Supplies the inbox page to clean up.
|
|
496
|
+
* @returns Whether cleanup completed while the session remained valid.
|
|
497
|
+
*/
|
|
498
|
+
async #cleanupPage(messages) {
|
|
499
|
+
if (this.input.inbox.removeDelivered === undefined)
|
|
500
|
+
return true;
|
|
501
|
+
if (!(await this.#validate()))
|
|
502
|
+
return false;
|
|
503
|
+
const now = this.#retentionTime().getTime();
|
|
504
|
+
for (const message of messages) {
|
|
505
|
+
if (message.status === "DELIVERED" &&
|
|
506
|
+
(message.keepUntil === undefined || message.keepUntil.getTime() <= now)) {
|
|
507
|
+
const removed = await this.#removeDelivered(message);
|
|
508
|
+
if (removed === undefined)
|
|
509
|
+
return false;
|
|
510
|
+
if (removed)
|
|
511
|
+
this.#removedInPage.add(message);
|
|
331
512
|
}
|
|
332
|
-
await safely(() => this.#monitor.onDeliveryCompleted(Object.freeze({
|
|
333
|
-
processed: statistics.processed,
|
|
334
|
-
delivered: statistics.delivered,
|
|
335
|
-
failed: statistics.failed,
|
|
336
|
-
})));
|
|
337
513
|
}
|
|
514
|
+
return true;
|
|
515
|
+
}
|
|
516
|
+
/**
|
|
517
|
+
* Removes one expired delivered message when supported.
|
|
518
|
+
*
|
|
519
|
+
* @param message Supplies the delivered message to remove.
|
|
520
|
+
* @returns Whether the row was removed, or undefined if the session was lost.
|
|
521
|
+
*/
|
|
522
|
+
async #removeDelivered(message) {
|
|
523
|
+
if (this.input.inbox.removeDelivered === undefined)
|
|
524
|
+
return false;
|
|
525
|
+
if (this.input.options.operation?.signal?.aborted || !(await this.#validate()))
|
|
526
|
+
return undefined;
|
|
527
|
+
const removed = await this.input.inbox.removeDelivered(message, this.#current, this.input.options.operation);
|
|
528
|
+
if (this.input.options.operation?.signal?.aborted || (!removed && !(await this.#validate())))
|
|
529
|
+
return undefined;
|
|
530
|
+
return removed;
|
|
531
|
+
}
|
|
532
|
+
/**
|
|
533
|
+
* Creates the continuation following the last message in a page.
|
|
534
|
+
*
|
|
535
|
+
* @param messages Supplies the non-empty inbox page.
|
|
536
|
+
* @returns The continuation identifying its last message.
|
|
537
|
+
*/
|
|
538
|
+
#continuation(messages) {
|
|
539
|
+
const last = messages.at(-1);
|
|
540
|
+
if (last === undefined)
|
|
541
|
+
throw new Error("Inbox page has no continuation.");
|
|
542
|
+
return { messageId: last.id.value, whenReceived: last.whenReceived, version: last.version };
|
|
543
|
+
}
|
|
544
|
+
/**
|
|
545
|
+
* Reads the inbox retention clock or the current time.
|
|
546
|
+
*
|
|
547
|
+
* @returns The time used to evaluate message retention.
|
|
548
|
+
*/
|
|
549
|
+
#retentionTime() {
|
|
550
|
+
return this.input.inbox instanceof Inbox
|
|
551
|
+
? InboxStorageClock.read(this.input.inbox.storage)
|
|
552
|
+
: new Date();
|
|
553
|
+
}
|
|
554
|
+
/**
|
|
555
|
+
* Checks the shard session and records when it is lost.
|
|
556
|
+
*
|
|
557
|
+
* @returns Whether the shard session remains valid.
|
|
558
|
+
*/
|
|
559
|
+
async #validate() {
|
|
560
|
+
const current = await safelyValue(() => this.input.shards.validateOwnership(this.#current, this.input.options.operation), undefined);
|
|
561
|
+
if (current === undefined)
|
|
562
|
+
this.#ownershipLost = true;
|
|
563
|
+
else
|
|
564
|
+
this.#current = current;
|
|
565
|
+
return current !== undefined;
|
|
566
|
+
}
|
|
567
|
+
/**
|
|
568
|
+
* Records an ephemeral delivery failure up to the configured limit.
|
|
569
|
+
*
|
|
570
|
+
* @param message Supplies the failed inbox message.
|
|
571
|
+
* @param error Supplies the observed failure.
|
|
572
|
+
*/
|
|
573
|
+
#recordFailure(message, error) {
|
|
574
|
+
this.#statistics.failed += 1;
|
|
575
|
+
if (this.#failures.length < deliveryFailureLimit)
|
|
576
|
+
this.#failures.push(Object.freeze({ message: snapshot(message), error }));
|
|
577
|
+
this.#failureLimitReached = this.#failures.length === deliveryFailureLimit;
|
|
578
|
+
}
|
|
579
|
+
/**
|
|
580
|
+
* Creates a terminal result from the current counters and failures.
|
|
581
|
+
*
|
|
582
|
+
* @param status Identifies the terminal delivery outcome.
|
|
583
|
+
* @returns The immutable delivery result.
|
|
584
|
+
*/
|
|
585
|
+
#complete(status) {
|
|
586
|
+
return result(status, this.#statistics, this.#failures);
|
|
587
|
+
}
|
|
588
|
+
/**
|
|
589
|
+
* Sends delivery completion to the monitor.
|
|
590
|
+
*
|
|
591
|
+
* @returns Completion after the monitor callback settles.
|
|
592
|
+
*/
|
|
593
|
+
#completeMonitoring() {
|
|
594
|
+
return Promise.resolve(this.input.monitor.onDeliveryCompleted(Object.freeze({
|
|
595
|
+
processed: this.#statistics.processed,
|
|
596
|
+
delivered: this.#statistics.delivered,
|
|
597
|
+
failed: this.#statistics.failed,
|
|
598
|
+
})));
|
|
338
599
|
}
|
|
339
600
|
}
|
|
340
601
|
function workerId(node) {
|
|
@@ -384,6 +645,14 @@ async function safelyBoolean(action) {
|
|
|
384
645
|
return false;
|
|
385
646
|
}
|
|
386
647
|
}
|
|
648
|
+
/**
|
|
649
|
+
* Returns an action result or a fallback when the action fails.
|
|
650
|
+
*
|
|
651
|
+
* @typeParam T Action result type.
|
|
652
|
+
* @param action Supplies the action to run.
|
|
653
|
+
* @param fallback Supplies the result to use after failure.
|
|
654
|
+
* @returns The action result or fallback value.
|
|
655
|
+
*/
|
|
387
656
|
async function safelyValue(action, fallback) {
|
|
388
657
|
try {
|
|
389
658
|
return await action();
|
|
@@ -397,4 +666,118 @@ function isEndpointMessage(message) {
|
|
|
397
666
|
message.label === "UPDATE_SUBSCRIBER" ||
|
|
398
667
|
message.label === "REACT_UPON_EVENT");
|
|
399
668
|
}
|
|
669
|
+
/**
|
|
670
|
+
* Tracks recently delivered messages for one inbox.
|
|
671
|
+
*/
|
|
672
|
+
class DeliveryDeduplication {
|
|
673
|
+
#recent;
|
|
674
|
+
/**
|
|
675
|
+
* Creates or reuses recent delivery state for the supplied inbox.
|
|
676
|
+
*
|
|
677
|
+
* @param inbox Supplies the inbox used as the deduplication key.
|
|
678
|
+
*/
|
|
679
|
+
constructor(inbox) {
|
|
680
|
+
const current = recentDeliveries.get(inbox);
|
|
681
|
+
this.#recent = current ?? new RecentDeliveries();
|
|
682
|
+
if (current === undefined)
|
|
683
|
+
recentDeliveries.set(inbox, this.#recent);
|
|
684
|
+
}
|
|
685
|
+
/**
|
|
686
|
+
* Creates duplicate checks for one page and retention time.
|
|
687
|
+
*
|
|
688
|
+
* @param messages Supplies the messages in the page.
|
|
689
|
+
* @param now Supplies the time used to check message retention.
|
|
690
|
+
* @returns Duplicate checks for the page.
|
|
691
|
+
*/
|
|
692
|
+
page(messages, now) {
|
|
693
|
+
return new DeliveryPageDeduplication(messages, this.#recent, now);
|
|
694
|
+
}
|
|
695
|
+
/**
|
|
696
|
+
* Records a message as recently delivered.
|
|
697
|
+
*
|
|
698
|
+
* @param message Supplies the delivered message.
|
|
699
|
+
*/
|
|
700
|
+
recordDelivered(message) {
|
|
701
|
+
this.#recent.add(message);
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
/**
|
|
705
|
+
* Checks duplicates within a page and the recent-delivery cache.
|
|
706
|
+
*/
|
|
707
|
+
class DeliveryPageDeduplication {
|
|
708
|
+
recent;
|
|
709
|
+
#identities;
|
|
710
|
+
/**
|
|
711
|
+
* Creates duplicate checks from retained messages in one page.
|
|
712
|
+
*
|
|
713
|
+
* @param messages Supplies the inbox page.
|
|
714
|
+
* @param recent Provides recently delivered message identities.
|
|
715
|
+
* @param now Supplies the time used to check message retention.
|
|
716
|
+
*/
|
|
717
|
+
constructor(messages, recent, now) {
|
|
718
|
+
this.recent = recent;
|
|
719
|
+
this.#identities = new Set(messages
|
|
720
|
+
.filter((message) => message.status === "DELIVERED" &&
|
|
721
|
+
message.keepUntil !== undefined &&
|
|
722
|
+
message.keepUntil.getTime() > now.getTime())
|
|
723
|
+
.map((message) => RecentDeliveries.key(message)));
|
|
724
|
+
}
|
|
725
|
+
/**
|
|
726
|
+
* Checks whether a message appeared earlier in the page or cache.
|
|
727
|
+
*
|
|
728
|
+
* @param message Supplies the message to check.
|
|
729
|
+
* @returns Whether this message is a duplicate.
|
|
730
|
+
*/
|
|
731
|
+
isDuplicate(message) {
|
|
732
|
+
const identity = RecentDeliveries.key(message);
|
|
733
|
+
if (this.#identities.has(identity) || this.recent.has(identity))
|
|
734
|
+
return true;
|
|
735
|
+
this.#identities.add(identity);
|
|
736
|
+
return false;
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
/**
|
|
740
|
+
* Stores a bounded set of recently delivered message identities.
|
|
741
|
+
*/
|
|
742
|
+
class RecentDeliveries {
|
|
743
|
+
#identities = new Map();
|
|
744
|
+
/**
|
|
745
|
+
* Checks whether an identity is in the recent-delivery set.
|
|
746
|
+
*
|
|
747
|
+
* @param identity Supplies the encoded message identity.
|
|
748
|
+
* @returns Whether that identity is present.
|
|
749
|
+
*/
|
|
750
|
+
has(identity) {
|
|
751
|
+
return this.#identities.has(identity);
|
|
752
|
+
}
|
|
753
|
+
/**
|
|
754
|
+
* Adds a message identity and removes the oldest entry when over capacity.
|
|
755
|
+
*
|
|
756
|
+
* @param message Supplies the delivered message.
|
|
757
|
+
*/
|
|
758
|
+
add(message) {
|
|
759
|
+
const identity = RecentDeliveries.key(message);
|
|
760
|
+
this.#identities.delete(identity);
|
|
761
|
+
this.#identities.set(identity, undefined);
|
|
762
|
+
if (this.#identities.size > 1_000) {
|
|
763
|
+
const oldest = this.#identities.keys().next().value;
|
|
764
|
+
if (oldest !== undefined)
|
|
765
|
+
this.#identities.delete(oldest);
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
/**
|
|
769
|
+
* Creates the identity key used for recent-delivery checks.
|
|
770
|
+
*
|
|
771
|
+
* @param message Supplies the message to identify.
|
|
772
|
+
* @returns The serialized message identity.
|
|
773
|
+
*/
|
|
774
|
+
static key(message) {
|
|
775
|
+
return JSON.stringify([
|
|
776
|
+
message.signalId,
|
|
777
|
+
message.inboxId.targetTypeUrl,
|
|
778
|
+
InboxTargets.key(message.inboxId.targetId),
|
|
779
|
+
]);
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
const recentDeliveries = new WeakMap();
|
|
400
783
|
//# sourceMappingURL=delivery.js.map
|