@evolu/common 8.7.0 → 8.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/Array.d.ts +21 -21
- package/dist/src/Array.js +5 -5
- package/dist/src/Assert.d.ts +478 -141
- package/dist/src/Assert.d.ts.map +1 -1
- package/dist/src/Assert.js +644 -99
- package/dist/src/{Buffer.d.ts → Binary.d.ts} +85 -12
- package/dist/src/Binary.d.ts.map +1 -0
- package/dist/src/{Buffer.js → Binary.js} +206 -24
- package/dist/src/Console.d.ts +2 -10
- package/dist/src/Console.d.ts.map +1 -1
- package/dist/src/Console.js +6 -12
- package/dist/src/Crypto.d.ts +5 -5
- package/dist/src/Crypto.js +1 -1
- package/dist/src/Eq.d.ts +35 -40
- package/dist/src/Eq.d.ts.map +1 -1
- package/dist/src/Eq.js +51 -87
- package/dist/src/Function.d.ts +9 -14
- package/dist/src/Function.d.ts.map +1 -1
- package/dist/src/Function.js +7 -9
- package/dist/src/Http.d.ts +6 -7
- package/dist/src/Http.d.ts.map +1 -1
- package/dist/src/LeakDetector.d.ts.map +1 -1
- package/dist/src/LeakDetector.js +1 -0
- package/dist/src/LockManager.d.ts +1 -1
- package/dist/src/LockManager.js +1 -1
- package/dist/src/Lookup.d.ts +3 -2
- package/dist/src/Lookup.d.ts.map +1 -1
- package/dist/src/Lookup.js +4 -3
- package/dist/src/Number.d.ts +1 -1
- package/dist/src/Number.d.ts.map +1 -1
- package/dist/src/Number.js +1 -1
- package/dist/src/Object.d.ts +4 -4
- package/dist/src/Object.d.ts.map +1 -1
- package/dist/src/Object.js +5 -4
- package/dist/src/Option.d.ts +1 -2
- package/dist/src/Option.d.ts.map +1 -1
- package/dist/src/Option.js +1 -2
- package/dist/src/Platform.js +2 -0
- package/dist/src/Polyfills.js +4 -1
- package/dist/src/Random.d.ts +1 -1
- package/dist/src/Result.d.ts +49 -54
- package/dist/src/Result.d.ts.map +1 -1
- package/dist/src/Result.js +17 -22
- package/dist/src/Set.d.ts +6 -6
- package/dist/src/Set.js +2 -2
- package/dist/src/Sqlite.js +1 -1
- package/dist/src/String.js +3 -2
- package/dist/src/Task.d.ts +32 -32
- package/dist/src/Task.d.ts.map +1 -1
- package/dist/src/Task.js +20 -22
- package/dist/src/Test.d.ts +31 -3
- package/dist/src/Test.d.ts.map +1 -1
- package/dist/src/Test.js +46 -3
- package/dist/src/Time.d.ts +3 -11
- package/dist/src/Time.d.ts.map +1 -1
- package/dist/src/Time.js +10 -18
- package/dist/src/Type.d.ts +87 -85
- package/dist/src/Type.d.ts.map +1 -1
- package/dist/src/Type.js +70 -63
- package/dist/src/Types.d.ts +12 -12
- package/dist/src/Types.js +1 -1
- package/dist/src/WebSocket.d.ts +4 -7
- package/dist/src/WebSocket.d.ts.map +1 -1
- package/dist/src/Worker.d.ts +1 -1
- package/dist/src/Worker.js +7 -7
- package/dist/src/index.d.ts +1 -1
- package/dist/src/index.js +1 -1
- package/dist/src/intl/_en.d.ts.map +1 -1
- package/dist/src/intl/_en.js +1 -1
- package/dist/src/intl/ar.d.ts.map +1 -1
- package/dist/src/intl/ar.js +1 -1
- package/dist/src/intl/bn.d.ts.map +1 -1
- package/dist/src/intl/bn.js +1 -1
- package/dist/src/intl/ca.d.ts.map +1 -1
- package/dist/src/intl/ca.js +1 -1
- package/dist/src/intl/cs.d.ts.map +1 -1
- package/dist/src/intl/cs.js +1 -1
- package/dist/src/intl/da.d.ts.map +1 -1
- package/dist/src/intl/da.js +1 -1
- package/dist/src/intl/de.d.ts.map +1 -1
- package/dist/src/intl/de.js +1 -1
- package/dist/src/intl/el.d.ts.map +1 -1
- package/dist/src/intl/el.js +1 -1
- package/dist/src/intl/es.d.ts.map +1 -1
- package/dist/src/intl/es.js +1 -1
- package/dist/src/intl/fa.d.ts.map +1 -1
- package/dist/src/intl/fa.js +1 -1
- package/dist/src/intl/fi.d.ts.map +1 -1
- package/dist/src/intl/fi.js +1 -1
- package/dist/src/intl/fil.d.ts.map +1 -1
- package/dist/src/intl/fil.js +1 -1
- package/dist/src/intl/fr.d.ts.map +1 -1
- package/dist/src/intl/fr.js +1 -1
- package/dist/src/intl/he.d.ts.map +1 -1
- package/dist/src/intl/he.js +1 -1
- package/dist/src/intl/hi.d.ts.map +1 -1
- package/dist/src/intl/hi.js +1 -1
- package/dist/src/intl/hr.d.ts.map +1 -1
- package/dist/src/intl/hr.js +1 -1
- package/dist/src/intl/hu.d.ts.map +1 -1
- package/dist/src/intl/hu.js +1 -1
- package/dist/src/intl/id.d.ts.map +1 -1
- package/dist/src/intl/id.js +1 -1
- package/dist/src/intl/it.d.ts.map +1 -1
- package/dist/src/intl/it.js +1 -1
- package/dist/src/intl/ja.d.ts.map +1 -1
- package/dist/src/intl/ja.js +1 -1
- package/dist/src/intl/ko.d.ts.map +1 -1
- package/dist/src/intl/ko.js +1 -1
- package/dist/src/intl/ml.d.ts.map +1 -1
- package/dist/src/intl/ml.js +1 -1
- package/dist/src/intl/mr.d.ts.map +1 -1
- package/dist/src/intl/mr.js +1 -1
- package/dist/src/intl/ms.d.ts.map +1 -1
- package/dist/src/intl/ms.js +1 -1
- package/dist/src/intl/nb.d.ts.map +1 -1
- package/dist/src/intl/nb.js +1 -1
- package/dist/src/intl/nl.d.ts.map +1 -1
- package/dist/src/intl/nl.js +1 -1
- package/dist/src/intl/pa.d.ts.map +1 -1
- package/dist/src/intl/pa.js +1 -1
- package/dist/src/intl/pl.d.ts.map +1 -1
- package/dist/src/intl/pl.js +1 -1
- package/dist/src/intl/pt-BR.d.ts.map +1 -1
- package/dist/src/intl/pt-BR.js +1 -1
- package/dist/src/intl/pt.d.ts.map +1 -1
- package/dist/src/intl/pt.js +1 -1
- package/dist/src/intl/ro.d.ts.map +1 -1
- package/dist/src/intl/ro.js +1 -1
- package/dist/src/intl/sk.d.ts.map +1 -1
- package/dist/src/intl/sk.js +1 -1
- package/dist/src/intl/sl.d.ts.map +1 -1
- package/dist/src/intl/sl.js +1 -1
- package/dist/src/intl/sv.d.ts.map +1 -1
- package/dist/src/intl/sv.js +1 -1
- package/dist/src/intl/sw.d.ts.map +1 -1
- package/dist/src/intl/sw.js +1 -1
- package/dist/src/intl/ta.d.ts.map +1 -1
- package/dist/src/intl/ta.js +1 -1
- package/dist/src/intl/te.d.ts.map +1 -1
- package/dist/src/intl/te.js +1 -1
- package/dist/src/intl/th.d.ts.map +1 -1
- package/dist/src/intl/th.js +1 -1
- package/dist/src/intl/tr.d.ts.map +1 -1
- package/dist/src/intl/tr.js +1 -1
- package/dist/src/intl/uk.d.ts.map +1 -1
- package/dist/src/intl/uk.js +1 -1
- package/dist/src/intl/ur.d.ts.map +1 -1
- package/dist/src/intl/ur.js +1 -1
- package/dist/src/intl/vi.d.ts.map +1 -1
- package/dist/src/intl/vi.js +1 -1
- package/dist/src/intl/zh-CN.d.ts.map +1 -1
- package/dist/src/intl/zh-CN.js +1 -1
- package/dist/src/intl/zh-TW.d.ts.map +1 -1
- package/dist/src/intl/zh-TW.js +1 -1
- package/dist/src/local-first/Evolu.d.ts +12 -12
- package/dist/src/local-first/Evolu.d.ts.map +1 -1
- package/dist/src/local-first/Evolu.js +2 -2
- package/dist/src/local-first/Protocol.d.ts +14 -76
- package/dist/src/local-first/Protocol.d.ts.map +1 -1
- package/dist/src/local-first/Protocol.js +29 -206
- package/dist/src/local-first/Query.d.ts +11 -11
- package/dist/src/local-first/Query.js +6 -6
- package/dist/src/local-first/Relay.d.ts +2 -2
- package/dist/src/local-first/Schema.d.ts +3 -5
- package/dist/src/local-first/Schema.d.ts.map +1 -1
- package/dist/src/local-first/Schema.js +2 -4
- package/dist/src/local-first/Shared.d.ts.map +1 -1
- package/dist/src/local-first/Shared.js +5 -5
- package/dist/src/local-first/Storage.js +4 -4
- package/dist/src/local-first/Timestamp.d.ts +9 -0
- package/dist/src/local-first/Timestamp.d.ts.map +1 -1
- package/dist/src/local-first/Timestamp.js +13 -10
- package/package.json +7 -4
- package/src/Array.test.ts +809 -0
- package/src/Array.ts +21 -21
- package/src/Assert.test.ts +1181 -0
- package/src/Assert.ts +971 -227
- package/src/BigInt.test.ts +28 -0
- package/src/Binary.test.ts +1128 -0
- package/src/{Buffer.ts → Binary.ts} +273 -46
- package/src/Brand.test.ts +90 -0
- package/src/Cache.test.ts +183 -0
- package/src/Callbacks.test.ts +66 -0
- package/src/Console.test.ts +809 -0
- package/src/Console.ts +9 -17
- package/src/Crypto.test.ts +138 -0
- package/src/Crypto.ts +5 -5
- package/src/Eq.test.ts +333 -0
- package/src/Eq.ts +110 -176
- package/src/Error.test.ts +118 -0
- package/src/Function.test.ts +204 -0
- package/src/Function.ts +10 -15
- package/src/Http.test.ts +816 -0
- package/src/Http.ts +6 -7
- package/src/LeakDetector.test.ts +210 -0
- package/src/LeakDetector.ts +1 -0
- package/src/LockManager.ts +2 -2
- package/src/Lookup.test.ts +418 -0
- package/src/Lookup.ts +4 -3
- package/src/Microtask.test.ts +140 -0
- package/src/Number.test.ts +218 -0
- package/src/Number.ts +2 -2
- package/src/Object.test.ts +293 -0
- package/src/Object.ts +5 -4
- package/src/Option.test.ts +104 -0
- package/src/Option.ts +1 -2
- package/src/Order.test.ts +56 -0
- package/src/Platform.ts +2 -0
- package/src/Polyfills.ts +3 -4
- package/src/Random.test.ts +89 -0
- package/src/Random.ts +1 -1
- package/src/Ref.test.ts +139 -0
- package/src/RefCount.test.ts +184 -0
- package/src/Relation.test.ts +505 -0
- package/src/Resource.test.ts +4623 -0
- package/src/Result.test.ts +1829 -0
- package/src/Result.ts +49 -54
- package/src/Schedule.test.ts +1554 -0
- package/src/Set.test.ts +279 -0
- package/src/Set.ts +6 -6
- package/src/Sqlite.ts +1 -1
- package/src/Store.test.ts +288 -0
- package/src/String.test.ts +54 -0
- package/src/String.ts +3 -3
- package/src/Task.test.ts +10820 -0
- package/src/Task.ts +38 -43
- package/src/Test.test.ts +88 -0
- package/src/Test.ts +49 -3
- package/src/Time.test.ts +926 -0
- package/src/Time.ts +13 -21
- package/src/Type.base64.test.ts +88 -0
- package/src/Type.test.ts +20465 -0
- package/src/Type.ts +114 -111
- package/src/Types.test.ts +324 -0
- package/src/Types.ts +13 -13
- package/src/WebSocket.ts +4 -7
- package/src/Worker.test.ts +755 -0
- package/src/Worker.ts +13 -18
- package/src/index.ts +1 -1
- package/src/intl/_en.ts +1 -1
- package/src/intl/ar.ts +1 -1
- package/src/intl/bn.ts +1 -1
- package/src/intl/ca.ts +1 -1
- package/src/intl/cs.ts +1 -1
- package/src/intl/da.ts +1 -1
- package/src/intl/de.ts +1 -1
- package/src/intl/el.ts +1 -1
- package/src/intl/es.ts +1 -1
- package/src/intl/fa.ts +1 -1
- package/src/intl/fi.ts +1 -1
- package/src/intl/fil.ts +1 -1
- package/src/intl/fr.ts +1 -1
- package/src/intl/he.ts +1 -1
- package/src/intl/hi.ts +1 -1
- package/src/intl/hr.ts +1 -1
- package/src/intl/hu.ts +1 -1
- package/src/intl/id.ts +1 -1
- package/src/intl/intl.test.ts +3106 -0
- package/src/intl/it.ts +1 -1
- package/src/intl/ja.ts +1 -1
- package/src/intl/ko.ts +1 -1
- package/src/intl/ml.ts +1 -1
- package/src/intl/mr.ts +1 -1
- package/src/intl/ms.ts +1 -1
- package/src/intl/nb.ts +1 -1
- package/src/intl/nl.ts +1 -1
- package/src/intl/pa.ts +1 -1
- package/src/intl/pl.ts +1 -1
- package/src/intl/pt-BR.ts +1 -1
- package/src/intl/pt.ts +1 -1
- package/src/intl/ro.ts +1 -1
- package/src/intl/sk.ts +1 -1
- package/src/intl/sl.ts +1 -1
- package/src/intl/sv.ts +1 -1
- package/src/intl/sw.ts +1 -1
- package/src/intl/ta.ts +1 -1
- package/src/intl/te.ts +1 -1
- package/src/intl/th.ts +1 -1
- package/src/intl/tr.ts +1 -1
- package/src/intl/uk.ts +1 -1
- package/src/intl/ur.ts +1 -1
- package/src/intl/vi.ts +1 -1
- package/src/intl/zh-CN.ts +1 -1
- package/src/intl/zh-TW.ts +1 -1
- package/src/local-first/Evolu.test.ts +1921 -0
- package/src/local-first/Evolu.ts +18 -14
- package/src/local-first/Owner.test.ts +83 -0
- package/src/local-first/Protocol.test.ts +1037 -0
- package/src/local-first/Protocol.ts +58 -261
- package/src/local-first/Query.test.ts +349 -0
- package/src/local-first/Query.ts +11 -11
- package/src/local-first/Relay.ts +2 -2
- package/src/local-first/Schema.ts +3 -5
- package/src/local-first/Shared.test.ts +1828 -0
- package/src/local-first/Shared.ts +9 -8
- package/src/local-first/Storage.ts +4 -4
- package/src/local-first/Timestamp.test.ts +374 -0
- package/src/local-first/Timestamp.ts +22 -10
- package/dist/src/Buffer.d.ts.map +0 -1
package/src/Assert.ts
CHANGED
|
@@ -1,11 +1,49 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Platform-agnostic assertions for invariants, examples, and tests.
|
|
3
|
+
*
|
|
4
|
+
* An assertion documents a condition required for the program to be correct.
|
|
5
|
+
* Ideally, the type system should enforce it so incorrect code cannot compile,
|
|
6
|
+
* but that is not always possible (not even in Rust). A runtime assertion
|
|
7
|
+
* failure therefore indicates a bug that must be fixed. Throwing at the point
|
|
8
|
+
* of violation prevents invalid state from propagating and makes the defect
|
|
9
|
+
* easier to diagnose.
|
|
10
|
+
*
|
|
11
|
+
* Evolu provides its own assertions so the same concise API works for
|
|
12
|
+
* production invariants, executable examples, and tests on every platform. The
|
|
13
|
+
* general {@link assert} requires a message explaining the invariant, and
|
|
14
|
+
* specialized assertions provide focused diagnostics and narrowing. The API
|
|
15
|
+
* deliberately favors strict and predictable contracts: one {@link assertEqual}
|
|
16
|
+
* covers primitives and structural Data comparisons, {@link assertEqualBytes}
|
|
17
|
+
* compares bytes across array representations, and {@link assertSame} is
|
|
18
|
+
* reserved for SameValue or reference identity. Generic partial structural
|
|
19
|
+
* assertions such as `assertMatches` are omitted so unmentioned state cannot
|
|
20
|
+
* hide regressions. If a complete expected value is too large to keep inline,
|
|
21
|
+
* use a fixture or a focused domain helper that keeps the contract explicit.
|
|
22
|
+
* {@link assertOk} and {@link assertErr} provide convenient Result narrowing,
|
|
23
|
+
* while {@link assertType} provides runtime Type assertions and compile-time
|
|
24
|
+
* type equality similar to Vitest's `expectTypeOf` without requiring a test
|
|
25
|
+
* runner. {@link assertThrows} and {@link assertRejects} verify thrown and
|
|
26
|
+
* rejected values without matcher semantics. Together, these assertions keep
|
|
27
|
+
* documentation examples concise and directly copyable.
|
|
28
|
+
*
|
|
29
|
+
* In Node.js, failures use the native `AssertionError` for structured
|
|
30
|
+
* diagnostics and diffs. Other platforms use a compatible fallback. You should
|
|
31
|
+
* not need to import Node.js assertions unless Evolu does not provide an
|
|
32
|
+
* equivalent or a test requires exact Node.js semantics.
|
|
33
|
+
*
|
|
34
|
+
* Do not use assertions to validate external input. Use a {@link Type}
|
|
35
|
+
* declaration's `fromUnknown` so invalid input is represented by a typed
|
|
36
|
+
* {@link Result}.
|
|
37
|
+
*
|
|
38
|
+
* TODO(next major): Add a separate production build that replaces full
|
|
39
|
+
* assertion messages with numeric error codes and a decoder, following React's
|
|
40
|
+
* approach, to reduce the core bundle size since Evolu has many assertions.
|
|
3
41
|
*
|
|
4
42
|
* @module
|
|
5
43
|
*/
|
|
6
44
|
|
|
7
45
|
import type { NonEmptyArray, NonEmptyReadonlyArray } from "./Array.ts";
|
|
8
|
-
import { eqData,
|
|
46
|
+
import { eqArrayNumber, eqData, eqSameValue, type Eq } from "./Eq.ts";
|
|
9
47
|
import type {
|
|
10
48
|
AnyResult,
|
|
11
49
|
Err,
|
|
@@ -14,306 +52,616 @@ import type {
|
|
|
14
52
|
Ok,
|
|
15
53
|
Result,
|
|
16
54
|
} from "./Result.ts";
|
|
17
|
-
import type {
|
|
18
|
-
import type {
|
|
55
|
+
import type { assertType, Type } from "./Type.ts";
|
|
56
|
+
import type { ValueWithLength } from "./Types.ts";
|
|
19
57
|
|
|
20
58
|
/**
|
|
21
|
-
*
|
|
22
|
-
*
|
|
59
|
+
* Asserts that a condition is truthy.
|
|
60
|
+
*
|
|
61
|
+
* Throws an `AssertionError` with the provided message if the condition is
|
|
62
|
+
* falsy, preventing invalid state from propagating and making the failure
|
|
63
|
+
* easier to diagnose.
|
|
23
64
|
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
65
|
+
* Use `assert` only when a more specific assertion, such as {@link assertTrue}
|
|
66
|
+
* or {@link assertEqual}, does not apply. For that reason, `assert` requires a
|
|
67
|
+
* custom message explaining the expected condition.
|
|
26
68
|
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
69
|
+
* Options provide structured diagnostics for custom assertions. Pass an
|
|
70
|
+
* underlying failure as `cause` when it explains why the asserted condition
|
|
71
|
+
* failed, and pass the custom assertion as `stackStartFn` to omit its
|
|
72
|
+
* implementation from the stack trace.
|
|
30
73
|
*
|
|
31
74
|
* ### Example
|
|
32
75
|
*
|
|
33
76
|
* ```ts
|
|
34
|
-
* import { assert, assertEqual,
|
|
77
|
+
* import { assert, assertEqual, assertType } from "@evolu/common";
|
|
35
78
|
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
* assertEqual(
|
|
79
|
+
* const value: unknown = "Evolu";
|
|
80
|
+
* assert(typeof value === "string", "Expected a string.");
|
|
81
|
+
*
|
|
82
|
+
* assertType<typeof value, string>();
|
|
83
|
+
* assertEqual(value, "Evolu");
|
|
41
84
|
* ```
|
|
85
|
+
*
|
|
86
|
+
* @group Assertions
|
|
42
87
|
*/
|
|
43
88
|
export const assert: (
|
|
44
89
|
condition: unknown,
|
|
45
90
|
message: string,
|
|
46
|
-
|
|
91
|
+
options?: {
|
|
92
|
+
readonly actual?: unknown;
|
|
93
|
+
readonly expected?: unknown;
|
|
94
|
+
readonly operator?: string;
|
|
95
|
+
readonly diff?: "full" | undefined;
|
|
96
|
+
readonly cause?: unknown;
|
|
97
|
+
readonly stackStartFn?: (...args: Array<never>) => unknown;
|
|
98
|
+
},
|
|
99
|
+
) => asserts condition = (condition, message, options = {}) => {
|
|
47
100
|
// oxlint-disable-next-line typescript/strict-boolean-expressions -- JavaScript truthiness is the contract of assert.
|
|
48
|
-
if (
|
|
49
|
-
|
|
101
|
+
if (condition) return;
|
|
102
|
+
|
|
103
|
+
const actual = Object.hasOwn(options, "actual") ? options.actual : condition;
|
|
104
|
+
const expected = Object.hasOwn(options, "expected") ? options.expected : true;
|
|
105
|
+
const operator = options.operator ?? "==";
|
|
106
|
+
const stackStartFn = options.stackStartFn ?? assert;
|
|
107
|
+
if (NodeAssert !== undefined) {
|
|
108
|
+
const error = new NodeAssert.AssertionError({
|
|
109
|
+
message,
|
|
110
|
+
actual,
|
|
111
|
+
expected,
|
|
112
|
+
operator: options.diff === "full" ? "deepStrictEqual" : operator,
|
|
113
|
+
diff: options.diff,
|
|
114
|
+
stackStartFn,
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
if (options.diff === "full") {
|
|
118
|
+
error.operator = operator;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (options.cause !== undefined) {
|
|
122
|
+
Object.defineProperty(error, "cause", {
|
|
123
|
+
configurable: true,
|
|
124
|
+
value: options.cause,
|
|
125
|
+
writable: true,
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
throw error;
|
|
50
130
|
}
|
|
131
|
+
|
|
132
|
+
const error = Object.assign(
|
|
133
|
+
new Error(
|
|
134
|
+
message,
|
|
135
|
+
options.cause === undefined ? undefined : { cause: options.cause },
|
|
136
|
+
),
|
|
137
|
+
{
|
|
138
|
+
name: "AssertionError",
|
|
139
|
+
code: "ERR_ASSERTION" as const,
|
|
140
|
+
actual,
|
|
141
|
+
expected,
|
|
142
|
+
generatedMessage: false,
|
|
143
|
+
operator,
|
|
144
|
+
},
|
|
145
|
+
);
|
|
146
|
+
|
|
147
|
+
Error.captureStackTrace?.(error, stackStartFn);
|
|
148
|
+
|
|
149
|
+
throw error;
|
|
51
150
|
};
|
|
52
151
|
|
|
152
|
+
// Node.js generates assertion diffs inside its AssertionError constructor; the
|
|
153
|
+
// test runner does not derive them from actual and expected metadata alone. Use
|
|
154
|
+
// the native constructor when available and the portable fallback elsewhere.
|
|
155
|
+
const NodeAssert = globalThis.process?.getBuiltinModule?.("node:assert/strict");
|
|
156
|
+
|
|
53
157
|
/**
|
|
54
|
-
* Asserts that
|
|
158
|
+
* Asserts that a value is exactly `true`.
|
|
55
159
|
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
160
|
+
* Boolean conditions preserve their control-flow narrowing, including named
|
|
161
|
+
* type guards. Unknown values narrow to the literal `true`. Unlike
|
|
162
|
+
* {@link assert}, this checks an exact boolean value instead of truthiness and
|
|
163
|
+
* does not require a custom message.
|
|
58
164
|
*
|
|
59
165
|
* ### Example
|
|
60
166
|
*
|
|
61
167
|
* ```ts
|
|
62
|
-
* import {
|
|
168
|
+
* import { assertTrue, assertType } from "@evolu/common";
|
|
63
169
|
*
|
|
64
170
|
* interface User {
|
|
65
171
|
* readonly name: string;
|
|
66
172
|
* }
|
|
67
173
|
*
|
|
68
|
-
* const
|
|
69
|
-
*
|
|
174
|
+
* const isUser = (value: unknown): value is User =>
|
|
175
|
+
* typeof value === "object" &&
|
|
176
|
+
* value !== null &&
|
|
177
|
+
* "name" in value &&
|
|
178
|
+
* typeof value.name === "string";
|
|
70
179
|
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
180
|
+
* const value: unknown = { name: "Ada" };
|
|
181
|
+
* assertTrue(isUser(value));
|
|
182
|
+
* assertType<typeof value, User>();
|
|
183
|
+
*
|
|
184
|
+
* const condition: unknown = true;
|
|
185
|
+
* assertTrue(condition);
|
|
186
|
+
* assertType<typeof condition, true>();
|
|
73
187
|
* ```
|
|
188
|
+
*
|
|
189
|
+
* @group Assertions
|
|
74
190
|
*/
|
|
75
|
-
export
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
191
|
+
export function assertTrue(condition: boolean): asserts condition;
|
|
192
|
+
export function assertTrue(value: unknown): asserts value is true;
|
|
193
|
+
export function assertTrue(value: unknown): void {
|
|
194
|
+
assert(value === true, "Expected true.", {
|
|
195
|
+
actual: value,
|
|
196
|
+
expected: true,
|
|
197
|
+
operator: "strictEqual",
|
|
198
|
+
stackStartFn: assertTrue,
|
|
199
|
+
});
|
|
200
|
+
}
|
|
81
201
|
|
|
82
202
|
/**
|
|
83
|
-
* Asserts that a value is exactly `
|
|
203
|
+
* Asserts that a value is exactly `false` and narrows it to `false`.
|
|
84
204
|
*
|
|
85
|
-
* Unlike {@link assert}, this checks an exact boolean value instead of
|
|
86
|
-
*
|
|
205
|
+
* Unlike {@link assert}, this checks an exact boolean value instead of falsiness
|
|
206
|
+
* and does not require a custom message.
|
|
87
207
|
*
|
|
88
208
|
* ### Example
|
|
89
209
|
*
|
|
90
210
|
* ```ts
|
|
91
|
-
* import {
|
|
211
|
+
* import { assertFalse, assertType } from "@evolu/common";
|
|
92
212
|
*
|
|
93
|
-
* const value: unknown =
|
|
94
|
-
*
|
|
95
|
-
* assertType<
|
|
213
|
+
* const value: unknown = false;
|
|
214
|
+
* assertFalse(value);
|
|
215
|
+
* assertType<typeof value, false>();
|
|
96
216
|
* ```
|
|
217
|
+
*
|
|
218
|
+
* @group Assertions
|
|
97
219
|
*/
|
|
98
|
-
export const
|
|
220
|
+
export const assertFalse: (value: unknown) => asserts value is false = (
|
|
99
221
|
value,
|
|
100
222
|
) => {
|
|
101
|
-
assert(value ===
|
|
223
|
+
assert(value === false, "Expected false.", {
|
|
224
|
+
actual: value,
|
|
225
|
+
expected: false,
|
|
226
|
+
operator: "strictEqual",
|
|
227
|
+
stackStartFn: assertFalse,
|
|
228
|
+
});
|
|
102
229
|
};
|
|
103
230
|
|
|
104
231
|
/**
|
|
105
|
-
* Asserts that a
|
|
232
|
+
* Asserts that a condition becomes true after exactly the specified number of
|
|
233
|
+
* microtasks.
|
|
106
234
|
*
|
|
107
|
-
*
|
|
108
|
-
*
|
|
235
|
+
* Use this in tests that intentionally specify async scheduling behavior.
|
|
236
|
+
* Application code must not depend on exact microtask counts. Maintainers
|
|
237
|
+
* should review count changes because they indicate that an async pipeline
|
|
238
|
+
* changed.
|
|
109
239
|
*
|
|
110
240
|
* ### Example
|
|
111
241
|
*
|
|
112
242
|
* ```ts
|
|
113
|
-
* import {
|
|
243
|
+
* import { assertConditionAfterMicrotasks } from "@evolu/common";
|
|
114
244
|
*
|
|
115
|
-
*
|
|
116
|
-
*
|
|
117
|
-
*
|
|
245
|
+
* let ready = false;
|
|
246
|
+
* queueMicrotask(() => {
|
|
247
|
+
* ready = true;
|
|
248
|
+
* });
|
|
249
|
+
*
|
|
250
|
+
* await assertConditionAfterMicrotasks(() => ready, 1);
|
|
118
251
|
* ```
|
|
252
|
+
*
|
|
253
|
+
* @group Assertions
|
|
119
254
|
*/
|
|
120
|
-
export const
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
255
|
+
export const assertConditionAfterMicrotasks = async (
|
|
256
|
+
condition: () => boolean,
|
|
257
|
+
expectedMicrotaskCount: number,
|
|
258
|
+
): Promise<void> => {
|
|
259
|
+
for (
|
|
260
|
+
let microtaskCount = 0;
|
|
261
|
+
microtaskCount < expectedMicrotaskCount;
|
|
262
|
+
microtaskCount++
|
|
263
|
+
) {
|
|
264
|
+
const actual = condition();
|
|
265
|
+
assert(
|
|
266
|
+
!actual,
|
|
267
|
+
`Expected condition to be false after ${microtaskCount} microtasks.`,
|
|
268
|
+
{
|
|
269
|
+
actual,
|
|
270
|
+
expected: false,
|
|
271
|
+
operator: "strictEqual",
|
|
272
|
+
stackStartFn: assertConditionAfterMicrotasks,
|
|
273
|
+
},
|
|
274
|
+
);
|
|
275
|
+
await Promise.resolve();
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const actual = condition();
|
|
279
|
+
assert(
|
|
280
|
+
actual,
|
|
281
|
+
`Expected condition to be true after exactly ${expectedMicrotaskCount} microtasks.`,
|
|
282
|
+
{
|
|
283
|
+
actual,
|
|
284
|
+
expected: true,
|
|
285
|
+
operator: "strictEqual",
|
|
286
|
+
stackStartFn: assertConditionAfterMicrotasks,
|
|
287
|
+
},
|
|
288
|
+
);
|
|
124
289
|
};
|
|
125
290
|
|
|
126
291
|
/**
|
|
127
|
-
* Asserts that
|
|
292
|
+
* Asserts that a function throws the expected value.
|
|
128
293
|
*
|
|
129
|
-
*
|
|
130
|
-
*
|
|
294
|
+
* Expected values use {@link assertEqual} semantics. To perform several or
|
|
295
|
+
* specialized assertions, pass an assertion function that receives the thrown
|
|
296
|
+
* value and returns nothing. Returning a predicate result fails. Use
|
|
297
|
+
* {@link assertThrowsSame} for SameValue or reference identity and
|
|
298
|
+
* {@link assertThrowsInstanceOf} for a runtime type.
|
|
299
|
+
*
|
|
300
|
+
* Because JavaScript permits throwing functions, a function second argument is
|
|
301
|
+
* always treated as an assertion function. Use `assertThrowsSame` to assert
|
|
302
|
+
* that a particular function was thrown.
|
|
131
303
|
*
|
|
132
304
|
* ### Example
|
|
133
305
|
*
|
|
134
306
|
* ```ts
|
|
135
|
-
* import {
|
|
307
|
+
* import { assertThrows } from "@evolu/common";
|
|
136
308
|
*
|
|
137
|
-
*
|
|
138
|
-
*
|
|
139
|
-
*
|
|
140
|
-
*
|
|
309
|
+
* assertThrows(
|
|
310
|
+
* () => {
|
|
311
|
+
* // oxlint-disable-next-line eslint/no-throw-literal, typescript/only-throw-error -- Exercise arbitrary thrown values.
|
|
312
|
+
* throw { type: "ExpectedFailure", value: 42 };
|
|
313
|
+
* },
|
|
314
|
+
* { type: "ExpectedFailure", value: 42 },
|
|
315
|
+
* );
|
|
141
316
|
* ```
|
|
317
|
+
*
|
|
318
|
+
* @group Assertions
|
|
142
319
|
*/
|
|
143
|
-
export function
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
...dataError: AssertEqualError<Actual | Expected>
|
|
320
|
+
export function assertThrows(
|
|
321
|
+
run: () => unknown,
|
|
322
|
+
assertThrown: (thrown: unknown) => void,
|
|
147
323
|
): void;
|
|
148
|
-
export function
|
|
149
|
-
|
|
324
|
+
export function assertThrows(run: () => unknown, expected: unknown): void;
|
|
325
|
+
export function assertThrows(
|
|
326
|
+
run: () => unknown,
|
|
327
|
+
expectedOrAssert: unknown,
|
|
328
|
+
): void {
|
|
329
|
+
const thrown = captureThrown(run, assertThrows);
|
|
330
|
+
|
|
331
|
+
if (typeof expectedOrAssert === "function") {
|
|
332
|
+
const result = (expectedOrAssert as (thrown: unknown) => unknown)(thrown);
|
|
333
|
+
assert(
|
|
334
|
+
result === undefined,
|
|
335
|
+
"Expected the thrown value assertion to return undefined.",
|
|
336
|
+
{
|
|
337
|
+
actual: result,
|
|
338
|
+
expected: undefined,
|
|
339
|
+
operator: "strictEqual",
|
|
340
|
+
stackStartFn: assertThrows,
|
|
341
|
+
},
|
|
342
|
+
);
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
assert(
|
|
347
|
+
eqUnknown(thrown, expectedOrAssert),
|
|
348
|
+
"Expected the thrown value to equal the expected value.",
|
|
349
|
+
{
|
|
350
|
+
actual: thrown,
|
|
351
|
+
expected: expectedOrAssert,
|
|
352
|
+
operator: "eqData",
|
|
353
|
+
diff: "full",
|
|
354
|
+
stackStartFn: assertThrows,
|
|
355
|
+
},
|
|
356
|
+
);
|
|
150
357
|
}
|
|
151
358
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
359
|
+
/**
|
|
360
|
+
* Asserts that a function throws the same value using `Object.is`.
|
|
361
|
+
*
|
|
362
|
+
* Use this to verify that a thrown value was propagated unchanged. For value
|
|
363
|
+
* comparisons, use {@link assertThrows}.
|
|
364
|
+
*
|
|
365
|
+
* ### Example
|
|
366
|
+
*
|
|
367
|
+
* ```ts
|
|
368
|
+
* import { assertThrowsSame } from "@evolu/common";
|
|
369
|
+
*
|
|
370
|
+
* const expected = new Error("Unavailable.");
|
|
371
|
+
* assertThrowsSame(() => {
|
|
372
|
+
* throw expected;
|
|
373
|
+
* }, expected);
|
|
374
|
+
* ```
|
|
375
|
+
*
|
|
376
|
+
* @group Assertions
|
|
377
|
+
*/
|
|
378
|
+
export const assertThrowsSame = (
|
|
379
|
+
run: () => unknown,
|
|
380
|
+
expected: unknown,
|
|
381
|
+
): void => {
|
|
382
|
+
const thrown = captureThrown(run, assertThrowsSame);
|
|
383
|
+
assert(
|
|
384
|
+
eqSameValue(thrown, expected),
|
|
385
|
+
"Expected the thrown value to be the same as the expected value.",
|
|
386
|
+
{
|
|
387
|
+
actual: thrown,
|
|
388
|
+
expected,
|
|
389
|
+
operator: "strictEqual",
|
|
390
|
+
stackStartFn: assertThrowsSame,
|
|
391
|
+
},
|
|
392
|
+
);
|
|
393
|
+
};
|
|
161
394
|
|
|
162
395
|
/**
|
|
163
|
-
* Asserts that a
|
|
164
|
-
* and narrows the Result.
|
|
396
|
+
* Asserts that a function throws an instance of a constructor.
|
|
165
397
|
*
|
|
166
|
-
*
|
|
167
|
-
*
|
|
168
|
-
* inferred independently, so an unbranded literal can compare a branded
|
|
169
|
-
* primitive or collection. Pass a custom {@link Eq} when either value is outside
|
|
170
|
-
* {@link Data} or needs domain-specific equality.
|
|
398
|
+
* Returns the narrowed instance so additional properties can be asserted
|
|
399
|
+
* without running the function again.
|
|
171
400
|
*
|
|
172
401
|
* ### Example
|
|
173
402
|
*
|
|
174
403
|
* ```ts
|
|
175
|
-
* import {
|
|
176
|
-
* assertEqual,
|
|
177
|
-
* assertOk,
|
|
178
|
-
* assertType,
|
|
179
|
-
* ok,
|
|
180
|
-
* type Ok,
|
|
181
|
-
* type Result,
|
|
182
|
-
* type Typed,
|
|
183
|
-
* } from "@evolu/common";
|
|
404
|
+
* import { assertEqual, assertThrowsInstanceOf } from "@evolu/common";
|
|
184
405
|
*
|
|
185
|
-
*
|
|
186
|
-
*
|
|
187
|
-
* }
|
|
406
|
+
* const error = assertThrowsInstanceOf(() => {
|
|
407
|
+
* throw new TypeError("Expected a string.");
|
|
408
|
+
* }, TypeError);
|
|
409
|
+
* assertEqual(error.message, "Expected a string.");
|
|
410
|
+
* ```
|
|
188
411
|
*
|
|
189
|
-
*
|
|
412
|
+
* @group Assertions
|
|
413
|
+
*/
|
|
414
|
+
export const assertThrowsInstanceOf = <
|
|
415
|
+
T extends {
|
|
416
|
+
readonly name: string;
|
|
417
|
+
} & (abstract new (...args: Array<never>) => unknown),
|
|
418
|
+
>(
|
|
419
|
+
run: () => unknown,
|
|
420
|
+
constructor: T,
|
|
421
|
+
): InstanceType<T> => {
|
|
422
|
+
const thrown = captureThrown(run, assertThrowsInstanceOf);
|
|
423
|
+
assert(
|
|
424
|
+
thrown instanceof constructor,
|
|
425
|
+
constructor.name === ""
|
|
426
|
+
? "Expected the thrown value to be an instance of the provided constructor."
|
|
427
|
+
: `Expected the thrown value to be an instance of ${constructor.name}.`,
|
|
428
|
+
{
|
|
429
|
+
actual: thrown,
|
|
430
|
+
expected: constructor,
|
|
431
|
+
operator: "instanceof",
|
|
432
|
+
stackStartFn: assertThrowsInstanceOf,
|
|
433
|
+
},
|
|
434
|
+
);
|
|
435
|
+
return thrown as InstanceType<T>;
|
|
436
|
+
};
|
|
437
|
+
|
|
438
|
+
const captureThrown = (
|
|
439
|
+
run: () => unknown,
|
|
440
|
+
stackStartFn: (...args: Array<never>) => unknown,
|
|
441
|
+
): unknown => {
|
|
442
|
+
let value: unknown;
|
|
443
|
+
|
|
444
|
+
try {
|
|
445
|
+
value = run();
|
|
446
|
+
} catch (error) {
|
|
447
|
+
return error;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
assert(false, "Expected function to throw.", {
|
|
451
|
+
actual: value,
|
|
452
|
+
expected: "throw",
|
|
453
|
+
operator: "throws",
|
|
454
|
+
stackStartFn,
|
|
455
|
+
});
|
|
456
|
+
};
|
|
457
|
+
|
|
458
|
+
/**
|
|
459
|
+
* Asserts that a promise rejects with the expected value.
|
|
190
460
|
*
|
|
191
|
-
*
|
|
461
|
+
* Expected values use {@link assertEqual} semantics. To perform several or
|
|
462
|
+
* specialized assertions, pass an assertion function that receives the
|
|
463
|
+
* rejection reason and returns nothing. Returning a predicate result fails. Use
|
|
464
|
+
* {@link assertRejectsSame} for SameValue or reference identity and
|
|
465
|
+
* {@link assertRejectsInstanceOf} for a runtime type.
|
|
466
|
+
*
|
|
467
|
+
* Because JavaScript permits rejecting with functions, a function second
|
|
468
|
+
* argument is always treated as an assertion function. Use `assertRejectsSame`
|
|
469
|
+
* to assert that a particular function was rejected.
|
|
470
|
+
*
|
|
471
|
+
* ### Example
|
|
192
472
|
*
|
|
193
|
-
*
|
|
194
|
-
*
|
|
195
|
-
*
|
|
473
|
+
* ```ts
|
|
474
|
+
* import { assertRejects } from "@evolu/common";
|
|
475
|
+
*
|
|
476
|
+
* const expected = new Error("Unavailable.");
|
|
477
|
+
* await assertRejects(Promise.reject(expected), expected);
|
|
196
478
|
* ```
|
|
479
|
+
*
|
|
480
|
+
* @group Assertions
|
|
197
481
|
*/
|
|
198
|
-
export function
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
):
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
if (comparison.length === 0) return;
|
|
482
|
+
export function assertRejects(
|
|
483
|
+
promise: PromiseLike<unknown>,
|
|
484
|
+
assertRejected: (reason: unknown) => void,
|
|
485
|
+
): Promise<void>;
|
|
486
|
+
export function assertRejects(
|
|
487
|
+
promise: PromiseLike<unknown>,
|
|
488
|
+
expected: unknown,
|
|
489
|
+
): Promise<void>;
|
|
490
|
+
export async function assertRejects(
|
|
491
|
+
promise: PromiseLike<unknown>,
|
|
492
|
+
expectedOrAssert: unknown,
|
|
493
|
+
): Promise<void> {
|
|
494
|
+
const rejected = await captureRejected(promise, assertRejects);
|
|
495
|
+
|
|
496
|
+
if (typeof expectedOrAssert === "function") {
|
|
497
|
+
const result = (expectedOrAssert as (reason: unknown) => unknown)(rejected);
|
|
498
|
+
assert(
|
|
499
|
+
result === undefined,
|
|
500
|
+
"Expected the rejection assertion to return undefined.",
|
|
501
|
+
{
|
|
502
|
+
actual: result,
|
|
503
|
+
expected: undefined,
|
|
504
|
+
operator: "strictEqual",
|
|
505
|
+
stackStartFn: assertRejects,
|
|
506
|
+
},
|
|
507
|
+
);
|
|
508
|
+
return;
|
|
509
|
+
}
|
|
227
510
|
|
|
228
|
-
const expectedValue = comparison[0];
|
|
229
|
-
const eq: Eq<any> = comparison[1] ?? eqData;
|
|
230
511
|
assert(
|
|
231
|
-
|
|
232
|
-
"Expected the
|
|
512
|
+
eqUnknown(rejected, expectedOrAssert),
|
|
513
|
+
"Expected the rejection reason to equal the expected value.",
|
|
514
|
+
{
|
|
515
|
+
actual: rejected,
|
|
516
|
+
expected: expectedOrAssert,
|
|
517
|
+
operator: "eqData",
|
|
518
|
+
diff: "full",
|
|
519
|
+
stackStartFn: assertRejects,
|
|
520
|
+
},
|
|
233
521
|
);
|
|
234
522
|
}
|
|
235
523
|
|
|
236
524
|
/**
|
|
237
|
-
* Asserts that a
|
|
238
|
-
* and narrows the Result.
|
|
525
|
+
* Asserts that a promise rejects with the same value using `Object.is`.
|
|
239
526
|
*
|
|
240
|
-
*
|
|
241
|
-
*
|
|
242
|
-
* inferred independently, so an unbranded literal can compare a branded
|
|
243
|
-
* primitive or collection. Pass a custom {@link Eq} when either value is outside
|
|
244
|
-
* {@link Data} or needs domain-specific equality.
|
|
527
|
+
* Use this to verify that a rejection reason was propagated unchanged. For
|
|
528
|
+
* value comparisons, use {@link assertRejects}.
|
|
245
529
|
*
|
|
246
530
|
* ### Example
|
|
247
531
|
*
|
|
248
532
|
* ```ts
|
|
249
|
-
* import {
|
|
250
|
-
* assertEqual,
|
|
251
|
-
* assertErr,
|
|
252
|
-
* assertType,
|
|
253
|
-
* err,
|
|
254
|
-
* type Err,
|
|
255
|
-
* type Result,
|
|
256
|
-
* type Typed,
|
|
257
|
-
* } from "@evolu/common";
|
|
533
|
+
* import { assertRejectsSame } from "@evolu/common";
|
|
258
534
|
*
|
|
259
|
-
*
|
|
260
|
-
*
|
|
261
|
-
*
|
|
535
|
+
* const expected = new Error("Unavailable.");
|
|
536
|
+
* await assertRejectsSame(Promise.reject(expected), expected);
|
|
537
|
+
* ```
|
|
262
538
|
*
|
|
263
|
-
*
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
539
|
+
* @group Assertions
|
|
540
|
+
*/
|
|
541
|
+
export const assertRejectsSame = async (
|
|
542
|
+
promise: PromiseLike<unknown>,
|
|
543
|
+
expected: unknown,
|
|
544
|
+
): Promise<void> => {
|
|
545
|
+
const rejected = await captureRejected(promise, assertRejectsSame);
|
|
546
|
+
assert(
|
|
547
|
+
eqSameValue(rejected, expected),
|
|
548
|
+
"Expected the rejection reason to be the same as the expected value.",
|
|
549
|
+
{
|
|
550
|
+
actual: rejected,
|
|
551
|
+
expected,
|
|
552
|
+
operator: "strictEqual",
|
|
553
|
+
stackStartFn: assertRejectsSame,
|
|
554
|
+
},
|
|
555
|
+
);
|
|
556
|
+
};
|
|
557
|
+
|
|
558
|
+
/**
|
|
559
|
+
* Asserts that a promise rejects with an instance of a constructor.
|
|
267
560
|
*
|
|
268
|
-
*
|
|
269
|
-
*
|
|
270
|
-
*
|
|
561
|
+
* Returns the narrowed instance so additional properties can be asserted.
|
|
562
|
+
*
|
|
563
|
+
* ### Example
|
|
564
|
+
*
|
|
565
|
+
* ```ts
|
|
566
|
+
* import { assertEqual, assertRejectsInstanceOf } from "@evolu/common";
|
|
567
|
+
*
|
|
568
|
+
* const error = await assertRejectsInstanceOf(
|
|
569
|
+
* Promise.reject(new TypeError("Unavailable.")),
|
|
570
|
+
* TypeError,
|
|
571
|
+
* );
|
|
572
|
+
* assertEqual(error.message, "Unavailable.");
|
|
271
573
|
* ```
|
|
574
|
+
*
|
|
575
|
+
* @group Assertions
|
|
272
576
|
*/
|
|
273
|
-
export
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
):
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
export function assertErr(
|
|
297
|
-
result: AnyResult,
|
|
298
|
-
...comparison: [] | [expectedError: unknown, eq?: Eq<any>]
|
|
299
|
-
): asserts result is Err<unknown> {
|
|
300
|
-
assert(!result.ok, "Expected an Err result.");
|
|
301
|
-
if (comparison.length === 0) return;
|
|
577
|
+
export const assertRejectsInstanceOf = async <
|
|
578
|
+
T extends {
|
|
579
|
+
readonly name: string;
|
|
580
|
+
} & (abstract new (...args: Array<never>) => unknown),
|
|
581
|
+
>(
|
|
582
|
+
promise: PromiseLike<unknown>,
|
|
583
|
+
constructor: T,
|
|
584
|
+
): Promise<InstanceType<T>> => {
|
|
585
|
+
const rejected = await captureRejected(promise, assertRejectsInstanceOf);
|
|
586
|
+
assert(
|
|
587
|
+
rejected instanceof constructor,
|
|
588
|
+
constructor.name === ""
|
|
589
|
+
? "Expected the rejection reason to be an instance of the provided constructor."
|
|
590
|
+
: `Expected the rejection reason to be an instance of ${constructor.name}.`,
|
|
591
|
+
{
|
|
592
|
+
actual: rejected,
|
|
593
|
+
expected: constructor,
|
|
594
|
+
operator: "instanceof",
|
|
595
|
+
stackStartFn: assertRejectsInstanceOf,
|
|
596
|
+
},
|
|
597
|
+
);
|
|
598
|
+
return rejected as InstanceType<T>;
|
|
599
|
+
};
|
|
302
600
|
|
|
303
|
-
|
|
304
|
-
|
|
601
|
+
const captureRejected = async (
|
|
602
|
+
promise: PromiseLike<unknown>,
|
|
603
|
+
stackStartFn: (...args: Array<never>) => unknown,
|
|
604
|
+
): Promise<unknown> => {
|
|
605
|
+
let value: unknown;
|
|
606
|
+
|
|
607
|
+
try {
|
|
608
|
+
value = await promise;
|
|
609
|
+
} catch (error) {
|
|
610
|
+
return error;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
assert(false, "Expected promise to reject.", {
|
|
614
|
+
actual: value,
|
|
615
|
+
expected: "rejection",
|
|
616
|
+
operator: "rejects",
|
|
617
|
+
stackStartFn,
|
|
618
|
+
});
|
|
619
|
+
};
|
|
620
|
+
|
|
621
|
+
/**
|
|
622
|
+
* Asserts that a value is an instance of a constructor and narrows it.
|
|
623
|
+
*
|
|
624
|
+
* ### Example
|
|
625
|
+
*
|
|
626
|
+
* ```ts
|
|
627
|
+
* import { assertInstanceOf, assertType } from "@evolu/common";
|
|
628
|
+
*
|
|
629
|
+
* const value: unknown = new TypeError("Invalid value.");
|
|
630
|
+
* assertInstanceOf(value, TypeError);
|
|
631
|
+
* assertType<typeof value, TypeError>();
|
|
632
|
+
* ```
|
|
633
|
+
*
|
|
634
|
+
* @group Assertions
|
|
635
|
+
*/
|
|
636
|
+
export const assertInstanceOf: <
|
|
637
|
+
Constructor extends {
|
|
638
|
+
readonly name: string;
|
|
639
|
+
} & (abstract new (...args: Array<never>) => unknown),
|
|
640
|
+
>(
|
|
641
|
+
value: unknown,
|
|
642
|
+
constructor: Constructor,
|
|
643
|
+
) => asserts value is InstanceType<Constructor> = (value, constructor) => {
|
|
305
644
|
assert(
|
|
306
|
-
|
|
307
|
-
|
|
645
|
+
value instanceof constructor,
|
|
646
|
+
constructor.name === ""
|
|
647
|
+
? "Expected an instance of the provided constructor."
|
|
648
|
+
: `Expected an instance of ${constructor.name}.`,
|
|
649
|
+
{
|
|
650
|
+
actual: value,
|
|
651
|
+
expected: constructor,
|
|
652
|
+
operator: "instanceof",
|
|
653
|
+
stackStartFn: assertInstanceOf,
|
|
654
|
+
},
|
|
308
655
|
);
|
|
309
|
-
}
|
|
656
|
+
};
|
|
310
657
|
|
|
311
658
|
/**
|
|
312
659
|
* Asserts that a value is non-nullable.
|
|
313
660
|
*
|
|
314
661
|
* Following TypeScript's {@link NonNullable}, non-nullable here means neither
|
|
315
|
-
* null nor undefined.
|
|
316
|
-
*
|
|
662
|
+
* null nor undefined.
|
|
663
|
+
*
|
|
664
|
+
* @group Assertions
|
|
317
665
|
*/
|
|
318
666
|
export const assertNonNullable: <T>(
|
|
319
667
|
value: T,
|
|
@@ -322,22 +670,24 @@ export const assertNonNullable: <T>(
|
|
|
322
670
|
value,
|
|
323
671
|
message = "Expected value to be non-nullable.",
|
|
324
672
|
) => {
|
|
325
|
-
assert(value != null, message
|
|
673
|
+
assert(value != null, message, {
|
|
674
|
+
actual: value,
|
|
675
|
+
expected: null,
|
|
676
|
+
operator: "!=",
|
|
677
|
+
stackStartFn: assertNonNullable,
|
|
678
|
+
});
|
|
326
679
|
};
|
|
327
680
|
|
|
328
681
|
/**
|
|
329
682
|
* Asserts that a value is not null while preserving undefined.
|
|
330
683
|
*
|
|
331
|
-
* Use this when a value is logically guaranteed not to be null but TypeScript
|
|
332
|
-
* cannot prove it.
|
|
333
|
-
*
|
|
334
684
|
* ### Example
|
|
335
685
|
*
|
|
336
686
|
* ```ts
|
|
337
687
|
* import {
|
|
338
|
-
* assert,
|
|
339
688
|
* assertEqual,
|
|
340
689
|
* assertErr,
|
|
690
|
+
* assertInstanceOf,
|
|
341
691
|
* assertNotNull,
|
|
342
692
|
* assertType,
|
|
343
693
|
* trySync,
|
|
@@ -345,13 +695,17 @@ export const assertNonNullable: <T>(
|
|
|
345
695
|
*
|
|
346
696
|
* const value = undefined as string | null | undefined;
|
|
347
697
|
* assertNotNull(value);
|
|
348
|
-
*
|
|
698
|
+
*
|
|
699
|
+
* assertType<typeof value, string | undefined>();
|
|
349
700
|
* assertEqual(value, undefined);
|
|
701
|
+
*
|
|
350
702
|
* const result = trySync(() => assertNotNull(null));
|
|
351
703
|
* assertErr(result);
|
|
352
|
-
*
|
|
704
|
+
* assertInstanceOf(result.error, Error);
|
|
353
705
|
* assertEqual(result.error.message, "Expected value not to be null.");
|
|
354
706
|
* ```
|
|
707
|
+
*
|
|
708
|
+
* @group Assertions
|
|
355
709
|
*/
|
|
356
710
|
export const assertNotNull: <T>(
|
|
357
711
|
value: T,
|
|
@@ -360,22 +714,24 @@ export const assertNotNull: <T>(
|
|
|
360
714
|
value,
|
|
361
715
|
message = "Expected value not to be null.",
|
|
362
716
|
) => {
|
|
363
|
-
assert(value !== null, message
|
|
717
|
+
assert(value !== null, message, {
|
|
718
|
+
actual: value,
|
|
719
|
+
expected: null,
|
|
720
|
+
operator: "notStrictEqual",
|
|
721
|
+
stackStartFn: assertNotNull,
|
|
722
|
+
});
|
|
364
723
|
};
|
|
365
724
|
|
|
366
725
|
/**
|
|
367
726
|
* Asserts that a value is not undefined while preserving null.
|
|
368
727
|
*
|
|
369
|
-
* Use this when a value is logically guaranteed not to be undefined but
|
|
370
|
-
* TypeScript cannot prove it.
|
|
371
|
-
*
|
|
372
728
|
* ### Example
|
|
373
729
|
*
|
|
374
730
|
* ```ts
|
|
375
731
|
* import {
|
|
376
|
-
* assert,
|
|
377
732
|
* assertEqual,
|
|
378
733
|
* assertErr,
|
|
734
|
+
* assertInstanceOf,
|
|
379
735
|
* assertNotUndefined,
|
|
380
736
|
* assertType,
|
|
381
737
|
* trySync,
|
|
@@ -383,13 +739,15 @@ export const assertNotNull: <T>(
|
|
|
383
739
|
*
|
|
384
740
|
* const value = null as string | null | undefined;
|
|
385
741
|
* assertNotUndefined(value);
|
|
386
|
-
* assertType<string | null
|
|
742
|
+
* assertType<typeof value, string | null>();
|
|
387
743
|
* assertEqual(value, null);
|
|
388
744
|
* const result = trySync(() => assertNotUndefined(undefined));
|
|
389
745
|
* assertErr(result);
|
|
390
|
-
*
|
|
746
|
+
* assertInstanceOf(result.error, Error);
|
|
391
747
|
* assertEqual(result.error.message, "Expected value not to be undefined.");
|
|
392
748
|
* ```
|
|
749
|
+
*
|
|
750
|
+
* @group Assertions
|
|
393
751
|
*/
|
|
394
752
|
export const assertNotUndefined: <T>(
|
|
395
753
|
value: T,
|
|
@@ -398,14 +756,53 @@ export const assertNotUndefined: <T>(
|
|
|
398
756
|
value,
|
|
399
757
|
message = "Expected value not to be undefined.",
|
|
400
758
|
) => {
|
|
401
|
-
assert(value !== undefined, message
|
|
759
|
+
assert(value !== undefined, message, {
|
|
760
|
+
actual: value,
|
|
761
|
+
expected: undefined,
|
|
762
|
+
operator: "notStrictEqual",
|
|
763
|
+
stackStartFn: assertNotUndefined,
|
|
764
|
+
});
|
|
402
765
|
};
|
|
403
766
|
|
|
404
767
|
/**
|
|
405
|
-
* Asserts that
|
|
768
|
+
* Asserts that a value has the expected length and narrows its length.
|
|
406
769
|
*
|
|
407
|
-
*
|
|
408
|
-
*
|
|
770
|
+
* ### Example
|
|
771
|
+
*
|
|
772
|
+
* ```ts
|
|
773
|
+
* import { assertLength, assertType } from "@evolu/common";
|
|
774
|
+
*
|
|
775
|
+
* const values: ReadonlyArray<string> = ["Ada", "Grace"];
|
|
776
|
+
* assertLength(values, 2);
|
|
777
|
+
* assertType<typeof values.length, 2>();
|
|
778
|
+
* ```
|
|
779
|
+
*
|
|
780
|
+
* @group Assertions
|
|
781
|
+
*/
|
|
782
|
+
export const assertLength: <
|
|
783
|
+
Value extends ValueWithLength,
|
|
784
|
+
const Length extends number,
|
|
785
|
+
>(
|
|
786
|
+
value: Value,
|
|
787
|
+
expectedLength: Length,
|
|
788
|
+
) => asserts value is Value & { readonly length: Length } = (
|
|
789
|
+
value,
|
|
790
|
+
expectedLength,
|
|
791
|
+
) => {
|
|
792
|
+
assert(
|
|
793
|
+
eqSameValue(value.length, expectedLength),
|
|
794
|
+
`Expected value to have length ${expectedLength}.`,
|
|
795
|
+
{
|
|
796
|
+
actual: value.length,
|
|
797
|
+
expected: expectedLength,
|
|
798
|
+
operator: "strictEqual",
|
|
799
|
+
stackStartFn: assertLength,
|
|
800
|
+
},
|
|
801
|
+
);
|
|
802
|
+
};
|
|
803
|
+
|
|
804
|
+
/**
|
|
805
|
+
* Asserts that an array is non-empty.
|
|
409
806
|
*
|
|
410
807
|
* ### Example
|
|
411
808
|
*
|
|
@@ -421,11 +818,13 @@ export const assertNotUndefined: <T>(
|
|
|
421
818
|
*
|
|
422
819
|
* const values = [1, 2, 3];
|
|
423
820
|
* assertNonEmptyArray(values);
|
|
424
|
-
* assertType<NonEmptyArray<number
|
|
821
|
+
* assertType<typeof values, NonEmptyArray<number>>();
|
|
425
822
|
* assertEqual(values[0], 1);
|
|
426
823
|
* const result = trySync(() => assertNonEmptyArray([]));
|
|
427
824
|
* assertErr(result);
|
|
428
825
|
* ```
|
|
826
|
+
*
|
|
827
|
+
* @group Assertions
|
|
429
828
|
*/
|
|
430
829
|
export const assertNonEmptyArray: <T>(
|
|
431
830
|
arr: Array<T>,
|
|
@@ -434,15 +833,17 @@ export const assertNonEmptyArray: <T>(
|
|
|
434
833
|
arr,
|
|
435
834
|
message = "Expected a non-empty array.",
|
|
436
835
|
) => {
|
|
437
|
-
assert(arr.length > 0, message
|
|
836
|
+
assert(arr.length > 0, message, {
|
|
837
|
+
actual: arr.length,
|
|
838
|
+
expected: 0,
|
|
839
|
+
operator: ">",
|
|
840
|
+
stackStartFn: assertNonEmptyArray,
|
|
841
|
+
});
|
|
438
842
|
};
|
|
439
843
|
|
|
440
844
|
/**
|
|
441
845
|
* Asserts that a readonly array is non-empty.
|
|
442
846
|
*
|
|
443
|
-
* Use this when a readonly array is logically guaranteed to be non-empty but
|
|
444
|
-
* TypeScript cannot prove it.
|
|
445
|
-
*
|
|
446
847
|
* ### Example
|
|
447
848
|
*
|
|
448
849
|
* ```ts
|
|
@@ -457,11 +858,13 @@ export const assertNonEmptyArray: <T>(
|
|
|
457
858
|
*
|
|
458
859
|
* const values: ReadonlyArray<number> = [1, 2, 3];
|
|
459
860
|
* assertNonEmptyReadonlyArray(values);
|
|
460
|
-
* assertType<NonEmptyReadonlyArray<number
|
|
861
|
+
* assertType<typeof values, NonEmptyReadonlyArray<number>>();
|
|
461
862
|
* assertEqual(values[0], 1);
|
|
462
863
|
* const result = trySync(() => assertNonEmptyReadonlyArray([]));
|
|
463
864
|
* assertErr(result);
|
|
464
865
|
* ```
|
|
866
|
+
*
|
|
867
|
+
* @group Assertions
|
|
465
868
|
*/
|
|
466
869
|
export const assertNonEmptyReadonlyArray: <T>(
|
|
467
870
|
arr: ReadonlyArray<T>,
|
|
@@ -470,7 +873,12 @@ export const assertNonEmptyReadonlyArray: <T>(
|
|
|
470
873
|
arr,
|
|
471
874
|
message = "Expected a non-empty readonly array.",
|
|
472
875
|
) => {
|
|
473
|
-
assert(arr.length > 0, message
|
|
876
|
+
assert(arr.length > 0, message, {
|
|
877
|
+
actual: arr.length,
|
|
878
|
+
expected: 0,
|
|
879
|
+
operator: ">",
|
|
880
|
+
stackStartFn: assertNonEmptyReadonlyArray,
|
|
881
|
+
});
|
|
474
882
|
};
|
|
475
883
|
|
|
476
884
|
/**
|
|
@@ -482,30 +890,366 @@ export const assertNonEmptyReadonlyArray: <T>(
|
|
|
482
890
|
*
|
|
483
891
|
* ```ts
|
|
484
892
|
* import {
|
|
485
|
-
* assert,
|
|
486
893
|
* assertEqual,
|
|
487
894
|
* assertErr,
|
|
895
|
+
* assertInstanceOf,
|
|
488
896
|
* assertNotDisposed,
|
|
489
897
|
* trySync,
|
|
490
898
|
* } from "@evolu/common";
|
|
491
899
|
*
|
|
492
|
-
* await using disposer = new
|
|
900
|
+
* await using disposer = new AsyncDisposableStack();
|
|
493
901
|
* assertNotDisposed(disposer);
|
|
494
902
|
* await disposer.disposeAsync();
|
|
495
903
|
* const result = trySync(() => assertNotDisposed(disposer));
|
|
496
904
|
* assertErr(result);
|
|
497
|
-
*
|
|
905
|
+
* assertInstanceOf(result.error, Error);
|
|
498
906
|
* assertEqual(result.error.message, "Cannot use a disposed object.");
|
|
499
907
|
* ```
|
|
500
908
|
*
|
|
501
909
|
* This is the JavaScript equivalent of the .NET `ObjectDisposedException`
|
|
502
910
|
* pattern: once a helper has been disposed, calling its synchronous methods is
|
|
503
|
-
* a
|
|
504
|
-
*
|
|
911
|
+
* a bug and should throw immediately instead of continuing with invalid state.
|
|
912
|
+
*
|
|
913
|
+
* @group Assertions
|
|
505
914
|
*/
|
|
506
915
|
export const assertNotDisposed = (
|
|
507
916
|
value:
|
|
508
917
|
DisposableStack | AsyncDisposableStack | { readonly disposed: boolean },
|
|
509
918
|
): void => {
|
|
510
|
-
assert(!value.disposed, "Cannot use a disposed object."
|
|
919
|
+
assert(!value.disposed, "Cannot use a disposed object.", {
|
|
920
|
+
actual: value.disposed,
|
|
921
|
+
expected: false,
|
|
922
|
+
operator: "==",
|
|
923
|
+
stackStartFn: assertNotDisposed,
|
|
924
|
+
});
|
|
925
|
+
};
|
|
926
|
+
|
|
927
|
+
/**
|
|
928
|
+
* Asserts that two values are the same using `Object.is`.
|
|
929
|
+
*
|
|
930
|
+
* Use this when exact sameness is the contract, such as asserting reference
|
|
931
|
+
* identity or narrowing the actual value to the expected value's type, as shown
|
|
932
|
+
* below. For value comparisons, use {@link assertEqual}.
|
|
933
|
+
*
|
|
934
|
+
* Uses the same equality semantics as `assert.strictEqual` from
|
|
935
|
+
* `node:assert/strict`, but is platform-agnostic. See {@link eqSameValue} for
|
|
936
|
+
* the equality semantics.
|
|
937
|
+
*
|
|
938
|
+
* ### Example
|
|
939
|
+
*
|
|
940
|
+
* ```ts
|
|
941
|
+
* import { assertSame, assertType } from "@evolu/common";
|
|
942
|
+
*
|
|
943
|
+
* interface User {
|
|
944
|
+
* readonly name: string;
|
|
945
|
+
* }
|
|
946
|
+
*
|
|
947
|
+
* const user: User = { name: "Ada" };
|
|
948
|
+
* const value: unknown = user;
|
|
949
|
+
*
|
|
950
|
+
* assertSame(value, user);
|
|
951
|
+
* // `assertSame` narrows `value` from `unknown` to `User`.
|
|
952
|
+
* assertType<typeof value, User>();
|
|
953
|
+
* ```
|
|
954
|
+
*
|
|
955
|
+
* @group Assertions
|
|
956
|
+
*/
|
|
957
|
+
export const assertSame: <Expected>(
|
|
958
|
+
actual: unknown,
|
|
959
|
+
expected: Expected,
|
|
960
|
+
) => asserts actual is Expected = (actual, expected) => {
|
|
961
|
+
// `Object.is` implements ECMAScript SameValue, whereas strict equality means
|
|
962
|
+
// `===`. Keep the platform-agnostic "same" wording instead of reproducing
|
|
963
|
+
// Node.js's value-dependent messages, which sometimes say "strictly equal".
|
|
964
|
+
assert(eqSameValue(actual, expected), "Expected values to be the same.", {
|
|
965
|
+
actual,
|
|
966
|
+
expected,
|
|
967
|
+
operator: "strictEqual",
|
|
968
|
+
stackStartFn: assertSame,
|
|
969
|
+
});
|
|
511
970
|
};
|
|
971
|
+
|
|
972
|
+
/**
|
|
973
|
+
* Asserts that two values are not the same using `Object.is`.
|
|
974
|
+
*
|
|
975
|
+
* The opposite of {@link assertSame}.
|
|
976
|
+
*
|
|
977
|
+
* ### Example
|
|
978
|
+
*
|
|
979
|
+
* ```ts
|
|
980
|
+
* import { assertNotSame } from "@evolu/common";
|
|
981
|
+
*
|
|
982
|
+
* const first = { name: "Ada" };
|
|
983
|
+
* const second = { name: "Ada" };
|
|
984
|
+
*
|
|
985
|
+
* assertNotSame(first, second);
|
|
986
|
+
* assertNotSame(0, -0);
|
|
987
|
+
* ```
|
|
988
|
+
*
|
|
989
|
+
* @group Assertions
|
|
990
|
+
*/
|
|
991
|
+
export const assertNotSame = (actual: unknown, expected: unknown): void => {
|
|
992
|
+
// `Object.is` implements ECMAScript SameValue, whereas strict equality means
|
|
993
|
+
// `===`. Keep the platform-agnostic "same" wording instead of reproducing
|
|
994
|
+
// Node.js's value-dependent messages, which sometimes say "strictly equal".
|
|
995
|
+
assert(
|
|
996
|
+
!eqSameValue(actual, expected),
|
|
997
|
+
"Expected values not to be the same.",
|
|
998
|
+
{
|
|
999
|
+
actual,
|
|
1000
|
+
expected,
|
|
1001
|
+
operator: "notStrictEqual",
|
|
1002
|
+
stackStartFn: assertNotSame,
|
|
1003
|
+
},
|
|
1004
|
+
);
|
|
1005
|
+
};
|
|
1006
|
+
|
|
1007
|
+
// oxlint-disable eslint/func-style -- Function declarations make TypeDoc categorize these APIs as functions.
|
|
1008
|
+
|
|
1009
|
+
/**
|
|
1010
|
+
* Asserts that two values are equal.
|
|
1011
|
+
*
|
|
1012
|
+
* Use this for value comparisons of both primitive and deeply structured
|
|
1013
|
+
* values. Use {@link assertSame} when exact sameness or narrowing is required.
|
|
1014
|
+
*
|
|
1015
|
+
* Like `assert.deepEqual` from `node:assert/strict`, it performs deep
|
|
1016
|
+
* structural comparison, but uses deliberately smaller, platform-agnostic
|
|
1017
|
+
* equality logic based on {@link eqData}. More complex comparison logic is
|
|
1018
|
+
* difficult to reason about and rarely needed. Other values are opaque and
|
|
1019
|
+
* compare equal only by identity. If broader comparison semantics are needed in
|
|
1020
|
+
* Node.js, use the native assertion instead.
|
|
1021
|
+
*
|
|
1022
|
+
* ### Example
|
|
1023
|
+
*
|
|
1024
|
+
* ```ts
|
|
1025
|
+
* import { assertEqual } from "@evolu/common";
|
|
1026
|
+
*
|
|
1027
|
+
* assertEqual(21 * 2, 42);
|
|
1028
|
+
*
|
|
1029
|
+
* const actual: unknown = new Map([
|
|
1030
|
+
* ["roles", new Set(["admin", "author"])],
|
|
1031
|
+
* ]);
|
|
1032
|
+
* const expected = new Map([["roles", new Set(["author", "admin"])]]);
|
|
1033
|
+
*
|
|
1034
|
+
* assertEqual(actual, expected);
|
|
1035
|
+
* ```
|
|
1036
|
+
*
|
|
1037
|
+
* @group Assertions
|
|
1038
|
+
*/
|
|
1039
|
+
export function assertEqual(actual: unknown, expected: unknown): void {
|
|
1040
|
+
assert(eqUnknown(actual, expected), "Expected values to be equal.", {
|
|
1041
|
+
actual,
|
|
1042
|
+
expected,
|
|
1043
|
+
operator: "eqData",
|
|
1044
|
+
diff: "full",
|
|
1045
|
+
stackStartFn: assertEqual,
|
|
1046
|
+
});
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
/**
|
|
1050
|
+
* Asserts that a `Uint8Array` contains the expected bytes.
|
|
1051
|
+
*
|
|
1052
|
+
* Uses {@link eqArrayNumber}, so the runtime representations may differ. For
|
|
1053
|
+
* example, a `Uint8Array` can be compared directly with a regular array.
|
|
1054
|
+
*
|
|
1055
|
+
* ### Example
|
|
1056
|
+
*
|
|
1057
|
+
* ```ts
|
|
1058
|
+
* import { assertEqualBytes } from "@evolu/common";
|
|
1059
|
+
*
|
|
1060
|
+
* const bytes = new Uint8Array([1, 5, 39, 254]);
|
|
1061
|
+
*
|
|
1062
|
+
* assertEqualBytes(bytes, [1, 5, 39, 254]);
|
|
1063
|
+
* ```
|
|
1064
|
+
*
|
|
1065
|
+
* @group Assertions
|
|
1066
|
+
*/
|
|
1067
|
+
export const assertEqualBytes = (
|
|
1068
|
+
actual: Uint8Array,
|
|
1069
|
+
expected: ArrayLike<number>,
|
|
1070
|
+
): void => {
|
|
1071
|
+
assert(eqArrayNumber(actual, expected), "Expected bytes to be equal.", {
|
|
1072
|
+
actual,
|
|
1073
|
+
expected,
|
|
1074
|
+
operator: "eqArrayNumber",
|
|
1075
|
+
diff: "full",
|
|
1076
|
+
stackStartFn: assertEqualBytes,
|
|
1077
|
+
});
|
|
1078
|
+
};
|
|
1079
|
+
|
|
1080
|
+
/**
|
|
1081
|
+
* Asserts that two values are not equal.
|
|
1082
|
+
*
|
|
1083
|
+
* The opposite of {@link assertEqual}.
|
|
1084
|
+
*
|
|
1085
|
+
* ### Example
|
|
1086
|
+
*
|
|
1087
|
+
* ```ts
|
|
1088
|
+
* import { assertNotEqual } from "@evolu/common";
|
|
1089
|
+
*
|
|
1090
|
+
* assertNotEqual({ name: "Ada" }, { name: "Grace" });
|
|
1091
|
+
* assertNotEqual([1, 2], [2, 1]);
|
|
1092
|
+
* ```
|
|
1093
|
+
*
|
|
1094
|
+
* @group Assertions
|
|
1095
|
+
*/
|
|
1096
|
+
export function assertNotEqual(actual: unknown, expected: unknown): void {
|
|
1097
|
+
assert(!eqUnknown(actual, expected), "Expected values not to be equal.", {
|
|
1098
|
+
actual,
|
|
1099
|
+
expected,
|
|
1100
|
+
operator: "notEqData",
|
|
1101
|
+
stackStartFn: assertNotEqual,
|
|
1102
|
+
});
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
// oxlint-enable eslint/func-style
|
|
1106
|
+
|
|
1107
|
+
// Assertion equality extends eqData to unknown values. The comparator's
|
|
1108
|
+
// SameValue fast path treats identical opaque values as equal, while its
|
|
1109
|
+
// unsupported branches make distinct opaque values unequal.
|
|
1110
|
+
const eqUnknown = eqData as Eq<unknown>;
|
|
1111
|
+
|
|
1112
|
+
/**
|
|
1113
|
+
* Asserts that a {@link Result} is an {@link Ok} and narrows it, optionally
|
|
1114
|
+
* comparing its value.
|
|
1115
|
+
*
|
|
1116
|
+
* When an expected value is provided, it is compared using {@link assertEqual}
|
|
1117
|
+
* semantics by default. Pass a custom {@link Eq} for different equality
|
|
1118
|
+
* semantics.
|
|
1119
|
+
*
|
|
1120
|
+
* ### Example
|
|
1121
|
+
*
|
|
1122
|
+
* ```ts
|
|
1123
|
+
* import {
|
|
1124
|
+
* assertOk,
|
|
1125
|
+
* assertType,
|
|
1126
|
+
* ok,
|
|
1127
|
+
* type Ok,
|
|
1128
|
+
* type Result,
|
|
1129
|
+
* type Typed,
|
|
1130
|
+
* } from "@evolu/common";
|
|
1131
|
+
*
|
|
1132
|
+
* interface User {
|
|
1133
|
+
* readonly id: string;
|
|
1134
|
+
* }
|
|
1135
|
+
*
|
|
1136
|
+
* interface UserNotFoundError extends Typed<"UserNotFound"> {}
|
|
1137
|
+
*
|
|
1138
|
+
* const result: Result<User, UserNotFoundError> = ok({ id: "user-1" });
|
|
1139
|
+
*
|
|
1140
|
+
* assertOk(result, { id: "user-1" });
|
|
1141
|
+
* assertType<typeof result, Ok<User>>();
|
|
1142
|
+
* ```
|
|
1143
|
+
*
|
|
1144
|
+
* @group Assertions
|
|
1145
|
+
*/
|
|
1146
|
+
export function assertOk<R extends Result<unknown, unknown>>(
|
|
1147
|
+
result: R,
|
|
1148
|
+
): asserts result is Extract<R, Ok<unknown>>;
|
|
1149
|
+
export function assertOk<R extends Result<unknown, unknown>>(
|
|
1150
|
+
result: R,
|
|
1151
|
+
expectedValue: InferOk<R>,
|
|
1152
|
+
eq: Eq<InferOk<R>>,
|
|
1153
|
+
): asserts result is Extract<R, Ok<unknown>>;
|
|
1154
|
+
export function assertOk<R extends Result<unknown, unknown>>(
|
|
1155
|
+
result: R,
|
|
1156
|
+
expectedValue: unknown,
|
|
1157
|
+
): asserts result is Extract<R, Ok<unknown>>;
|
|
1158
|
+
export function assertOk(
|
|
1159
|
+
result: AnyResult,
|
|
1160
|
+
...comparison: [] | [expectedValue: unknown, eq?: Eq<any>]
|
|
1161
|
+
): asserts result is Ok<unknown> {
|
|
1162
|
+
assert(result.ok, "Expected an Ok result.", {
|
|
1163
|
+
actual: result.ok,
|
|
1164
|
+
expected: true,
|
|
1165
|
+
operator: "strictEqual",
|
|
1166
|
+
stackStartFn: assertOk,
|
|
1167
|
+
});
|
|
1168
|
+
if (comparison.length === 0) return;
|
|
1169
|
+
|
|
1170
|
+
const expectedValue = comparison[0];
|
|
1171
|
+
assert(
|
|
1172
|
+
(comparison[1] ?? eqUnknown)(result.value, expectedValue),
|
|
1173
|
+
"Expected the value to equal the expected value.",
|
|
1174
|
+
{
|
|
1175
|
+
actual: result.value,
|
|
1176
|
+
expected: expectedValue,
|
|
1177
|
+
operator: comparison[1] === undefined ? "eqData" : "Eq",
|
|
1178
|
+
diff: comparison[1] === undefined ? "full" : undefined,
|
|
1179
|
+
stackStartFn: assertOk,
|
|
1180
|
+
},
|
|
1181
|
+
);
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1184
|
+
/**
|
|
1185
|
+
* Asserts that a {@link Result} is an {@link Err} and narrows it, optionally
|
|
1186
|
+
* comparing its error.
|
|
1187
|
+
*
|
|
1188
|
+
* When an expected error is provided, it is compared using {@link assertEqual}
|
|
1189
|
+
* semantics by default. Pass a custom {@link Eq} for different equality
|
|
1190
|
+
* semantics.
|
|
1191
|
+
*
|
|
1192
|
+
* ### Example
|
|
1193
|
+
*
|
|
1194
|
+
* ```ts
|
|
1195
|
+
* import {
|
|
1196
|
+
* assertErr,
|
|
1197
|
+
* assertType,
|
|
1198
|
+
* err,
|
|
1199
|
+
* type Err,
|
|
1200
|
+
* type Result,
|
|
1201
|
+
* type Typed,
|
|
1202
|
+
* } from "@evolu/common";
|
|
1203
|
+
*
|
|
1204
|
+
* interface UserNotFoundError extends Typed<"UserNotFound"> {
|
|
1205
|
+
* readonly id: string;
|
|
1206
|
+
* }
|
|
1207
|
+
*
|
|
1208
|
+
* const result: Result<string, UserNotFoundError> = err({
|
|
1209
|
+
* type: "UserNotFound",
|
|
1210
|
+
* id: "user-1",
|
|
1211
|
+
* });
|
|
1212
|
+
*
|
|
1213
|
+
* assertErr(result, { type: "UserNotFound", id: "user-1" });
|
|
1214
|
+
* assertType<typeof result, Err<UserNotFoundError>>();
|
|
1215
|
+
* ```
|
|
1216
|
+
*
|
|
1217
|
+
* @group Assertions
|
|
1218
|
+
*/
|
|
1219
|
+
export function assertErr<R extends Result<unknown, unknown>>(
|
|
1220
|
+
result: R,
|
|
1221
|
+
): asserts result is Extract<R, Err<unknown>>;
|
|
1222
|
+
export function assertErr<R extends Result<unknown, unknown>>(
|
|
1223
|
+
result: R,
|
|
1224
|
+
expectedError: InferErr<R>,
|
|
1225
|
+
eq: Eq<InferErr<R>>,
|
|
1226
|
+
): asserts result is Extract<R, Err<unknown>>;
|
|
1227
|
+
export function assertErr<R extends Result<unknown, unknown>>(
|
|
1228
|
+
result: R,
|
|
1229
|
+
expectedError: unknown,
|
|
1230
|
+
): asserts result is Extract<R, Err<unknown>>;
|
|
1231
|
+
export function assertErr(
|
|
1232
|
+
result: AnyResult,
|
|
1233
|
+
...comparison: [] | [expectedError: unknown, eq?: Eq<any>]
|
|
1234
|
+
): asserts result is Err<unknown> {
|
|
1235
|
+
assert(!result.ok, "Expected an Err result.", {
|
|
1236
|
+
actual: result.ok,
|
|
1237
|
+
expected: false,
|
|
1238
|
+
operator: "strictEqual",
|
|
1239
|
+
stackStartFn: assertErr,
|
|
1240
|
+
});
|
|
1241
|
+
if (comparison.length === 0) return;
|
|
1242
|
+
|
|
1243
|
+
const expectedError = comparison[0];
|
|
1244
|
+
assert(
|
|
1245
|
+
(comparison[1] ?? eqUnknown)(result.error, expectedError),
|
|
1246
|
+
"Expected the error to equal the expected error.",
|
|
1247
|
+
{
|
|
1248
|
+
actual: result.error,
|
|
1249
|
+
expected: expectedError,
|
|
1250
|
+
operator: comparison[1] === undefined ? "eqData" : "Eq",
|
|
1251
|
+
diff: comparison[1] === undefined ? "full" : undefined,
|
|
1252
|
+
stackStartFn: assertErr,
|
|
1253
|
+
},
|
|
1254
|
+
);
|
|
1255
|
+
}
|