@photon-grove/evt 0.0.0-stage → 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/LICENSE +176 -0
- package/README.md +122 -2
- package/dist/aggregate.d.ts +83 -0
- package/dist/aggregate.d.ts.map +1 -0
- package/dist/aggregate.js +92 -0
- package/dist/aggregate.js.map +1 -0
- package/dist/base64.d.ts +8 -0
- package/dist/base64.d.ts.map +1 -0
- package/dist/base64.js +67 -0
- package/dist/base64.js.map +1 -0
- package/dist/codec.d.ts +56 -0
- package/dist/codec.d.ts.map +1 -0
- package/dist/codec.js +90 -0
- package/dist/codec.js.map +1 -0
- package/dist/commit-id.d.ts +3 -0
- package/dist/commit-id.d.ts.map +1 -0
- package/dist/commit-id.js +43 -0
- package/dist/commit-id.js.map +1 -0
- package/dist/dynamodb/head.d.ts +34 -0
- package/dist/dynamodb/head.d.ts.map +1 -0
- package/dist/dynamodb/head.js +62 -0
- package/dist/dynamodb/head.js.map +1 -0
- package/dist/dynamodb/index.d.ts +2 -0
- package/dist/dynamodb/index.d.ts.map +1 -0
- package/dist/dynamodb/index.js +4 -0
- package/dist/dynamodb/index.js.map +1 -0
- package/dist/dynamodb/item.d.ts +68 -0
- package/dist/dynamodb/item.d.ts.map +1 -0
- package/dist/dynamodb/item.js +263 -0
- package/dist/dynamodb/item.js.map +1 -0
- package/dist/envelope.d.ts +15 -0
- package/dist/envelope.d.ts.map +1 -0
- package/dist/envelope.js +59 -0
- package/dist/envelope.js.map +1 -0
- package/dist/errors.d.ts +111 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +139 -0
- package/dist/errors.js.map +1 -0
- package/dist/event.d.ts +47 -0
- package/dist/event.d.ts.map +1 -0
- package/dist/event.js +64 -0
- package/dist/event.js.map +1 -0
- package/dist/fields.d.ts +31 -0
- package/dist/fields.d.ts.map +1 -0
- package/dist/fields.js +105 -0
- package/dist/fields.js.map +1 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +16 -0
- package/dist/index.js.map +1 -0
- package/dist/json.d.ts +47 -0
- package/dist/json.d.ts.map +1 -0
- package/dist/json.js +458 -0
- package/dist/json.js.map +1 -0
- package/dist/memory/index.d.ts +2 -0
- package/dist/memory/index.d.ts.map +1 -0
- package/dist/memory/index.js +3 -0
- package/dist/memory/index.js.map +1 -0
- package/dist/memory/repository.d.ts +34 -0
- package/dist/memory/repository.d.ts.map +1 -0
- package/dist/memory/repository.js +219 -0
- package/dist/memory/repository.js.map +1 -0
- package/dist/metadata.d.ts +47 -0
- package/dist/metadata.d.ts.map +1 -0
- package/dist/metadata.js +75 -0
- package/dist/metadata.js.map +1 -0
- package/dist/repository.d.ts +102 -0
- package/dist/repository.d.ts.map +1 -0
- package/dist/repository.js +131 -0
- package/dist/repository.js.map +1 -0
- package/dist/retry.d.ts +25 -0
- package/dist/retry.d.ts.map +1 -0
- package/dist/retry.js +50 -0
- package/dist/retry.js.map +1 -0
- package/dist/snapshots.d.ts +21 -0
- package/dist/snapshots.d.ts.map +1 -0
- package/dist/snapshots.js +35 -0
- package/dist/snapshots.js.map +1 -0
- package/dist/store.d.ts +103 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/store.js +246 -0
- package/dist/store.js.map +1 -0
- package/dist/testing/equal.d.ts +22 -0
- package/dist/testing/equal.d.ts.map +1 -0
- package/dist/testing/equal.js +121 -0
- package/dist/testing/equal.js.map +1 -0
- package/dist/testing/index.d.ts +3 -0
- package/dist/testing/index.d.ts.map +1 -0
- package/dist/testing/index.js +5 -0
- package/dist/testing/index.js.map +1 -0
- package/dist/testing/scenario.d.ts +136 -0
- package/dist/testing/scenario.d.ts.map +1 -0
- package/dist/testing/scenario.js +307 -0
- package/dist/testing/scenario.js.map +1 -0
- package/dist/utf8.d.ts +17 -0
- package/dist/utf8.d.ts.map +1 -0
- package/dist/utf8.js +82 -0
- package/dist/utf8.js.map +1 -0
- package/package.json +56 -4
- package/src/aggregate.ts +196 -0
- package/src/base64.ts +80 -0
- package/src/codec.ts +139 -0
- package/src/commit-id.ts +50 -0
- package/src/dynamodb/head.ts +93 -0
- package/src/dynamodb/index.ts +21 -0
- package/src/dynamodb/item.ts +332 -0
- package/src/envelope.ts +67 -0
- package/src/errors.ts +214 -0
- package/src/event.ts +117 -0
- package/src/fields.ts +134 -0
- package/src/globals.d.ts +39 -0
- package/src/index.ts +75 -0
- package/src/json.ts +546 -0
- package/src/memory/index.ts +3 -0
- package/src/memory/repository.ts +313 -0
- package/src/metadata.ts +120 -0
- package/src/repository.ts +247 -0
- package/src/retry.ts +74 -0
- package/src/snapshots.ts +42 -0
- package/src/store.ts +377 -0
- package/src/testing/equal.ts +146 -0
- package/src/testing/index.ts +14 -0
- package/src/testing/scenario.ts +406 -0
- package/src/utf8.ts +92 -0
package/dist/retry.js
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// Retry policy: how many times a command runs and how long to wait between attempts, with the
|
|
2
|
+
// defaults the compat fixtures pin (outcomes.json, retry).
|
|
3
|
+
import { EvtError } from './errors.js';
|
|
4
|
+
/** Five attempts, waiting from 25 ms up to at most 1 s, as Go and Rust do. */
|
|
5
|
+
export const DEFAULT_RETRY_POLICY = Object.freeze({
|
|
6
|
+
maxAttempts: 5,
|
|
7
|
+
baseDelayMs: 25,
|
|
8
|
+
maxDelayMs: 1000,
|
|
9
|
+
});
|
|
10
|
+
/** Refuses a policy with fewer than one attempt or a delay that isn't a whole, finite number. */
|
|
11
|
+
export function checkRetryPolicy(policy) {
|
|
12
|
+
const valid = Number.isSafeInteger(policy.maxAttempts) &&
|
|
13
|
+
policy.maxAttempts >= 1 &&
|
|
14
|
+
Number.isSafeInteger(policy.baseDelayMs) &&
|
|
15
|
+
policy.baseDelayMs >= 0 &&
|
|
16
|
+
Number.isSafeInteger(policy.maxDelayMs) &&
|
|
17
|
+
policy.maxDelayMs >= 0;
|
|
18
|
+
if (!valid) {
|
|
19
|
+
throw new EvtError('invalidArgument', 'a retry policy needs at least one attempt and whole, non-negative delays: ' +
|
|
20
|
+
`${policy.maxAttempts}, ${policy.baseDelayMs}, ${policy.maxDelayMs}`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* The jittered delay in milliseconds before retry `retry` (0 for the first retry). `random`
|
|
25
|
+
* returns a number in [0, 1), as `Math.random` does.
|
|
26
|
+
*/
|
|
27
|
+
export function retryDelay(policy, retry, random = Math.random) {
|
|
28
|
+
const ceiling = Math.min(policy.baseDelayMs * 2 ** retry, policy.maxDelayMs);
|
|
29
|
+
const floor = Math.floor(ceiling / 2);
|
|
30
|
+
return floor + Math.floor(random() * (ceiling - floor + 1));
|
|
31
|
+
}
|
|
32
|
+
/** Waits `ms` milliseconds, or rejects with the signal's reason when it aborts first. */
|
|
33
|
+
export function sleep(ms, signal) {
|
|
34
|
+
return new Promise((resolve, reject) => {
|
|
35
|
+
if (signal?.aborted) {
|
|
36
|
+
reject(signal.reason);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
const timer = setTimeout(() => {
|
|
40
|
+
signal?.removeEventListener('abort', aborted);
|
|
41
|
+
resolve();
|
|
42
|
+
}, ms);
|
|
43
|
+
const aborted = () => {
|
|
44
|
+
clearTimeout(timer);
|
|
45
|
+
reject(signal?.reason);
|
|
46
|
+
};
|
|
47
|
+
signal?.addEventListener('abort', aborted, { once: true });
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=retry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"retry.js","sourceRoot":"","sources":["../src/retry.ts"],"names":[],"mappings":"AAAA,8FAA8F;AAC9F,2DAA2D;AAE3D,OAAO,EAAC,QAAQ,EAAC,MAAM,aAAa,CAAA;AAgBpC,8EAA8E;AAC9E,MAAM,CAAC,MAAM,oBAAoB,GAAgB,MAAM,CAAC,MAAM,CAAC;IAC7D,WAAW,EAAE,CAAC;IACd,WAAW,EAAE,EAAE;IACf,UAAU,EAAE,IAAI;CACjB,CAAC,CAAA;AAEF,iGAAiG;AACjG,MAAM,UAAU,gBAAgB,CAAC,MAAmB;IAClD,MAAM,KAAK,GACT,MAAM,CAAC,aAAa,CAAC,MAAM,CAAC,WAAW,CAAC;QACxC,MAAM,CAAC,WAAW,IAAI,CAAC;QACvB,MAAM,CAAC,aAAa,CAAC,MAAM,CAAC,WAAW,CAAC;QACxC,MAAM,CAAC,WAAW,IAAI,CAAC;QACvB,MAAM,CAAC,aAAa,CAAC,MAAM,CAAC,UAAU,CAAC;QACvC,MAAM,CAAC,UAAU,IAAI,CAAC,CAAA;IACxB,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,MAAM,IAAI,QAAQ,CAChB,iBAAiB,EACjB,4EAA4E;YAC1E,GAAG,MAAM,CAAC,WAAW,KAAK,MAAM,CAAC,WAAW,KAAK,MAAM,CAAC,UAAU,EAAE,CACvE,CAAA;IACH,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,UAAU,CAAC,MAAmB,EAAE,KAAa,EAAE,MAAM,GAAG,IAAI,CAAC,MAAM;IACjF,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,WAAW,GAAG,CAAC,IAAI,KAAK,EAAE,MAAM,CAAC,UAAU,CAAC,CAAA;IAC5E,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,GAAG,CAAC,CAAC,CAAA;IAErC,OAAO,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,GAAG,CAAC,OAAO,GAAG,KAAK,GAAG,CAAC,CAAC,CAAC,CAAA;AAC7D,CAAC;AAED,yFAAyF;AACzF,MAAM,UAAU,KAAK,CAAC,EAAU,EAAE,MAAoB;IACpD,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,IAAI,MAAM,EAAE,OAAO,EAAE,CAAC;YACpB,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;YACrB,OAAM;QACR,CAAC;QAED,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE;YAC5B,MAAM,EAAE,mBAAmB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAA;YAC7C,OAAO,EAAE,CAAA;QACX,CAAC,EAAE,EAAE,CAAC,CAAA;QACN,MAAM,OAAO,GAAG,GAAG,EAAE;YACnB,YAAY,CAAC,KAAK,CAAC,CAAA;YACnB,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;QACxB,CAAC,CAAA;QACD,MAAM,EAAE,gBAAgB,CAAC,OAAO,EAAE,OAAO,EAAE,EAAC,IAAI,EAAE,IAAI,EAAC,CAAC,CAAA;IAC1D,CAAC,CAAC,CAAA;AACJ,CAAC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { SerializedSnapshot } from './event.js';
|
|
2
|
+
/**
|
|
3
|
+
* The most bytes one action of a commit may carry: 95% of DynamoDB's 400 KB item limit. A commit
|
|
4
|
+
* with a larger item fails on every backend.
|
|
5
|
+
*/
|
|
6
|
+
export declare const MAX_COMMIT_ITEM_BYTES = 380000;
|
|
7
|
+
/**
|
|
8
|
+
* Whether a commit takes a snapshot, given the stream's current sequence, the events in the
|
|
9
|
+
* commit and the snapshot interval: 0 for no snapshot, otherwise the events it folds in, which is
|
|
10
|
+
* always the whole commit. A commit snapshots when it reaches or crosses the next multiple of the
|
|
11
|
+
* interval, and an interval of 0 (or less, as in Go) disables snapshots.
|
|
12
|
+
*/
|
|
13
|
+
export declare function snapshotEventsDue(current: bigint, events: number, interval: number): number;
|
|
14
|
+
/**
|
|
15
|
+
* The bytes the commit budget counts for a snapshot's item, with a `ttl` at its widest whether or
|
|
16
|
+
* not the entity type is retained. A store leaves out a snapshot over
|
|
17
|
+
* {@link MAX_COMMIT_ITEM_BYTES}, so an aggregate whose state outgrows the budget doesn't make
|
|
18
|
+
* every commit that reaches the interval fail.
|
|
19
|
+
*/
|
|
20
|
+
export declare function snapshotCommitSize(snapshot: SerializedSnapshot): number;
|
|
21
|
+
//# sourceMappingURL=snapshots.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"snapshots.d.ts","sourceRoot":"","sources":["../src/snapshots.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAC,kBAAkB,EAAC,MAAM,YAAY,CAAA;AAGlD;;;GAGG;AACH,eAAO,MAAM,qBAAqB,SAAU,CAAA;AAK5C;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAU3F;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,kBAAkB,GAAG,MAAM,CAEvE"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// The snapshot policy and the per-item budget a snapshot must fit, the same on every backend.
|
|
2
|
+
import { snapshotItem, itemSize } from './dynamodb/item.js';
|
|
3
|
+
import { safeIntegerArgument } from './fields.js';
|
|
4
|
+
/**
|
|
5
|
+
* The most bytes one action of a commit may carry: 95% of DynamoDB's 400 KB item limit. A commit
|
|
6
|
+
* with a larger item fails on every backend.
|
|
7
|
+
*/
|
|
8
|
+
export const MAX_COMMIT_ITEM_BYTES = 380_000;
|
|
9
|
+
// A ttl attribute at its widest: the name and the 19 digits of the largest int64.
|
|
10
|
+
const WIDEST_TTL = { N: (2n ** 63n - 1n).toString() };
|
|
11
|
+
/**
|
|
12
|
+
* Whether a commit takes a snapshot, given the stream's current sequence, the events in the
|
|
13
|
+
* commit and the snapshot interval: 0 for no snapshot, otherwise the events it folds in, which is
|
|
14
|
+
* always the whole commit. A commit snapshots when it reaches or crosses the next multiple of the
|
|
15
|
+
* interval, and an interval of 0 (or less, as in Go) disables snapshots.
|
|
16
|
+
*/
|
|
17
|
+
export function snapshotEventsDue(current, events, interval) {
|
|
18
|
+
safeIntegerArgument(events, 'events');
|
|
19
|
+
if (safeIntegerArgument(interval, 'interval') <= 0) {
|
|
20
|
+
return 0;
|
|
21
|
+
}
|
|
22
|
+
const divisor = BigInt(interval);
|
|
23
|
+
const nextSnapshotAt = interval - Number(((current % divisor) + divisor) % divisor);
|
|
24
|
+
return events < nextSnapshotAt ? 0 : events;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* The bytes the commit budget counts for a snapshot's item, with a `ttl` at its widest whether or
|
|
28
|
+
* not the entity type is retained. A store leaves out a snapshot over
|
|
29
|
+
* {@link MAX_COMMIT_ITEM_BYTES}, so an aggregate whose state outgrows the budget doesn't make
|
|
30
|
+
* every commit that reaches the interval fail.
|
|
31
|
+
*/
|
|
32
|
+
export function snapshotCommitSize(snapshot) {
|
|
33
|
+
return itemSize({ ...snapshotItem(snapshot), ttl: WIDEST_TTL });
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=snapshots.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"snapshots.js","sourceRoot":"","sources":["../src/snapshots.ts"],"names":[],"mappings":"AAAA,8FAA8F;AAE9F,OAAO,EAAC,YAAY,EAAE,QAAQ,EAAC,MAAM,oBAAoB,CAAA;AAEzD,OAAO,EAAC,mBAAmB,EAAC,MAAM,aAAa,CAAA;AAE/C;;;GAGG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG,OAAO,CAAA;AAE5C,kFAAkF;AAClF,MAAM,UAAU,GAAG,EAAC,CAAC,EAAE,CAAC,EAAE,IAAI,GAAG,GAAG,EAAE,CAAC,CAAC,QAAQ,EAAE,EAAC,CAAA;AAEnD;;;;;GAKG;AACH,MAAM,UAAU,iBAAiB,CAAC,OAAe,EAAE,MAAc,EAAE,QAAgB;IACjF,mBAAmB,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAA;IACrC,IAAI,mBAAmB,CAAC,QAAQ,EAAE,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;QACnD,OAAO,CAAC,CAAA;IACV,CAAC;IAED,MAAM,OAAO,GAAG,MAAM,CAAC,QAAQ,CAAC,CAAA;IAChC,MAAM,cAAc,GAAG,QAAQ,GAAG,MAAM,CAAC,CAAC,CAAC,OAAO,GAAG,OAAO,CAAC,GAAG,OAAO,CAAC,GAAG,OAAO,CAAC,CAAA;IAEnF,OAAO,MAAM,GAAG,cAAc,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAA;AAC7C,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,kBAAkB,CAAC,QAA4B;IAC7D,OAAO,QAAQ,CAAC,EAAC,GAAG,YAAY,CAAC,QAAQ,CAAC,EAAE,GAAG,EAAE,UAAU,EAAC,CAAC,CAAA;AAC/D,CAAC"}
|
package/dist/store.d.ts
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { type Aggregate, type EventDefinitions, type Rejection } from './aggregate.js';
|
|
2
|
+
import { type SerializedEvent } from './event.js';
|
|
3
|
+
import { type Metadata } from './metadata.js';
|
|
4
|
+
import type { CallOptions, Repository } from './repository.js';
|
|
5
|
+
import { type RetryPolicy } from './retry.js';
|
|
6
|
+
/** The default number of events between snapshots, as in Go and Rust. */
|
|
7
|
+
export declare const DEFAULT_SNAPSHOT_INTERVAL = 5;
|
|
8
|
+
/** How a {@link Store} fills in what commands leave out. */
|
|
9
|
+
export interface StoreOptions {
|
|
10
|
+
/** The region metadata gets when a command's metadata has none. */
|
|
11
|
+
region?: string | undefined;
|
|
12
|
+
/** The clock, in Unix milliseconds, that stamps metadata timestamps. `Date.now` by default. */
|
|
13
|
+
clock?: (() => number) | undefined;
|
|
14
|
+
/** The number of events between snapshots (default 5). 0 disables them. */
|
|
15
|
+
snapshotInterval?: number | undefined;
|
|
16
|
+
/**
|
|
17
|
+
* Makes the commit ID of a commit whose metadata has neither a command ID nor a commit ID. ULID
|
|
18
|
+
* format by default.
|
|
19
|
+
*/
|
|
20
|
+
commitId?: (() => string) | undefined;
|
|
21
|
+
}
|
|
22
|
+
/** An aggregate loaded from its stream, with the position it was loaded at. */
|
|
23
|
+
export interface Loaded<S> {
|
|
24
|
+
readonly entityId: string;
|
|
25
|
+
readonly state: S;
|
|
26
|
+
/** The sequence of the last event folded into the state, 0 for a new entity. */
|
|
27
|
+
readonly sequence: bigint;
|
|
28
|
+
/** The sequence of the snapshot the load started from, or the commit wrote, 0 with none. */
|
|
29
|
+
readonly snapshotSequence: bigint;
|
|
30
|
+
/** The command IDs of the events folded in since the snapshot: the dedupe window. */
|
|
31
|
+
readonly seenCommandIds: ReadonlySet<string>;
|
|
32
|
+
}
|
|
33
|
+
/** Options for {@link Store.execute}. */
|
|
34
|
+
export interface ExecuteOptions extends CallOptions {
|
|
35
|
+
/**
|
|
36
|
+
* The command's metadata. What it leaves out is empty, except the region and the timestamp,
|
|
37
|
+
* which the store fills in.
|
|
38
|
+
*/
|
|
39
|
+
metadata?: Partial<Metadata> | undefined;
|
|
40
|
+
}
|
|
41
|
+
/** Options for {@link Store.executeWithRetry}. */
|
|
42
|
+
export interface RetryOptions extends ExecuteOptions {
|
|
43
|
+
/** {@link DEFAULT_RETRY_POLICY} by default. */
|
|
44
|
+
policy?: RetryPolicy | undefined;
|
|
45
|
+
/** Waits between attempts. A timer by default; inject one in tests. */
|
|
46
|
+
sleep?: ((ms: number, signal?: AbortSignal) => Promise<void>) | undefined;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* What a command did:
|
|
50
|
+
*
|
|
51
|
+
* - `committed`: it was accepted, and its events (possibly none) are stored.
|
|
52
|
+
* - `duplicate`: an event since the latest snapshot already carries its command ID, so it didn't
|
|
53
|
+
* run.
|
|
54
|
+
* - `rejected`: the handler refused it. Nothing was written.
|
|
55
|
+
*/
|
|
56
|
+
export type ExecuteResult<S> = {
|
|
57
|
+
kind: 'committed';
|
|
58
|
+
state: Loaded<S>;
|
|
59
|
+
events: SerializedEvent[];
|
|
60
|
+
} | {
|
|
61
|
+
kind: 'duplicate';
|
|
62
|
+
commandId: string;
|
|
63
|
+
} | {
|
|
64
|
+
kind: 'rejected';
|
|
65
|
+
rejection: Rejection;
|
|
66
|
+
state: Loaded<S>;
|
|
67
|
+
};
|
|
68
|
+
export declare class Store {
|
|
69
|
+
#private;
|
|
70
|
+
readonly repository: Repository;
|
|
71
|
+
constructor(repository: Repository, options?: StoreOptions);
|
|
72
|
+
/** The number of events between snapshots of `aggregate`: its own interval, or the store's. */
|
|
73
|
+
snapshotInterval(aggregate: {
|
|
74
|
+
readonly snapshotInterval?: number | undefined;
|
|
75
|
+
}): number;
|
|
76
|
+
/**
|
|
77
|
+
* Loads an aggregate from its snapshot and the events after it, or from every event when it has
|
|
78
|
+
* no snapshot. An entity with no events loads as the initial state at sequence 0.
|
|
79
|
+
*
|
|
80
|
+
* The events must continue the sequence. A gap means another writer snapshotted and compacted
|
|
81
|
+
* the stream between the two reads, so the load starts over, up to three times, and then fails
|
|
82
|
+
* with a {@link SequenceGapError}. When no events follow, the snapshot is read again to rule out
|
|
83
|
+
* a compaction that left no gap to see.
|
|
84
|
+
*/
|
|
85
|
+
load<S, C, D extends EventDefinitions>(aggregate: Aggregate<S, C, D>, entityId: string, options?: CallOptions): Promise<Loaded<S>>;
|
|
86
|
+
/**
|
|
87
|
+
* Loads the aggregate, runs the command, and commits its events, with a snapshot when the
|
|
88
|
+
* commit reaches the snapshot interval. A snapshot whose item is over the per-item commit budget
|
|
89
|
+
* is left out, so the commit still succeeds and later loads replay from the one before.
|
|
90
|
+
*
|
|
91
|
+
* A command ID already carried by an event folded in since the latest snapshot makes the
|
|
92
|
+
* command a `duplicate`, and the handler doesn't run. A conflict with another writer throws;
|
|
93
|
+
* {@link executeWithRetry} runs the command again.
|
|
94
|
+
*/
|
|
95
|
+
execute<S, C, D extends EventDefinitions>(aggregate: Aggregate<S, C, D>, entityId: string, command: C, options?: ExecuteOptions): Promise<ExecuteResult<S>>;
|
|
96
|
+
/**
|
|
97
|
+
* Like {@link execute}, but loads and runs the command again after a retryable error (a
|
|
98
|
+
* conflict), waiting {@link retryDelay} between attempts. When the attempts run out, the last
|
|
99
|
+
* error is thrown. A rejection or a duplicate is a result, never retried.
|
|
100
|
+
*/
|
|
101
|
+
executeWithRetry<S, C, D extends EventDefinitions>(aggregate: Aggregate<S, C, D>, entityId: string, command: C, options?: RetryOptions): Promise<ExecuteResult<S>>;
|
|
102
|
+
}
|
|
103
|
+
//# sourceMappingURL=store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"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"}
|
package/dist/store.js
ADDED
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
// The Store: loads aggregates, runs commands, and commits their events with the snapshots the
|
|
2
|
+
// snapshot policy calls for.
|
|
3
|
+
import { encodeEventData, replayEvent, } from './aggregate.js';
|
|
4
|
+
import { commitIds } from './commit-id.js';
|
|
5
|
+
import { EvtError, SequenceGapError, SnapshotError, isConflict, isRetryable } from './errors.js';
|
|
6
|
+
import { eventId } from './event.js';
|
|
7
|
+
import { emptyMetadata } from './metadata.js';
|
|
8
|
+
import { DEFAULT_RETRY_POLICY, checkRetryPolicy, retryDelay, sleep, } from './retry.js';
|
|
9
|
+
import { MAX_COMMIT_ITEM_BYTES, snapshotCommitSize, snapshotEventsDue } from './snapshots.js';
|
|
10
|
+
/** The default number of events between snapshots, as in Go and Rust. */
|
|
11
|
+
export const DEFAULT_SNAPSHOT_INTERVAL = 5;
|
|
12
|
+
// How many times a load reads the stream when it finds a sequence gap.
|
|
13
|
+
const LOAD_ATTEMPTS = 3;
|
|
14
|
+
const INT64_MAX = 2n ** 63n - 1n;
|
|
15
|
+
export class Store {
|
|
16
|
+
repository;
|
|
17
|
+
#region;
|
|
18
|
+
#clock;
|
|
19
|
+
#snapshotInterval;
|
|
20
|
+
#commitId;
|
|
21
|
+
constructor(repository, options = {}) {
|
|
22
|
+
const interval = options.snapshotInterval ?? DEFAULT_SNAPSHOT_INTERVAL;
|
|
23
|
+
if (!Number.isSafeInteger(interval) || interval < 0) {
|
|
24
|
+
throw new EvtError('invalidArgument', `the snapshot interval must be a whole number of events: ${interval}`);
|
|
25
|
+
}
|
|
26
|
+
this.repository = repository;
|
|
27
|
+
this.#region = options.region ?? '';
|
|
28
|
+
this.#clock = options.clock ?? Date.now;
|
|
29
|
+
this.#snapshotInterval = interval;
|
|
30
|
+
this.#commitId = options.commitId ?? commitIds(this.#clock);
|
|
31
|
+
}
|
|
32
|
+
/** The number of events between snapshots of `aggregate`: its own interval, or the store's. */
|
|
33
|
+
snapshotInterval(aggregate) {
|
|
34
|
+
return aggregate.snapshotInterval ?? this.#snapshotInterval;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Loads an aggregate from its snapshot and the events after it, or from every event when it has
|
|
38
|
+
* no snapshot. An entity with no events loads as the initial state at sequence 0.
|
|
39
|
+
*
|
|
40
|
+
* The events must continue the sequence. A gap means another writer snapshotted and compacted
|
|
41
|
+
* the stream between the two reads, so the load starts over, up to three times, and then fails
|
|
42
|
+
* with a {@link SequenceGapError}. When no events follow, the snapshot is read again to rule out
|
|
43
|
+
* a compaction that left no gap to see.
|
|
44
|
+
*/
|
|
45
|
+
async load(aggregate, entityId, options = {}) {
|
|
46
|
+
// Each attempt waits for the one before it.
|
|
47
|
+
/* oxlint-disable no-await-in-loop */
|
|
48
|
+
for (let attempt = 1;; attempt++) {
|
|
49
|
+
try {
|
|
50
|
+
return await this.#loadOnce(aggregate, entityId, options);
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
if (!isConflict(error) || attempt >= LOAD_ATTEMPTS) {
|
|
54
|
+
throw error;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/* oxlint-enable no-await-in-loop */
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Loads the aggregate, runs the command, and commits its events, with a snapshot when the
|
|
62
|
+
* commit reaches the snapshot interval. A snapshot whose item is over the per-item commit budget
|
|
63
|
+
* is left out, so the commit still succeeds and later loads replay from the one before.
|
|
64
|
+
*
|
|
65
|
+
* A command ID already carried by an event folded in since the latest snapshot makes the
|
|
66
|
+
* command a `duplicate`, and the handler doesn't run. A conflict with another writer throws;
|
|
67
|
+
* {@link executeWithRetry} runs the command again.
|
|
68
|
+
*/
|
|
69
|
+
async execute(aggregate, entityId, command, options = {}) {
|
|
70
|
+
const metadata = this.#metadata(options.metadata);
|
|
71
|
+
const loaded = await this.load(aggregate, entityId, options);
|
|
72
|
+
const commandId = metadata.commandId;
|
|
73
|
+
if (commandId !== null && commandId !== '' && loaded.seenCommandIds.has(commandId)) {
|
|
74
|
+
return { kind: 'duplicate', commandId };
|
|
75
|
+
}
|
|
76
|
+
const decision = await aggregate.handle(loaded.state, command);
|
|
77
|
+
if (decision.kind === 'reject') {
|
|
78
|
+
return { kind: 'rejected', rejection: decision.rejection, state: loaded };
|
|
79
|
+
}
|
|
80
|
+
if (decision.events.length === 0) {
|
|
81
|
+
return { kind: 'committed', state: loaded, events: [] };
|
|
82
|
+
}
|
|
83
|
+
const { state, events, snapshot } = this.#stage(aggregate, loaded, decision.events, metadata);
|
|
84
|
+
await this.repository.commit({ entityId, events, snapshot }, options);
|
|
85
|
+
return { kind: 'committed', state, events };
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Like {@link execute}, but loads and runs the command again after a retryable error (a
|
|
89
|
+
* conflict), waiting {@link retryDelay} between attempts. When the attempts run out, the last
|
|
90
|
+
* error is thrown. A rejection or a duplicate is a result, never retried.
|
|
91
|
+
*/
|
|
92
|
+
async executeWithRetry(aggregate, entityId, command, options = {}) {
|
|
93
|
+
const policy = options.policy ?? DEFAULT_RETRY_POLICY;
|
|
94
|
+
checkRetryPolicy(policy);
|
|
95
|
+
const wait = options.sleep ?? sleep;
|
|
96
|
+
// Each attempt waits for the one before it.
|
|
97
|
+
/* oxlint-disable no-await-in-loop */
|
|
98
|
+
for (let retry = 0;; retry++) {
|
|
99
|
+
try {
|
|
100
|
+
return await this.execute(aggregate, entityId, command, options);
|
|
101
|
+
}
|
|
102
|
+
catch (error) {
|
|
103
|
+
if (!isRetryable(error) || retry + 1 >= policy.maxAttempts) {
|
|
104
|
+
throw error;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
await wait(retryDelay(policy, retry), options.signal);
|
|
108
|
+
}
|
|
109
|
+
/* oxlint-enable no-await-in-loop */
|
|
110
|
+
}
|
|
111
|
+
async #loadOnce(aggregate, entityId, options) {
|
|
112
|
+
const snapshot = await this.repository.snapshot(entityId, options);
|
|
113
|
+
let state = aggregate.initial();
|
|
114
|
+
let sequence = 0n;
|
|
115
|
+
let snapshotSequence = 0n;
|
|
116
|
+
if (snapshot) {
|
|
117
|
+
try {
|
|
118
|
+
state = aggregate.state.decode(snapshot.payload ?? new Uint8Array());
|
|
119
|
+
}
|
|
120
|
+
catch (error) {
|
|
121
|
+
throw new SnapshotError(entityId, error);
|
|
122
|
+
}
|
|
123
|
+
sequence = snapshot.eventSequence;
|
|
124
|
+
snapshotSequence = snapshot.sequence;
|
|
125
|
+
}
|
|
126
|
+
const events = snapshot
|
|
127
|
+
? await this.repository.eventsAfter(entityId, sequence, options)
|
|
128
|
+
: await this.repository.events(entityId, options);
|
|
129
|
+
// With no events after it, a snapshot and a compaction between the two reads would leave no
|
|
130
|
+
// gap to see, so the snapshot is read again: a newer one means the stream moved past it.
|
|
131
|
+
if (events.length === 0) {
|
|
132
|
+
const covered = (await this.repository.snapshot(entityId, options))?.eventSequence ?? 0n;
|
|
133
|
+
if (covered > sequence) {
|
|
134
|
+
throw new SequenceGapError(entityId, sequence, covered + 1n);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
const seenCommandIds = new Set();
|
|
138
|
+
for (const stored of events) {
|
|
139
|
+
if (stored.sequence !== sequence + 1n) {
|
|
140
|
+
throw new SequenceGapError(entityId, sequence, stored.sequence);
|
|
141
|
+
}
|
|
142
|
+
state = aggregate.apply(state, replayEvent(aggregate.events, stored));
|
|
143
|
+
sequence = stored.sequence;
|
|
144
|
+
const commandId = stored.metadata.commandId;
|
|
145
|
+
if (commandId !== null && commandId !== '') {
|
|
146
|
+
seenCommandIds.add(commandId);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return { entityId, state, sequence, snapshotSequence, seenCommandIds };
|
|
150
|
+
}
|
|
151
|
+
// Applies the decided events, and builds the stored events and the snapshot the policy calls
|
|
152
|
+
// for. The state is only used once the commit succeeds.
|
|
153
|
+
#stage(aggregate, loaded, decided, metadata) {
|
|
154
|
+
const entityId = loaded.entityId;
|
|
155
|
+
let sequence = loaded.sequence;
|
|
156
|
+
let state = loaded.state;
|
|
157
|
+
const events = [];
|
|
158
|
+
for (const event of decided) {
|
|
159
|
+
if (sequence >= INT64_MAX) {
|
|
160
|
+
throw new EvtError('invalidCommit', `invalid commit: entity ${entityId} is at the maximum event sequence`);
|
|
161
|
+
}
|
|
162
|
+
sequence++;
|
|
163
|
+
const { version, payload } = encodeEventData(aggregate.events, event);
|
|
164
|
+
events.push({
|
|
165
|
+
id: eventId(entityId, sequence),
|
|
166
|
+
sequence,
|
|
167
|
+
type: event.type,
|
|
168
|
+
version,
|
|
169
|
+
entityId,
|
|
170
|
+
entityType: aggregate.type,
|
|
171
|
+
payload,
|
|
172
|
+
metadata: copyMetadata(metadata),
|
|
173
|
+
});
|
|
174
|
+
state = aggregate.apply(state, event);
|
|
175
|
+
}
|
|
176
|
+
const interval = this.snapshotInterval(aggregate);
|
|
177
|
+
let snapshot = null;
|
|
178
|
+
if (snapshotEventsDue(loaded.sequence, decided.length, interval) > 0) {
|
|
179
|
+
snapshot = {
|
|
180
|
+
entityType: aggregate.type,
|
|
181
|
+
sequence: loaded.snapshotSequence + 1n,
|
|
182
|
+
payload: aggregate.state.encode(state),
|
|
183
|
+
};
|
|
184
|
+
// A snapshot over the per-item budget would fail every commit that carries it.
|
|
185
|
+
const size = snapshotCommitSize({ ...snapshot, entityId, eventSequence: sequence });
|
|
186
|
+
if (size > MAX_COMMIT_ITEM_BYTES) {
|
|
187
|
+
snapshot = null;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
const seenCommandIds = new Set(loaded.seenCommandIds);
|
|
191
|
+
if (metadata.commandId !== null && metadata.commandId !== '') {
|
|
192
|
+
seenCommandIds.add(metadata.commandId);
|
|
193
|
+
}
|
|
194
|
+
return {
|
|
195
|
+
state: {
|
|
196
|
+
entityId,
|
|
197
|
+
state,
|
|
198
|
+
sequence,
|
|
199
|
+
snapshotSequence: snapshot ? snapshot.sequence : loaded.snapshotSequence,
|
|
200
|
+
seenCommandIds,
|
|
201
|
+
},
|
|
202
|
+
events,
|
|
203
|
+
snapshot,
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
// The command's metadata with the store's region and the clock's timestamp where it has none,
|
|
207
|
+
// and the commit ID the engine stamps: the command ID, else the caller's, else a generated one.
|
|
208
|
+
// Field by field, so a field an untyped caller set to undefined is empty too, and every key is
|
|
209
|
+
// written.
|
|
210
|
+
#metadata(partial = {}) {
|
|
211
|
+
const empty = emptyMetadata();
|
|
212
|
+
const metadata = {
|
|
213
|
+
commandId: partial.commandId ?? empty.commandId,
|
|
214
|
+
trace: partial.trace ? new Map(partial.trace) : empty.trace,
|
|
215
|
+
origin: partial.origin ? { ...partial.origin } : empty.origin,
|
|
216
|
+
address: partial.address ?? empty.address,
|
|
217
|
+
region: partial.region || this.#region,
|
|
218
|
+
timestamp: partial.timestamp || rfc3339Seconds(this.#clock()),
|
|
219
|
+
principalId: partial.principalId ?? empty.principalId,
|
|
220
|
+
causationId: partial.causationId ?? empty.causationId,
|
|
221
|
+
extensions: partial.extensions ? new Map(partial.extensions) : empty.extensions,
|
|
222
|
+
commitId: partial.commitId ?? empty.commitId,
|
|
223
|
+
};
|
|
224
|
+
if (metadata.commandId !== null && metadata.commandId !== '') {
|
|
225
|
+
metadata.commitId = metadata.commandId;
|
|
226
|
+
}
|
|
227
|
+
else if (metadata.commitId === '') {
|
|
228
|
+
metadata.commitId = this.#commitId();
|
|
229
|
+
}
|
|
230
|
+
return metadata;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
// Each stored event gets its own metadata, so changing one changes no other.
|
|
234
|
+
function copyMetadata(metadata) {
|
|
235
|
+
return {
|
|
236
|
+
...metadata,
|
|
237
|
+
trace: metadata.trace && new Map(metadata.trace),
|
|
238
|
+
origin: metadata.origin && { ...metadata.origin },
|
|
239
|
+
extensions: new Map(metadata.extensions),
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
// RFC 3339 in UTC, to the second, as Go's time.RFC3339 formats a UTC time.
|
|
243
|
+
function rfc3339Seconds(ms) {
|
|
244
|
+
return new Date(ms).toISOString().replace(/\.\d{3}Z$/, 'Z');
|
|
245
|
+
}
|
|
246
|
+
//# sourceMappingURL=store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.js","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A failed assertion. It carries `actual` and `expected` the way Node's `assert` does, so runners
|
|
3
|
+
* such as Vitest and Jest show a diff, and `node:test` prints both.
|
|
4
|
+
*/
|
|
5
|
+
export declare class AssertionError extends Error {
|
|
6
|
+
readonly name = "AssertionError";
|
|
7
|
+
readonly actual: unknown;
|
|
8
|
+
readonly expected: unknown;
|
|
9
|
+
constructor(message: string, actual?: unknown, expected?: unknown);
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Whether two values have the same structure: objects of the same prototype by their own
|
|
13
|
+
* enumerable string and symbol keys (a key whose value is `undefined` counts as absent), arrays by
|
|
14
|
+
* their elements, holes included, `Map`s by their entries and `Set`s by their members, buffers and
|
|
15
|
+
* typed arrays by their bytes, dates by their time, regular expressions by their source and flags,
|
|
16
|
+
* and everything else with `Object.is`. `Map` keys and `Set` members match by identity, and
|
|
17
|
+
* `#private` fields aren't seen.
|
|
18
|
+
*/
|
|
19
|
+
export declare function deepEqual(a: unknown, b: unknown): boolean;
|
|
20
|
+
/** Prints a value for an assertion message, with `bigint`s, `Map`s and byte arrays legible. */
|
|
21
|
+
export declare function show(value: unknown): string;
|
|
22
|
+
//# sourceMappingURL=equal.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"equal.d.ts","sourceRoot":"","sources":["../../src/testing/equal.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,qBAAa,cAAe,SAAQ,KAAK;IACvC,SAAkB,IAAI,oBAAmB;IACzC,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAA;IACxB,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAA;IAE1B,YAAY,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,QAAQ,CAAC,EAAE,OAAO,EAIhE;CACF;AAED;;;;;;;GAOG;AACH,wBAAgB,SAAS,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,OAAO,GAAG,OAAO,CA2DzD;AAqBD,+FAA+F;AAC/F,wBAAgB,IAAI,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAsC3C"}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
// Structural equality and printing for test assertions, so /testing depends on no test framework.
|
|
2
|
+
/**
|
|
3
|
+
* A failed assertion. It carries `actual` and `expected` the way Node's `assert` does, so runners
|
|
4
|
+
* such as Vitest and Jest show a diff, and `node:test` prints both.
|
|
5
|
+
*/
|
|
6
|
+
export class AssertionError extends Error {
|
|
7
|
+
name = 'AssertionError';
|
|
8
|
+
actual;
|
|
9
|
+
expected;
|
|
10
|
+
constructor(message, actual, expected) {
|
|
11
|
+
super(message);
|
|
12
|
+
this.actual = actual;
|
|
13
|
+
this.expected = expected;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Whether two values have the same structure: objects of the same prototype by their own
|
|
18
|
+
* enumerable string and symbol keys (a key whose value is `undefined` counts as absent), arrays by
|
|
19
|
+
* their elements, holes included, `Map`s by their entries and `Set`s by their members, buffers and
|
|
20
|
+
* typed arrays by their bytes, dates by their time, regular expressions by their source and flags,
|
|
21
|
+
* and everything else with `Object.is`. `Map` keys and `Set` members match by identity, and
|
|
22
|
+
* `#private` fields aren't seen.
|
|
23
|
+
*/
|
|
24
|
+
export function deepEqual(a, b) {
|
|
25
|
+
if (Object.is(a, b)) {
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
if (typeof a !== 'object' || typeof b !== 'object' || a === null || b === null) {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
if (Object.getPrototypeOf(a) !== Object.getPrototypeOf(b)) {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
if (Array.isArray(a)) {
|
|
35
|
+
const other = b;
|
|
36
|
+
if (a.length !== other.length) {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
// An index loop, since every() skips holes.
|
|
40
|
+
for (let i = 0; i < a.length; i++) {
|
|
41
|
+
if (i in a !== i in other || !deepEqual(a[i], other[i])) {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
if (a instanceof ArrayBuffer || ArrayBuffer.isView(a)) {
|
|
48
|
+
const left = bytes(a);
|
|
49
|
+
const right = bytes(b);
|
|
50
|
+
return left.length === right.length && left.every((byte, i) => byte === right[i]);
|
|
51
|
+
}
|
|
52
|
+
if (a instanceof Map) {
|
|
53
|
+
const other = b;
|
|
54
|
+
return (a.size === other.size &&
|
|
55
|
+
[...a].every(([key, entry]) => other.has(key) && deepEqual(entry, other.get(key))));
|
|
56
|
+
}
|
|
57
|
+
if (a instanceof Set) {
|
|
58
|
+
const other = b;
|
|
59
|
+
return a.size === other.size && [...a].every((entry) => other.has(entry));
|
|
60
|
+
}
|
|
61
|
+
if (a instanceof Date) {
|
|
62
|
+
return Object.is(a.getTime(), b.getTime());
|
|
63
|
+
}
|
|
64
|
+
if (a instanceof RegExp) {
|
|
65
|
+
return a.source === b.source && a.flags === b.flags;
|
|
66
|
+
}
|
|
67
|
+
const left = definedKeys(a);
|
|
68
|
+
const right = definedKeys(b);
|
|
69
|
+
return (left.length === right.length &&
|
|
70
|
+
left.every((key) => Object.hasOwn(b, key) && deepEqual(field(a, key), field(b, key))));
|
|
71
|
+
}
|
|
72
|
+
function bytes(value) {
|
|
73
|
+
return value instanceof ArrayBuffer
|
|
74
|
+
? new Uint8Array(value)
|
|
75
|
+
: new Uint8Array(value.buffer, value.byteOffset, value.byteLength);
|
|
76
|
+
}
|
|
77
|
+
// The own enumerable keys, symbols included, whose values aren't undefined.
|
|
78
|
+
function definedKeys(value) {
|
|
79
|
+
const symbols = Object.getOwnPropertySymbols(value).filter((key) => Object.prototype.propertyIsEnumerable.call(value, key));
|
|
80
|
+
return [...Object.keys(value), ...symbols].filter((key) => field(value, key) !== undefined);
|
|
81
|
+
}
|
|
82
|
+
function field(value, key) {
|
|
83
|
+
return value[key];
|
|
84
|
+
}
|
|
85
|
+
/** Prints a value for an assertion message, with `bigint`s, `Map`s and byte arrays legible. */
|
|
86
|
+
export function show(value) {
|
|
87
|
+
if (typeof value === 'string') {
|
|
88
|
+
return JSON.stringify(value);
|
|
89
|
+
}
|
|
90
|
+
if (typeof value === 'bigint') {
|
|
91
|
+
return `${value}n`;
|
|
92
|
+
}
|
|
93
|
+
if (Object.is(value, -0)) {
|
|
94
|
+
return '-0';
|
|
95
|
+
}
|
|
96
|
+
if (typeof value !== 'object' || value === null) {
|
|
97
|
+
return String(value);
|
|
98
|
+
}
|
|
99
|
+
if (Array.isArray(value)) {
|
|
100
|
+
return `[${value.map(show).join(', ')}]`;
|
|
101
|
+
}
|
|
102
|
+
if (value instanceof Uint8Array) {
|
|
103
|
+
return `Uint8Array [${value.join(', ')}]`;
|
|
104
|
+
}
|
|
105
|
+
if (value instanceof Map) {
|
|
106
|
+
const entries = [...value].map(([key, entry]) => `${show(key)} => ${show(entry)}`);
|
|
107
|
+
return `Map {${entries.join(', ')}}`;
|
|
108
|
+
}
|
|
109
|
+
if (value instanceof Set) {
|
|
110
|
+
return `Set {${[...value].map(show).join(', ')}}`;
|
|
111
|
+
}
|
|
112
|
+
if (value instanceof Date) {
|
|
113
|
+
return Number.isNaN(value.getTime()) ? 'Invalid Date' : `Date ${value.toISOString()}`;
|
|
114
|
+
}
|
|
115
|
+
if (value instanceof Error) {
|
|
116
|
+
return `${value.name}: ${value.message}`;
|
|
117
|
+
}
|
|
118
|
+
const entries = Object.entries(value).map(([key, entry]) => `${show(key)}: ${show(entry)}`);
|
|
119
|
+
return `{${entries.join(', ')}}`;
|
|
120
|
+
}
|
|
121
|
+
//# sourceMappingURL=equal.js.map
|