@loomwork/event-runtime 0.0.0-stage → 0.1.1
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/LICENSE +202 -0
- package/NOTICE +8 -0
- package/README.md +95 -2
- package/dist/BranchRegistry.d.ts +140 -0
- package/dist/BranchRegistry.d.ts.map +1 -0
- package/dist/BranchRegistry.js +66 -0
- package/dist/BranchRegistry.js.map +1 -0
- package/dist/CoalescingEventStore.d.ts +97 -0
- package/dist/CoalescingEventStore.d.ts.map +1 -0
- package/dist/CoalescingEventStore.js +180 -0
- package/dist/CoalescingEventStore.js.map +1 -0
- package/dist/InMemoryArchiveStore.d.ts +19 -0
- package/dist/InMemoryArchiveStore.d.ts.map +1 -0
- package/dist/InMemoryArchiveStore.js +49 -0
- package/dist/InMemoryArchiveStore.js.map +1 -0
- package/dist/InMemoryAsyncEventStore.d.ts +111 -0
- package/dist/InMemoryAsyncEventStore.d.ts.map +1 -0
- package/dist/InMemoryAsyncEventStore.js +374 -0
- package/dist/InMemoryAsyncEventStore.js.map +1 -0
- package/dist/InMemoryBranchRegistry.d.ts +31 -0
- package/dist/InMemoryBranchRegistry.d.ts.map +1 -0
- package/dist/InMemoryBranchRegistry.js +167 -0
- package/dist/InMemoryBranchRegistry.js.map +1 -0
- package/dist/ProfilingEventStore.d.ts +75 -0
- package/dist/ProfilingEventStore.d.ts.map +1 -0
- package/dist/ProfilingEventStore.js +133 -0
- package/dist/ProfilingEventStore.js.map +1 -0
- package/dist/ShardPlan.d.ts +87 -0
- package/dist/ShardPlan.d.ts.map +1 -0
- package/dist/ShardPlan.js +193 -0
- package/dist/ShardPlan.js.map +1 -0
- package/dist/ShardedEventStore.d.ts +149 -0
- package/dist/ShardedEventStore.d.ts.map +1 -0
- package/dist/ShardedEventStore.js +468 -0
- package/dist/ShardedEventStore.js.map +1 -0
- package/dist/SubscriberRegistry.d.ts +59 -0
- package/dist/SubscriberRegistry.d.ts.map +1 -0
- package/dist/SubscriberRegistry.js +113 -0
- package/dist/SubscriberRegistry.js.map +1 -0
- package/dist/TenantScopedArchiveStore.d.ts +36 -0
- package/dist/TenantScopedArchiveStore.d.ts.map +1 -0
- package/dist/TenantScopedArchiveStore.js +96 -0
- package/dist/TenantScopedArchiveStore.js.map +1 -0
- package/dist/TenantScopedEventStore.d.ts +59 -0
- package/dist/TenantScopedEventStore.d.ts.map +1 -0
- package/dist/TenantScopedEventStore.js +227 -0
- package/dist/TenantScopedEventStore.js.map +1 -0
- package/dist/archive.d.ts +68 -0
- package/dist/archive.d.ts.map +1 -0
- package/dist/archive.js +207 -0
- package/dist/archive.js.map +1 -0
- package/dist/archiveSpi.d.ts +165 -0
- package/dist/archiveSpi.d.ts.map +1 -0
- package/dist/archiveSpi.js +184 -0
- package/dist/archiveSpi.js.map +1 -0
- package/dist/creationMarker.d.ts +13 -0
- package/dist/creationMarker.d.ts.map +1 -0
- package/dist/creationMarker.js +17 -0
- package/dist/creationMarker.js.map +1 -0
- package/dist/decoratorPlan.typecheck.d.ts +2 -0
- package/dist/decoratorPlan.typecheck.d.ts.map +1 -0
- package/dist/decoratorPlan.typecheck.js +41 -0
- package/dist/decoratorPlan.typecheck.js.map +1 -0
- package/dist/eventKindClass.d.ts +40 -0
- package/dist/eventKindClass.d.ts.map +1 -0
- package/dist/eventKindClass.js +99 -0
- package/dist/eventKindClass.js.map +1 -0
- package/dist/executionIdOrder.d.ts +21 -0
- package/dist/executionIdOrder.d.ts.map +1 -0
- package/dist/executionIdOrder.js +43 -0
- package/dist/executionIdOrder.js.map +1 -0
- package/dist/fencing.d.ts +21 -0
- package/dist/fencing.d.ts.map +1 -0
- package/dist/fencing.js +36 -0
- package/dist/fencing.js.map +1 -0
- package/dist/index.d.ts +39 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +126 -0
- package/dist/index.js.map +1 -0
- package/dist/installOptional.d.ts +52 -0
- package/dist/installOptional.d.ts.map +1 -0
- package/dist/installOptional.js +112 -0
- package/dist/installOptional.js.map +1 -0
- package/dist/keyUnavailable.d.ts +67 -0
- package/dist/keyUnavailable.d.ts.map +1 -0
- package/dist/keyUnavailable.js +87 -0
- package/dist/keyUnavailable.js.map +1 -0
- package/dist/leases.d.ts +177 -0
- package/dist/leases.d.ts.map +1 -0
- package/dist/leases.js +142 -0
- package/dist/leases.js.map +1 -0
- package/dist/payloadShredded.d.ts +30 -0
- package/dist/payloadShredded.d.ts.map +1 -0
- package/dist/payloadShredded.js +67 -0
- package/dist/payloadShredded.js.map +1 -0
- package/dist/scaleDecorators.bench.d.ts +2 -0
- package/dist/scaleDecorators.bench.d.ts.map +1 -0
- package/dist/scaleDecorators.bench.js +93 -0
- package/dist/scaleDecorators.bench.js.map +1 -0
- package/dist/sequenceRange.bench.d.ts +2 -0
- package/dist/sequenceRange.bench.d.ts.map +1 -0
- package/dist/sequenceRange.bench.js +46 -0
- package/dist/sequenceRange.bench.js.map +1 -0
- package/dist/sequenceRange.d.ts +10 -0
- package/dist/sequenceRange.d.ts.map +1 -0
- package/dist/sequenceRange.js +35 -0
- package/dist/sequenceRange.js.map +1 -0
- package/dist/timers.d.ts +87 -0
- package/dist/timers.d.ts.map +1 -0
- package/dist/timers.js +68 -0
- package/dist/timers.js.map +1 -0
- package/dist/types.d.ts +634 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +429 -0
- package/dist/types.js.map +1 -0
- package/dist/writeConflicts.d.ts +46 -0
- package/dist/writeConflicts.d.ts.map +1 -0
- package/dist/writeConflicts.js +63 -0
- package/dist/writeConflicts.js.map +1 -0
- package/memory.d.ts +4 -0
- package/memory.js +13 -0
- package/package.json +67 -3
- package/sequence.d.ts +1 -0
- package/sequence.js +5 -0
- package/src/BranchRegistry.ts +161 -0
- package/src/CoalescingEventStore.ts +214 -0
- package/src/InMemoryArchiveStore.ts +69 -0
- package/src/InMemoryAsyncEventStore.ts +425 -0
- package/src/InMemoryBranchRegistry.ts +178 -0
- package/src/ProfilingEventStore.ts +153 -0
- package/src/ShardPlan.ts +235 -0
- package/src/ShardedEventStore.ts +557 -0
- package/src/SubscriberRegistry.ts +115 -0
- package/src/TenantScopedArchiveStore.ts +114 -0
- package/src/TenantScopedEventStore.ts +266 -0
- package/src/archive.ts +248 -0
- package/src/archiveSpi.ts +308 -0
- package/src/creationMarker.ts +15 -0
- package/src/decoratorPlan.typecheck.ts +52 -0
- package/src/eventKindClass.ts +127 -0
- package/src/executionIdOrder.ts +38 -0
- package/src/fencing.ts +31 -0
- package/src/index.ts +151 -0
- package/src/installOptional.ts +136 -0
- package/src/keyUnavailable.ts +98 -0
- package/src/leases.ts +319 -0
- package/src/payloadShredded.ts +63 -0
- package/src/scaleDecorators.bench.ts +107 -0
- package/src/sequenceRange.bench.ts +51 -0
- package/src/sequenceRange.ts +35 -0
- package/src/timers.ts +150 -0
- package/src/types.ts +857 -0
- package/src/writeConflicts.ts +56 -0
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import type { EventEnvelope } from "@loomwork/core";
|
|
2
|
+
import type { ShardPlan } from "./ShardPlan";
|
|
3
|
+
import { type EventStore, type SealingCapability } from "./types";
|
|
4
|
+
export interface ShardedEventStoreOptions {
|
|
5
|
+
/**
|
|
6
|
+
* Which shard owns an execution, when no {@link ShardedEventStoreOptions.plan} is given.
|
|
7
|
+
* Must be STABLE across processes and restarts — two nodes disagreeing routes the same
|
|
8
|
+
* execution to two logs. Default: FNV-1a over the id.
|
|
9
|
+
*/
|
|
10
|
+
shardOf?: (executionId: string, shards: number) => number;
|
|
11
|
+
/**
|
|
12
|
+
* An explicit bucket→shard map (ADR 0075). **Required to rebalance**: without it, routing
|
|
13
|
+
* is `hash(id) % shardCount` and changing the shard count silently re-routes almost every
|
|
14
|
+
* execution to a shard that has never seen its data.
|
|
15
|
+
*
|
|
16
|
+
* Omitting it keeps the pre-0075 routing exactly, so existing deployments are unaffected
|
|
17
|
+
* — but they cannot change shard count without moving everything. Start with a plan if
|
|
18
|
+
* you expect to grow.
|
|
19
|
+
*/
|
|
20
|
+
plan?: ShardPlan;
|
|
21
|
+
/**
|
|
22
|
+
* How many execution→shard resolutions to remember while buckets are migrating. Bounded
|
|
23
|
+
* so a long-lived store cannot grow without limit. Default 10,000.
|
|
24
|
+
*/
|
|
25
|
+
placementCacheSize?: number;
|
|
26
|
+
}
|
|
27
|
+
/** Result of moving one execution between shards. */
|
|
28
|
+
export interface MigrationResult {
|
|
29
|
+
executionId: string;
|
|
30
|
+
/** `moved` — copied then removed from the source. `already-there` — nothing to do. */
|
|
31
|
+
outcome: "moved" | "already-there" | "not-migrating";
|
|
32
|
+
events: number;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* ADR 0129 — an incarnation counter is per shard, so the same number can be issued by two shards
|
|
36
|
+
* (a migrating bucket re-creates an id on another shard). The anchor this store hands out is
|
|
37
|
+
* therefore `s<shardIndex>/<inner anchor>`: it starts with a letter, so it can collide neither
|
|
38
|
+
* with a bare incarnation (digits) nor with the legacy `<timestamp>:<id>` marker (digits first),
|
|
39
|
+
* and an observation taken on one shard never equals the anchor of another.
|
|
40
|
+
*/
|
|
41
|
+
/**
|
|
42
|
+
* ADR 0130 residuals D7/D8: the shards of one store disagree about sealing (some seal and some do
|
|
43
|
+
* not, or they seal different paths or with different ciphers). Misconfiguration, raised live on
|
|
44
|
+
* every `sealingCapability()` call and never papered over, because one answer serves two questions
|
|
45
|
+
* that must not diverge: "what can this node open" (D7) and "does this store seal at all" (D8, a
|
|
46
|
+
* non-empty `covers`). Carries each shard's cipher and covers, never key material.
|
|
47
|
+
*/
|
|
48
|
+
export declare class ShardSealingMismatchError extends Error {
|
|
49
|
+
readonly code = "SHARD_SEALING_MISMATCH";
|
|
50
|
+
readonly shards: readonly {
|
|
51
|
+
readonly index: number;
|
|
52
|
+
readonly cipher: string | undefined;
|
|
53
|
+
readonly covers: readonly string[] | undefined;
|
|
54
|
+
}[];
|
|
55
|
+
constructor(caps: readonly (SealingCapability | undefined)[]);
|
|
56
|
+
}
|
|
57
|
+
export declare class ShardedEventStore implements EventStore {
|
|
58
|
+
readonly head?: NonNullable<EventStore["head"]>;
|
|
59
|
+
readonly readRange?: NonNullable<EventStore["readRange"]>;
|
|
60
|
+
readonly acquireFence?: NonNullable<EventStore["acquireFence"]>;
|
|
61
|
+
readonly acquireFenceIf?: NonNullable<EventStore["acquireFenceIf"]>;
|
|
62
|
+
readonly appendIfUnchanged?: NonNullable<EventStore["appendIfUnchanged"]>;
|
|
63
|
+
readonly storeNow?: NonNullable<EventStore["storeNow"]>;
|
|
64
|
+
readonly sealingCapability?: NonNullable<EventStore["sealingCapability"]>;
|
|
65
|
+
readonly readAtRest?: NonNullable<EventStore["readAtRest"]>;
|
|
66
|
+
readonly deleteExecution?: NonNullable<EventStore["deleteExecution"]>;
|
|
67
|
+
readonly deleteThrough?: NonNullable<EventStore["deleteThrough"]>;
|
|
68
|
+
readonly truncatePrefix?: NonNullable<EventStore["truncatePrefix"]>;
|
|
69
|
+
readonly truncatePrefixKeeping?: NonNullable<EventStore["truncatePrefixKeeping"]>;
|
|
70
|
+
readonly listExecutions?: NonNullable<EventStore["listExecutions"]>;
|
|
71
|
+
readonly listExecutionsPage?: NonNullable<EventStore["listExecutionsPage"]>;
|
|
72
|
+
private readonly shards;
|
|
73
|
+
private readonly shardOf;
|
|
74
|
+
private plan;
|
|
75
|
+
private readonly cacheLimit;
|
|
76
|
+
/** executionId → shard, only for executions in migrating buckets. Insertion-ordered LRU. */
|
|
77
|
+
private readonly placementCache;
|
|
78
|
+
constructor(shards: readonly EventStore[], options?: ShardedEventStoreOptions);
|
|
79
|
+
/**
|
|
80
|
+
* Install a new routing plan — how a rebalance is applied, and how a drained bucket is
|
|
81
|
+
* settled. **Every node sharing the log must converge on the same plan**; different plans
|
|
82
|
+
* on different nodes split the log.
|
|
83
|
+
*/
|
|
84
|
+
setPlan(plan: ShardPlan): void;
|
|
85
|
+
/** The active plan, if routing is plan-driven. */
|
|
86
|
+
get shardPlan(): ShardPlan | undefined;
|
|
87
|
+
/** Resolve the store owning `executionId` right now. */
|
|
88
|
+
private route;
|
|
89
|
+
private routeIndex;
|
|
90
|
+
private remember;
|
|
91
|
+
/**
|
|
92
|
+
* Move one execution from a migrating bucket's source to its destination: copy every
|
|
93
|
+
* event, then delete the source copy.
|
|
94
|
+
*
|
|
95
|
+
* **The caller must ensure the execution is not being written concurrently** — an append
|
|
96
|
+
* arriving mid-move lands on the source and is then deleted. Terminal executions are the
|
|
97
|
+
* safe case, and the lazy drain described on the class means you usually do not need this
|
|
98
|
+
* at all; reach for it when a source shard must be decommissioned.
|
|
99
|
+
*
|
|
100
|
+
* Fence state does not move (the SPI has no way to read it), so the execution arrives
|
|
101
|
+
* unfenced on the destination.
|
|
102
|
+
*/
|
|
103
|
+
migrateExecution(executionId: string): Promise<MigrationResult>;
|
|
104
|
+
/**
|
|
105
|
+
* Whether a migrating bucket's source side still holds anything — i.e. whether
|
|
106
|
+
* `plan.settled(bucket)` is safe yet. `true` for a settled bucket (nothing to drain).
|
|
107
|
+
*
|
|
108
|
+
* Requires enumerable shards.
|
|
109
|
+
*/
|
|
110
|
+
isBucketDrained(bucket: number): Promise<boolean>;
|
|
111
|
+
append(envelope: EventEnvelope): Promise<void>;
|
|
112
|
+
/**
|
|
113
|
+
* Split a batch by shard and append each part. Shards are independent logs, so the parts
|
|
114
|
+
* run concurrently.
|
|
115
|
+
*
|
|
116
|
+
* Atomicity is per shard, which is exactly what the SPI requires: the contract is atomic
|
|
117
|
+
* PER EXECUTION, and an execution never spans shards. Whole-batch atomicity across
|
|
118
|
+
* shards would need a distributed transaction, costs far more than batching saves, and
|
|
119
|
+
* nothing needs it — a batch only groups INDEPENDENT executions, so nothing in it
|
|
120
|
+
* depends on anything else in it. `Promise.all` rejects if any shard does, so a caller
|
|
121
|
+
* still never reads a partial write as success.
|
|
122
|
+
*/
|
|
123
|
+
appendBatch(envelopes: readonly EventEnvelope[]): Promise<void>;
|
|
124
|
+
read(executionId: string): Promise<EventEnvelope[]>;
|
|
125
|
+
readFrom(executionId: string, fromSequence: number): Promise<EventEnvelope[]>;
|
|
126
|
+
subscribe(executionId: string, handler: (event: EventEnvelope) => void): Promise<() => void>;
|
|
127
|
+
/**
|
|
128
|
+
* Fork. Fast when parent and child resolve to the same shard; otherwise the prefix has to
|
|
129
|
+
* cross a store boundary, so we use the SPI's documented per-event fallback.
|
|
130
|
+
*/
|
|
131
|
+
copyPrefix(fromExecutionId: string, toExecutionId: string, throughSequence: number, metadata: Record<string, unknown>): Promise<void>;
|
|
132
|
+
/**
|
|
133
|
+
* Keyset pagination across shards. Each shard is asked for its next page beyond the
|
|
134
|
+
* cursor; the results are merged and truncated to `limit`, so the caller sees one
|
|
135
|
+
* globally ordered stream and the cursor stays a single id.
|
|
136
|
+
*
|
|
137
|
+
* Correct ONLY because every shard pages in the SPI's one order (code point, SPI 2.0.0)
|
|
138
|
+
* and accepts any id as a cursor: the merged page's last id is handed back to EVERY shard,
|
|
139
|
+
* including shards that never returned it. A shard paging in another order (a Postgres
|
|
140
|
+
* database collation, before L10) skips the ids that sort after the cursor in one order
|
|
141
|
+
* and before it in the other — silently, since every page still looks well-formed.
|
|
142
|
+
*
|
|
143
|
+
* Deduplicated for the same reason as `listExecutions`: mid-migration an execution can
|
|
144
|
+
* briefly exist on two shards.
|
|
145
|
+
*/
|
|
146
|
+
private pagedScatterGather;
|
|
147
|
+
close(): Promise<void>;
|
|
148
|
+
}
|
|
149
|
+
//# sourceMappingURL=ShardedEventStore.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ShardedEventStore.d.ts","sourceRoot":"","sources":["../src/ShardedEventStore.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,468 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ShardedEventStore = exports.ShardSealingMismatchError = void 0;
|
|
4
|
+
const SubscriberRegistry_1 = require("./SubscriberRegistry");
|
|
5
|
+
const executionIdOrder_1 = require("./executionIdOrder");
|
|
6
|
+
const installOptional_1 = require("./installOptional");
|
|
7
|
+
const types_1 = require("./types");
|
|
8
|
+
/**
|
|
9
|
+
* Routes each execution to one of N inner stores (ADR 0074 D3), with support for moving
|
|
10
|
+
* buckets between shards without downtime (ADR 0075).
|
|
11
|
+
*
|
|
12
|
+
* This is the item that needs **zero backend cooperation**: it takes `EventStore[]` and
|
|
13
|
+
* never asks what it is wrapping, so a bring-your-own backend gets the horizontal-scale
|
|
14
|
+
* story free, and the shards may be heterogeneous.
|
|
15
|
+
*
|
|
16
|
+
* ## Routing
|
|
17
|
+
*
|
|
18
|
+
* With a {@link ShardPlan}, executions hash to a virtual bucket and buckets map to shards
|
|
19
|
+
* by an explicit table. Without one, routing is `hash(id) % shardCount` — identical to
|
|
20
|
+
* pre-0075 behaviour, and **not rebalanceable**.
|
|
21
|
+
*
|
|
22
|
+
* ## Rebalancing
|
|
23
|
+
*
|
|
24
|
+
* `plan.rebalanced(n)` marks every bucket whose owner changes as `migrating(from → to)`.
|
|
25
|
+
* That is a routing change only; **no data moves**. For a migrating bucket the store
|
|
26
|
+
* resolves per execution which side actually holds it (one bounded point-read, then
|
|
27
|
+
* cached), so:
|
|
28
|
+
*
|
|
29
|
+
* - executions already on the source keep being read and written there — nothing breaks;
|
|
30
|
+
* - executions that do not exist yet start on the destination.
|
|
31
|
+
*
|
|
32
|
+
* A bucket therefore **drains on its own** as executions complete and retention reclaims
|
|
33
|
+
* them: no copying, no downtime, no coordinator. When you want convergence sooner, or a
|
|
34
|
+
* source shard must be decommissioned, {@link migrateExecution} moves one explicitly. Once
|
|
35
|
+
* {@link isBucketDrained} reports true, `plan.settled(bucket)` retires the extra lookup.
|
|
36
|
+
*
|
|
37
|
+
* ## What this does NOT do
|
|
38
|
+
*
|
|
39
|
+
* - **It does not move a live execution safely.** `migrateExecution` copies then deletes;
|
|
40
|
+
* an append arriving mid-move lands on the source and is dropped by the delete. Move
|
|
41
|
+
* terminal executions, or ones you know are not dispatched. The lazy drain above exists
|
|
42
|
+
* precisely so you rarely need to.
|
|
43
|
+
* - **Fence state does not travel.** The SPI exposes `acquireFence` but no way to read or
|
|
44
|
+
* write a fence, so a migrated execution arrives unfenced on its destination and a zombie
|
|
45
|
+
* writer holding an old token could write there. Fence-sensitive deployments should
|
|
46
|
+
* migrate only executions that are terminal *and* undispatched.
|
|
47
|
+
*
|
|
48
|
+
* Invariants relied on throughout: an execution lives entirely on one shard at any moment,
|
|
49
|
+
* and a stream that exists has a head — which is what makes "which side holds it?" a single
|
|
50
|
+
* indexed point-read (the HEAD, not sequence 0: an inbox topic stream or a floor-seeded stream has
|
|
51
|
+
* no sequence 0).
|
|
52
|
+
*/
|
|
53
|
+
/**
|
|
54
|
+
* The optional-capability surface EVERY shard shares: an object carrying each optional
|
|
55
|
+
* `EventStore` method that all shards implement (bound to the first shard, so it is a real
|
|
56
|
+
* method, never a stub) and nothing else. Used as the `inner` of `installOptional`, whose
|
|
57
|
+
* rule is "install a method iff the inner store has it" — for a sharded store, "has it" means
|
|
58
|
+
* every shard has it. O(shards × optional methods), once, at construction.
|
|
59
|
+
*/
|
|
60
|
+
function commonOptional(shards) {
|
|
61
|
+
const surface = {};
|
|
62
|
+
const first = shards[0];
|
|
63
|
+
for (const method of types_1.OPTIONAL_EVENT_STORE_METHODS) {
|
|
64
|
+
if (shards.every((s) => typeof s[method] === "function")) {
|
|
65
|
+
surface[method] = first[method].bind(first);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
// ADR 0130 D7/D8 exception: `sealingCapability` is offered when ANY shard has it, because a shard
|
|
69
|
+
// lacking the method seals nothing and must be judged (commonSealing), not silently hide a sealing
|
|
70
|
+
// shard by making the whole store look like it does not seal.
|
|
71
|
+
if (shards.some((s) => typeof s.sealingCapability === "function")) {
|
|
72
|
+
surface.sealingCapability = () => undefined;
|
|
73
|
+
}
|
|
74
|
+
return surface;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* ADR 0129 — an incarnation counter is per shard, so the same number can be issued by two shards
|
|
78
|
+
* (a migrating bucket re-creates an id on another shard). The anchor this store hands out is
|
|
79
|
+
* therefore `s<shardIndex>/<inner anchor>`: it starts with a letter, so it can collide neither
|
|
80
|
+
* with a bare incarnation (digits) nor with the legacy `<timestamp>:<id>` marker (digits first),
|
|
81
|
+
* and an observation taken on one shard never equals the anchor of another.
|
|
82
|
+
*/
|
|
83
|
+
/**
|
|
84
|
+
* ADR 0130 residuals D7/D8: the shards of one store disagree about sealing (some seal and some do
|
|
85
|
+
* not, or they seal different paths or with different ciphers). Misconfiguration, raised live on
|
|
86
|
+
* every `sealingCapability()` call and never papered over, because one answer serves two questions
|
|
87
|
+
* that must not diverge: "what can this node open" (D7) and "does this store seal at all" (D8, a
|
|
88
|
+
* non-empty `covers`). Carries each shard's cipher and covers, never key material.
|
|
89
|
+
*/
|
|
90
|
+
class ShardSealingMismatchError extends Error {
|
|
91
|
+
code = "SHARD_SEALING_MISMATCH";
|
|
92
|
+
shards;
|
|
93
|
+
constructor(caps) {
|
|
94
|
+
const shards = caps.map((c, index) => ({
|
|
95
|
+
index,
|
|
96
|
+
cipher: c?.cipher,
|
|
97
|
+
covers: c === undefined ? undefined : [...c.covers],
|
|
98
|
+
}));
|
|
99
|
+
super(`shards disagree about sealing (${shards
|
|
100
|
+
.map((x) => `#${x.index}: ${x.cipher === undefined ? "none" : `${x.cipher} [${x.covers?.join(", ")}]`}`)
|
|
101
|
+
.join("; ")}); every shard must seal the same paths with the same cipher, or none may seal`);
|
|
102
|
+
this.name = "ShardSealingMismatchError";
|
|
103
|
+
this.shards = shards;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
exports.ShardSealingMismatchError = ShardSealingMismatchError;
|
|
107
|
+
/**
|
|
108
|
+
* The sealing capability of a fleet of shards, FAIL CLOSED: no shard seals -> undefined; every shard
|
|
109
|
+
* reports the same cipher and the same covers -> that capability (`opens` needs every shard to
|
|
110
|
+
* agree); anything else throws {@link ShardSealingMismatchError}.
|
|
111
|
+
*/
|
|
112
|
+
function commonSealing(caps) {
|
|
113
|
+
const first = caps[0];
|
|
114
|
+
if (caps.every((c) => c === undefined))
|
|
115
|
+
return undefined;
|
|
116
|
+
if (first === undefined)
|
|
117
|
+
throw new ShardSealingMismatchError(caps);
|
|
118
|
+
const covers = new Set(first.covers);
|
|
119
|
+
const all = [];
|
|
120
|
+
for (const c of caps) {
|
|
121
|
+
if (c === undefined || c.cipher !== first.cipher || !sameSet(covers, c.covers)) {
|
|
122
|
+
throw new ShardSealingMismatchError(caps);
|
|
123
|
+
}
|
|
124
|
+
all.push(c);
|
|
125
|
+
}
|
|
126
|
+
return {
|
|
127
|
+
cipher: first.cipher,
|
|
128
|
+
covers: first.covers,
|
|
129
|
+
opens: (marker) => all.every((c) => c.opens(marker)),
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
/** `covers` is a set of paths: order and duplicates carry no meaning, so compare as sets. */
|
|
133
|
+
function sameSet(a, b) {
|
|
134
|
+
const bs = new Set(b);
|
|
135
|
+
return bs.size === a.size && [...bs].every((e) => a.has(e));
|
|
136
|
+
}
|
|
137
|
+
const shardAnchor = (shard, inner) => `s${shard}/${inner}`;
|
|
138
|
+
/** The inner anchor if `anchor` was issued by `shard`; otherwise the string unchanged, which no
|
|
139
|
+
* inner store can have issued (it starts with `s`), so the shard answers `moved`. */
|
|
140
|
+
const unshardAnchor = (anchor, shard) => {
|
|
141
|
+
const prefix = `s${shard}/`;
|
|
142
|
+
return anchor.startsWith(prefix) ? anchor.slice(prefix.length) : anchor;
|
|
143
|
+
};
|
|
144
|
+
class ShardedEventStore {
|
|
145
|
+
head;
|
|
146
|
+
readRange;
|
|
147
|
+
acquireFence;
|
|
148
|
+
acquireFenceIf;
|
|
149
|
+
appendIfUnchanged;
|
|
150
|
+
storeNow;
|
|
151
|
+
sealingCapability;
|
|
152
|
+
readAtRest;
|
|
153
|
+
deleteExecution;
|
|
154
|
+
deleteThrough;
|
|
155
|
+
truncatePrefix;
|
|
156
|
+
truncatePrefixKeeping;
|
|
157
|
+
listExecutions;
|
|
158
|
+
listExecutionsPage;
|
|
159
|
+
shards;
|
|
160
|
+
shardOf;
|
|
161
|
+
plan;
|
|
162
|
+
cacheLimit;
|
|
163
|
+
/** executionId → shard, only for executions in migrating buckets. Insertion-ordered LRU. */
|
|
164
|
+
placementCache = new Map();
|
|
165
|
+
constructor(shards, options = {}) {
|
|
166
|
+
if (shards.length === 0)
|
|
167
|
+
throw new Error("ShardedEventStore requires at least one shard");
|
|
168
|
+
this.shards = shards;
|
|
169
|
+
this.shardOf = options.shardOf ?? SubscriberRegistry_1.bucketOf;
|
|
170
|
+
this.cacheLimit = Math.max(1, options.placementCacheSize ?? 10_000);
|
|
171
|
+
if (options.plan !== undefined)
|
|
172
|
+
this.setPlan(options.plan);
|
|
173
|
+
const plan = {
|
|
174
|
+
head: async (id) => (await this.route(id)).head(id),
|
|
175
|
+
readRange: async (id, from, to) => (await this.route(id)).readRange(id, from, to),
|
|
176
|
+
acquireFence: async (id) => (await this.route(id)).acquireFence(id),
|
|
177
|
+
acquireFenceIf: async (id, expect) => (await this.route(id)).acquireFenceIf(id, expect),
|
|
178
|
+
appendIfUnchanged: async (id, event, expect) => {
|
|
179
|
+
const i = await this.routeIndex(id);
|
|
180
|
+
const shard = this.shards[i];
|
|
181
|
+
const result = await shard.appendIfUnchanged(id, event, expect === undefined ? undefined : { ...expect, anchor: unshardAnchor(expect.anchor, i) });
|
|
182
|
+
return "anchor" in result && typeof result.anchor === "string"
|
|
183
|
+
? { ...result, anchor: shardAnchor(i, result.anchor) }
|
|
184
|
+
: result;
|
|
185
|
+
},
|
|
186
|
+
// One clock for the fleet: every shard has it (commonOptional), read from the first shard.
|
|
187
|
+
storeNow: "forward",
|
|
188
|
+
// ADR 0130 residuals D7/D8: a stream may sit on any shard, so shards must agree. Installed when
|
|
189
|
+
// ANY shard has the method (commonOptional); a shard lacking it counts as sealing nothing
|
|
190
|
+
// (undefined). Fails closed (ShardSealingMismatchError) on any disagreement, so "can open" and
|
|
191
|
+
// "does seal" cannot diverge. When no shard has it the method is omitted.
|
|
192
|
+
sealingCapability: () => commonSealing(this.shards.map((s) => s.sealingCapability?.())),
|
|
193
|
+
// ADR 0130 D9: the at-rest view routes each call to the stream's shard, like `read` (installed
|
|
194
|
+
// only when EVERY shard offers it, via commonOptional).
|
|
195
|
+
readAtRest: () => {
|
|
196
|
+
const readers = this.shards.map((s) => s.readAtRest());
|
|
197
|
+
const hasRange = readers.every((r) => r.readRange !== undefined);
|
|
198
|
+
const hasHead = readers.every((r) => r.head !== undefined);
|
|
199
|
+
const via = (id, f) => this.routeIndex(id).then((i) => f(readers[i]));
|
|
200
|
+
return {
|
|
201
|
+
read: (id) => via(id, (r) => r.read(id)),
|
|
202
|
+
readFrom: (id, from) => via(id, (r) => r.readFrom(id, from)),
|
|
203
|
+
...(hasRange && {
|
|
204
|
+
readRange: (id, a, b) => via(id, (r) => r.readRange(id, a, b)),
|
|
205
|
+
}),
|
|
206
|
+
...(hasHead && { head: (id) => via(id, (r) => r.head(id)) }),
|
|
207
|
+
};
|
|
208
|
+
},
|
|
209
|
+
deleteExecution: async (id) => {
|
|
210
|
+
await (await this.route(id)).deleteExecution(id);
|
|
211
|
+
this.placementCache.delete(id);
|
|
212
|
+
},
|
|
213
|
+
// Bounded by `through`, so a stream a caller drains stays placed while anything above it remains.
|
|
214
|
+
deleteThrough: async (id, through) => {
|
|
215
|
+
const shard = await this.route(id);
|
|
216
|
+
await shard.deleteThrough(id, through);
|
|
217
|
+
if ((await (0, types_1.headOf)(shard, id)) === null)
|
|
218
|
+
this.placementCache.delete(id);
|
|
219
|
+
},
|
|
220
|
+
truncatePrefix: async (id, through) => (await this.route(id)).truncatePrefix(id, through),
|
|
221
|
+
truncatePrefixKeeping: async (id, through, keep) => (await this.route(id)).truncatePrefixKeeping(id, through, keep),
|
|
222
|
+
listExecutions: async () => {
|
|
223
|
+
const perShard = await Promise.all(this.shards.map((s) => s.listExecutions()));
|
|
224
|
+
// Dedupe: a crash between migrateExecution's copy and delete can leave an execution
|
|
225
|
+
// on both sides, and enumeration must still report it once.
|
|
226
|
+
return [...new Set(perShard.flat())];
|
|
227
|
+
},
|
|
228
|
+
// Paged enumeration needs EVERY shard to be pageable; falling back to listExecutions
|
|
229
|
+
// on some would defeat the bounded-memory guarantee the page API exists to provide.
|
|
230
|
+
listExecutionsPage: (opts) => this.pagedScatterGather(opts),
|
|
231
|
+
};
|
|
232
|
+
// `installOptional` installs a method iff its `inner` has it; here "inner" is the
|
|
233
|
+
// capability surface every shard shares.
|
|
234
|
+
(0, installOptional_1.installOptional)(this, commonOptional(shards), plan);
|
|
235
|
+
// The structural encryption marker (`keys`, see `installOptional`) must survive sharding,
|
|
236
|
+
// or `Sharded(Encrypting(pg)…)` — the ADR 0074 composition — would look like a plain store
|
|
237
|
+
// to LogLifecycle's compaction/archive guard and to `loom doctor`. Carried up when every
|
|
238
|
+
// shard shares ONE KeyStore; shards that disagree (or only some encrypting) get a marker
|
|
239
|
+
// no snapshot store can match, so the guard fails closed rather than guessing.
|
|
240
|
+
const markers = shards.map((s) => s.keys);
|
|
241
|
+
if (markers.some((k) => typeof k === "object" && k !== null)) {
|
|
242
|
+
const first = markers[0];
|
|
243
|
+
this.keys = markers.every((k) => k === first)
|
|
244
|
+
? first
|
|
245
|
+
: Object.freeze({ mixed: true });
|
|
246
|
+
}
|
|
247
|
+
// ADR 0138 H3: the tenant marker survives sharding when every shard is scoped to ONE tenant.
|
|
248
|
+
// Shards that disagree (or only some scoped) get a scope no archive can match, so the guard
|
|
249
|
+
// refuses rather than guessing.
|
|
250
|
+
const scopes = shards.map((s) => s.tenantScope);
|
|
251
|
+
if (scopes.some((t) => typeof t === "string")) {
|
|
252
|
+
const first = scopes[0];
|
|
253
|
+
this.tenantScope =
|
|
254
|
+
typeof first === "string" && scopes.every((t) => t === first)
|
|
255
|
+
? first
|
|
256
|
+
: "\u0000mixed-tenant-shards";
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Install a new routing plan — how a rebalance is applied, and how a drained bucket is
|
|
261
|
+
* settled. **Every node sharing the log must converge on the same plan**; different plans
|
|
262
|
+
* on different nodes split the log.
|
|
263
|
+
*/
|
|
264
|
+
setPlan(plan) {
|
|
265
|
+
if (plan.shardCount !== this.shards.length) {
|
|
266
|
+
throw new Error(`plan expects ${plan.shardCount} shards but the store has ${this.shards.length}`);
|
|
267
|
+
}
|
|
268
|
+
this.plan = plan;
|
|
269
|
+
// Cached placements were resolved against the previous plan; a bucket that settled or
|
|
270
|
+
// started migrating invalidates them.
|
|
271
|
+
this.placementCache.clear();
|
|
272
|
+
}
|
|
273
|
+
/** The active plan, if routing is plan-driven. */
|
|
274
|
+
get shardPlan() {
|
|
275
|
+
return this.plan;
|
|
276
|
+
}
|
|
277
|
+
/** Resolve the store owning `executionId` right now. */
|
|
278
|
+
async route(executionId) {
|
|
279
|
+
return this.shards[await this.routeIndex(executionId)];
|
|
280
|
+
}
|
|
281
|
+
async routeIndex(executionId) {
|
|
282
|
+
if (this.plan === undefined) {
|
|
283
|
+
return this.shardOf(executionId, this.shards.length);
|
|
284
|
+
}
|
|
285
|
+
const placement = this.plan.placementFor(executionId);
|
|
286
|
+
if (placement.kind === "settled")
|
|
287
|
+
return placement.shard;
|
|
288
|
+
const cached = this.placementCache.get(executionId);
|
|
289
|
+
if (cached !== undefined) {
|
|
290
|
+
// Refresh recency (Map preserves insertion order, so re-insert moves it to the end).
|
|
291
|
+
this.placementCache.delete(executionId);
|
|
292
|
+
this.placementCache.set(executionId, cached);
|
|
293
|
+
return cached;
|
|
294
|
+
}
|
|
295
|
+
// Which side actually holds it? Sequence 0 is the bootstrap anchor: present for as long
|
|
296
|
+
// as the execution exists (truncatePrefix preserves it, deleteExecution removes it), so
|
|
297
|
+
// one indexed point-read answers the question. The destination is checked FIRST, so a
|
|
298
|
+
// migrated execution stops consulting the source immediately — and so a crash between
|
|
299
|
+
// copy and delete resolves to the destination, which is the surviving copy.
|
|
300
|
+
// The probe is the stream HEAD (one indexed MAX(sequence)), not sequence 0: a stream that never
|
|
301
|
+
// had a sequence 0 (an inbox topic stream starts at 2^40; a legacy inbox stream seeded at a fork
|
|
302
|
+
// floor starts above 0) is on a shard all the same, and a probe of 0 would route it to the
|
|
303
|
+
// destination while its data (and the owner's cursor) sit on the source.
|
|
304
|
+
const onTo = await (0, types_1.headOf)(this.shards[placement.to], executionId);
|
|
305
|
+
if (onTo !== null)
|
|
306
|
+
return this.remember(executionId, placement.to);
|
|
307
|
+
const onFrom = await (0, types_1.headOf)(this.shards[placement.from], executionId);
|
|
308
|
+
if (onFrom !== null)
|
|
309
|
+
return this.remember(executionId, placement.from);
|
|
310
|
+
// Unknown to both ⇒ it does not exist yet, so it belongs to the destination. Caching
|
|
311
|
+
// that is what makes a new execution's subsequent appends single-lookup.
|
|
312
|
+
return this.remember(executionId, placement.to);
|
|
313
|
+
}
|
|
314
|
+
remember(executionId, shard) {
|
|
315
|
+
this.placementCache.set(executionId, shard);
|
|
316
|
+
if (this.placementCache.size > this.cacheLimit) {
|
|
317
|
+
const oldest = this.placementCache.keys().next();
|
|
318
|
+
if (!oldest.done)
|
|
319
|
+
this.placementCache.delete(oldest.value);
|
|
320
|
+
}
|
|
321
|
+
return shard;
|
|
322
|
+
}
|
|
323
|
+
/**
|
|
324
|
+
* Move one execution from a migrating bucket's source to its destination: copy every
|
|
325
|
+
* event, then delete the source copy.
|
|
326
|
+
*
|
|
327
|
+
* **The caller must ensure the execution is not being written concurrently** — an append
|
|
328
|
+
* arriving mid-move lands on the source and is then deleted. Terminal executions are the
|
|
329
|
+
* safe case, and the lazy drain described on the class means you usually do not need this
|
|
330
|
+
* at all; reach for it when a source shard must be decommissioned.
|
|
331
|
+
*
|
|
332
|
+
* Fence state does not move (the SPI has no way to read it), so the execution arrives
|
|
333
|
+
* unfenced on the destination.
|
|
334
|
+
*/
|
|
335
|
+
async migrateExecution(executionId) {
|
|
336
|
+
if (this.plan === undefined)
|
|
337
|
+
return { executionId, outcome: "not-migrating", events: 0 };
|
|
338
|
+
const placement = this.plan.placementFor(executionId);
|
|
339
|
+
if (placement.kind === "settled")
|
|
340
|
+
return { executionId, outcome: "not-migrating", events: 0 };
|
|
341
|
+
const current = await this.routeIndex(executionId);
|
|
342
|
+
if (current === placement.to)
|
|
343
|
+
return { executionId, outcome: "already-there", events: 0 };
|
|
344
|
+
const source = this.shards[placement.from];
|
|
345
|
+
const target = this.shards[placement.to];
|
|
346
|
+
const events = await source.read(executionId);
|
|
347
|
+
if (events.length > 0)
|
|
348
|
+
await (0, types_1.appendBatchOf)(target, events);
|
|
349
|
+
// Copy BEFORE delete. A crash in between leaves the execution on both sides, which
|
|
350
|
+
// `routeIndex` resolves to the destination (checked first) and a re-run cleans up. The
|
|
351
|
+
// reverse order would lose the log outright.
|
|
352
|
+
await source.deleteExecution?.(executionId);
|
|
353
|
+
this.placementCache.delete(executionId);
|
|
354
|
+
this.remember(executionId, placement.to);
|
|
355
|
+
return { executionId, outcome: "moved", events: events.length };
|
|
356
|
+
}
|
|
357
|
+
/**
|
|
358
|
+
* Whether a migrating bucket's source side still holds anything — i.e. whether
|
|
359
|
+
* `plan.settled(bucket)` is safe yet. `true` for a settled bucket (nothing to drain).
|
|
360
|
+
*
|
|
361
|
+
* Requires enumerable shards.
|
|
362
|
+
*/
|
|
363
|
+
async isBucketDrained(bucket) {
|
|
364
|
+
const plan = this.plan;
|
|
365
|
+
if (plan === undefined)
|
|
366
|
+
return true;
|
|
367
|
+
const placement = plan.placementAt(bucket);
|
|
368
|
+
if (placement.kind === "settled")
|
|
369
|
+
return true;
|
|
370
|
+
const source = this.shards[placement.from];
|
|
371
|
+
if (source.listExecutions === undefined) {
|
|
372
|
+
throw new Error("isBucketDrained needs shards that support listExecutions()");
|
|
373
|
+
}
|
|
374
|
+
const ids = await source.listExecutions();
|
|
375
|
+
return !ids.some((id) => plan.bucketFor(id) === bucket);
|
|
376
|
+
}
|
|
377
|
+
async append(envelope) {
|
|
378
|
+
return (await this.route(envelope.executionId)).append(envelope);
|
|
379
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
* Split a batch by shard and append each part. Shards are independent logs, so the parts
|
|
382
|
+
* run concurrently.
|
|
383
|
+
*
|
|
384
|
+
* Atomicity is per shard, which is exactly what the SPI requires: the contract is atomic
|
|
385
|
+
* PER EXECUTION, and an execution never spans shards. Whole-batch atomicity across
|
|
386
|
+
* shards would need a distributed transaction, costs far more than batching saves, and
|
|
387
|
+
* nothing needs it — a batch only groups INDEPENDENT executions, so nothing in it
|
|
388
|
+
* depends on anything else in it. `Promise.all` rejects if any shard does, so a caller
|
|
389
|
+
* still never reads a partial write as success.
|
|
390
|
+
*/
|
|
391
|
+
async appendBatch(envelopes) {
|
|
392
|
+
if (envelopes.length === 0)
|
|
393
|
+
return;
|
|
394
|
+
const slots = await Promise.all(envelopes.map((e) => this.routeIndex(e.executionId)));
|
|
395
|
+
const bySlot = new Map();
|
|
396
|
+
for (const [i, envelope] of envelopes.entries()) {
|
|
397
|
+
const slot = slots[i];
|
|
398
|
+
const list = bySlot.get(slot);
|
|
399
|
+
if (list === undefined)
|
|
400
|
+
bySlot.set(slot, [envelope]);
|
|
401
|
+
else
|
|
402
|
+
list.push(envelope);
|
|
403
|
+
}
|
|
404
|
+
await Promise.all([...bySlot].map(([slot, part]) => (0, types_1.appendBatchOf)(this.shards[slot], part)));
|
|
405
|
+
}
|
|
406
|
+
async read(executionId) {
|
|
407
|
+
return (await this.route(executionId)).read(executionId);
|
|
408
|
+
}
|
|
409
|
+
async readFrom(executionId, fromSequence) {
|
|
410
|
+
return (await this.route(executionId)).readFrom(executionId, fromSequence);
|
|
411
|
+
}
|
|
412
|
+
async subscribe(executionId, handler) {
|
|
413
|
+
return (await this.route(executionId)).subscribe(executionId, handler);
|
|
414
|
+
}
|
|
415
|
+
/**
|
|
416
|
+
* Fork. Fast when parent and child resolve to the same shard; otherwise the prefix has to
|
|
417
|
+
* cross a store boundary, so we use the SPI's documented per-event fallback.
|
|
418
|
+
*/
|
|
419
|
+
async copyPrefix(fromExecutionId, toExecutionId, throughSequence, metadata) {
|
|
420
|
+
const source = await this.route(fromExecutionId);
|
|
421
|
+
const target = await this.route(toExecutionId);
|
|
422
|
+
if (source === target && source.copyPrefix !== undefined) {
|
|
423
|
+
return source.copyPrefix(fromExecutionId, toExecutionId, throughSequence, metadata);
|
|
424
|
+
}
|
|
425
|
+
const events = await source.read(fromExecutionId);
|
|
426
|
+
const copied = events
|
|
427
|
+
.filter((e) => e.sequence <= throughSequence)
|
|
428
|
+
.map((e) => ({
|
|
429
|
+
...e,
|
|
430
|
+
id: `${toExecutionId}-${e.sequence}`,
|
|
431
|
+
executionId: toExecutionId,
|
|
432
|
+
metadata: { ...e.metadata, ...metadata },
|
|
433
|
+
}));
|
|
434
|
+
await (0, types_1.appendBatchOf)(target, copied);
|
|
435
|
+
}
|
|
436
|
+
/**
|
|
437
|
+
* Keyset pagination across shards. Each shard is asked for its next page beyond the
|
|
438
|
+
* cursor; the results are merged and truncated to `limit`, so the caller sees one
|
|
439
|
+
* globally ordered stream and the cursor stays a single id.
|
|
440
|
+
*
|
|
441
|
+
* Correct ONLY because every shard pages in the SPI's one order (code point, SPI 2.0.0)
|
|
442
|
+
* and accepts any id as a cursor: the merged page's last id is handed back to EVERY shard,
|
|
443
|
+
* including shards that never returned it. A shard paging in another order (a Postgres
|
|
444
|
+
* database collation, before L10) skips the ids that sort after the cursor in one order
|
|
445
|
+
* and before it in the other — silently, since every page still looks well-formed.
|
|
446
|
+
*
|
|
447
|
+
* Deduplicated for the same reason as `listExecutions`: mid-migration an execution can
|
|
448
|
+
* briefly exist on two shards.
|
|
449
|
+
*/
|
|
450
|
+
async pagedScatterGather(opts) {
|
|
451
|
+
const limit = opts?.limit ?? 1000;
|
|
452
|
+
const pages = await Promise.all(this.shards.map((s) => s.listExecutionsPage({ ...(opts?.cursor !== undefined && { cursor: opts.cursor }), limit })));
|
|
453
|
+
const union = new Set(pages.flatMap((p) => p.executionIds));
|
|
454
|
+
const merged = [...union].sort(executionIdOrder_1.compareExecutionIds).slice(0, limit);
|
|
455
|
+
const last = merged[merged.length - 1];
|
|
456
|
+
// More remains if any shard reported a further page, or if the merge itself truncated.
|
|
457
|
+
const truncated = pages.some((p) => p.nextCursor !== undefined) || union.size > merged.length;
|
|
458
|
+
return {
|
|
459
|
+
executionIds: merged,
|
|
460
|
+
...(truncated && last !== undefined && { nextCursor: last }),
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
async close() {
|
|
464
|
+
await Promise.all(this.shards.map((s) => s.close()));
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
exports.ShardedEventStore = ShardedEventStore;
|
|
468
|
+
//# sourceMappingURL=ShardedEventStore.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ShardedEventStore.js","sourceRoot":"","sources":["../src/ShardedEventStore.ts"],"names":[],"mappings":"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"}
|