@evolu/common 8.7.0 → 8.9.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 +498 -141
- package/dist/src/Assert.d.ts.map +1 -1
- package/dist/src/Assert.js +648 -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.d.ts +6 -0
- package/dist/src/Polyfills.d.ts.map +1 -1
- package/dist/src/Polyfills.js +10 -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 +1232 -0
- package/src/Assert.ts +1023 -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 +9 -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,668 @@ 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
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
255
|
+
export function assertConditionAfterMicrotasks(
|
|
256
|
+
condition: () => boolean,
|
|
257
|
+
expectedMicrotaskCount: number,
|
|
258
|
+
): Promise<void>;
|
|
259
|
+
export async function assertConditionAfterMicrotasks(
|
|
260
|
+
condition: () => boolean,
|
|
261
|
+
expectedMicrotaskCount: number,
|
|
262
|
+
stackStartFn: (
|
|
263
|
+
...args: Array<never>
|
|
264
|
+
) => unknown = assertConditionAfterMicrotasks,
|
|
265
|
+
): Promise<void> {
|
|
266
|
+
for (
|
|
267
|
+
let microtaskCount = 0;
|
|
268
|
+
microtaskCount < expectedMicrotaskCount;
|
|
269
|
+
microtaskCount++
|
|
270
|
+
) {
|
|
271
|
+
const actual = condition();
|
|
272
|
+
assert(
|
|
273
|
+
!actual,
|
|
274
|
+
`Expected condition to be false after ${microtaskCount} microtasks.`,
|
|
275
|
+
{
|
|
276
|
+
actual,
|
|
277
|
+
expected: false,
|
|
278
|
+
operator: "strictEqual",
|
|
279
|
+
stackStartFn,
|
|
280
|
+
},
|
|
281
|
+
);
|
|
282
|
+
await Promise.resolve();
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
const actual = condition();
|
|
286
|
+
assert(
|
|
287
|
+
actual,
|
|
288
|
+
`Expected condition to be true after exactly ${expectedMicrotaskCount} microtasks.`,
|
|
289
|
+
{
|
|
290
|
+
actual,
|
|
291
|
+
expected: true,
|
|
292
|
+
operator: "strictEqual",
|
|
293
|
+
stackStartFn,
|
|
294
|
+
},
|
|
295
|
+
);
|
|
296
|
+
}
|
|
125
297
|
|
|
126
298
|
/**
|
|
127
|
-
* Asserts that
|
|
299
|
+
* Asserts that a Promise continuation runs after exactly the specified number
|
|
300
|
+
* of microtasks.
|
|
128
301
|
*
|
|
129
|
-
*
|
|
130
|
-
*
|
|
302
|
+
* Both fulfillment and rejection count as running the continuation. Use this in
|
|
303
|
+
* tests that intentionally specify async scheduling behavior. Application code
|
|
304
|
+
* must not depend on exact microtask counts. Maintainers should review count
|
|
305
|
+
* changes because they indicate that an async pipeline changed.
|
|
131
306
|
*
|
|
132
307
|
* ### Example
|
|
133
308
|
*
|
|
134
309
|
* ```ts
|
|
135
|
-
* import {
|
|
310
|
+
* import { assertContinuationAfterMicrotasks } from "@evolu/common";
|
|
136
311
|
*
|
|
137
|
-
*
|
|
138
|
-
*
|
|
312
|
+
* await assertContinuationAfterMicrotasks(Promise.resolve("ready"), 1);
|
|
313
|
+
* ```
|
|
139
314
|
*
|
|
140
|
-
*
|
|
315
|
+
* @group Assertions
|
|
316
|
+
*/
|
|
317
|
+
export const assertContinuationAfterMicrotasks = async (
|
|
318
|
+
promise: Promise<unknown>,
|
|
319
|
+
expectedMicrotaskCount: number,
|
|
320
|
+
): Promise<void> => {
|
|
321
|
+
let continuationCalled = false;
|
|
322
|
+
|
|
323
|
+
const markContinuationCalled = (): void => {
|
|
324
|
+
continuationCalled = true;
|
|
325
|
+
};
|
|
326
|
+
|
|
327
|
+
void promise.then(markContinuationCalled, markContinuationCalled);
|
|
328
|
+
|
|
329
|
+
await assertConditionAfterMicrotasksWithStackStartFn(
|
|
330
|
+
() => continuationCalled,
|
|
331
|
+
expectedMicrotaskCount,
|
|
332
|
+
assertContinuationAfterMicrotasks,
|
|
333
|
+
);
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
const assertConditionAfterMicrotasksWithStackStartFn =
|
|
337
|
+
assertConditionAfterMicrotasks as (
|
|
338
|
+
condition: () => boolean,
|
|
339
|
+
expectedMicrotaskCount: number,
|
|
340
|
+
stackStartFn: (...args: Array<never>) => unknown,
|
|
341
|
+
) => Promise<void>;
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* Asserts that a function throws the expected value.
|
|
345
|
+
*
|
|
346
|
+
* Expected values use {@link assertEqual} semantics. To perform several or
|
|
347
|
+
* specialized assertions, pass an assertion function that receives the thrown
|
|
348
|
+
* value and returns nothing. Returning a predicate result fails. Use
|
|
349
|
+
* {@link assertThrowsSame} for SameValue or reference identity and
|
|
350
|
+
* {@link assertThrowsInstanceOf} for a runtime type.
|
|
351
|
+
*
|
|
352
|
+
* Because JavaScript permits throwing functions, a function second argument is
|
|
353
|
+
* always treated as an assertion function. Use `assertThrowsSame` to assert
|
|
354
|
+
* that a particular function was thrown.
|
|
355
|
+
*
|
|
356
|
+
* ### Example
|
|
357
|
+
*
|
|
358
|
+
* ```ts
|
|
359
|
+
* import { assertThrows } from "@evolu/common";
|
|
360
|
+
*
|
|
361
|
+
* assertThrows(
|
|
362
|
+
* () => {
|
|
363
|
+
* // oxlint-disable-next-line eslint/no-throw-literal, typescript/only-throw-error -- Exercise arbitrary thrown values.
|
|
364
|
+
* throw { type: "ExpectedFailure", value: 42 };
|
|
365
|
+
* },
|
|
366
|
+
* { type: "ExpectedFailure", value: 42 },
|
|
367
|
+
* );
|
|
141
368
|
* ```
|
|
369
|
+
*
|
|
370
|
+
* @group Assertions
|
|
142
371
|
*/
|
|
143
|
-
export function
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
...dataError: AssertEqualError<Actual | Expected>
|
|
372
|
+
export function assertThrows(
|
|
373
|
+
run: () => unknown,
|
|
374
|
+
assertThrown: (thrown: unknown) => void,
|
|
147
375
|
): void;
|
|
148
|
-
export function
|
|
149
|
-
|
|
376
|
+
export function assertThrows(run: () => unknown, expected: unknown): void;
|
|
377
|
+
export function assertThrows(
|
|
378
|
+
run: () => unknown,
|
|
379
|
+
expectedOrAssert: unknown,
|
|
380
|
+
): void {
|
|
381
|
+
const thrown = captureThrown(run, assertThrows);
|
|
382
|
+
|
|
383
|
+
if (typeof expectedOrAssert === "function") {
|
|
384
|
+
const result = (expectedOrAssert as (thrown: unknown) => unknown)(thrown);
|
|
385
|
+
assert(
|
|
386
|
+
result === undefined,
|
|
387
|
+
"Expected the thrown value assertion to return undefined.",
|
|
388
|
+
{
|
|
389
|
+
actual: result,
|
|
390
|
+
expected: undefined,
|
|
391
|
+
operator: "strictEqual",
|
|
392
|
+
stackStartFn: assertThrows,
|
|
393
|
+
},
|
|
394
|
+
);
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
assert(
|
|
399
|
+
eqUnknown(thrown, expectedOrAssert),
|
|
400
|
+
"Expected the thrown value to equal the expected value.",
|
|
401
|
+
{
|
|
402
|
+
actual: thrown,
|
|
403
|
+
expected: expectedOrAssert,
|
|
404
|
+
operator: "eqData",
|
|
405
|
+
diff: "full",
|
|
406
|
+
stackStartFn: assertThrows,
|
|
407
|
+
},
|
|
408
|
+
);
|
|
150
409
|
}
|
|
151
410
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
411
|
+
/**
|
|
412
|
+
* Asserts that a function throws the same value using `Object.is`.
|
|
413
|
+
*
|
|
414
|
+
* Use this to verify that a thrown value was propagated unchanged. For value
|
|
415
|
+
* comparisons, use {@link assertThrows}.
|
|
416
|
+
*
|
|
417
|
+
* ### Example
|
|
418
|
+
*
|
|
419
|
+
* ```ts
|
|
420
|
+
* import { assertThrowsSame } from "@evolu/common";
|
|
421
|
+
*
|
|
422
|
+
* const expected = new Error("Unavailable.");
|
|
423
|
+
* assertThrowsSame(() => {
|
|
424
|
+
* throw expected;
|
|
425
|
+
* }, expected);
|
|
426
|
+
* ```
|
|
427
|
+
*
|
|
428
|
+
* @group Assertions
|
|
429
|
+
*/
|
|
430
|
+
export const assertThrowsSame = (
|
|
431
|
+
run: () => unknown,
|
|
432
|
+
expected: unknown,
|
|
433
|
+
): void => {
|
|
434
|
+
const thrown = captureThrown(run, assertThrowsSame);
|
|
435
|
+
assert(
|
|
436
|
+
eqSameValue(thrown, expected),
|
|
437
|
+
"Expected the thrown value to be the same as the expected value.",
|
|
438
|
+
{
|
|
439
|
+
actual: thrown,
|
|
440
|
+
expected,
|
|
441
|
+
operator: "strictEqual",
|
|
442
|
+
stackStartFn: assertThrowsSame,
|
|
443
|
+
},
|
|
444
|
+
);
|
|
445
|
+
};
|
|
161
446
|
|
|
162
447
|
/**
|
|
163
|
-
* Asserts that a
|
|
164
|
-
* and narrows the Result.
|
|
448
|
+
* Asserts that a function throws an instance of a constructor.
|
|
165
449
|
*
|
|
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.
|
|
450
|
+
* Returns the narrowed instance so additional properties can be asserted
|
|
451
|
+
* without running the function again.
|
|
171
452
|
*
|
|
172
453
|
* ### Example
|
|
173
454
|
*
|
|
174
455
|
* ```ts
|
|
175
|
-
* import {
|
|
176
|
-
* assertEqual,
|
|
177
|
-
* assertOk,
|
|
178
|
-
* assertType,
|
|
179
|
-
* ok,
|
|
180
|
-
* type Ok,
|
|
181
|
-
* type Result,
|
|
182
|
-
* type Typed,
|
|
183
|
-
* } from "@evolu/common";
|
|
456
|
+
* import { assertEqual, assertThrowsInstanceOf } from "@evolu/common";
|
|
184
457
|
*
|
|
185
|
-
*
|
|
186
|
-
*
|
|
187
|
-
* }
|
|
458
|
+
* const error = assertThrowsInstanceOf(() => {
|
|
459
|
+
* throw new TypeError("Expected a string.");
|
|
460
|
+
* }, TypeError);
|
|
461
|
+
* assertEqual(error.message, "Expected a string.");
|
|
462
|
+
* ```
|
|
188
463
|
*
|
|
189
|
-
*
|
|
464
|
+
* @group Assertions
|
|
465
|
+
*/
|
|
466
|
+
export const assertThrowsInstanceOf = <
|
|
467
|
+
T extends {
|
|
468
|
+
readonly name: string;
|
|
469
|
+
} & (abstract new (...args: Array<never>) => unknown),
|
|
470
|
+
>(
|
|
471
|
+
run: () => unknown,
|
|
472
|
+
constructor: T,
|
|
473
|
+
): InstanceType<T> => {
|
|
474
|
+
const thrown = captureThrown(run, assertThrowsInstanceOf);
|
|
475
|
+
assert(
|
|
476
|
+
thrown instanceof constructor,
|
|
477
|
+
constructor.name === ""
|
|
478
|
+
? "Expected the thrown value to be an instance of the provided constructor."
|
|
479
|
+
: `Expected the thrown value to be an instance of ${constructor.name}.`,
|
|
480
|
+
{
|
|
481
|
+
actual: thrown,
|
|
482
|
+
expected: constructor,
|
|
483
|
+
operator: "instanceof",
|
|
484
|
+
stackStartFn: assertThrowsInstanceOf,
|
|
485
|
+
},
|
|
486
|
+
);
|
|
487
|
+
return thrown as InstanceType<T>;
|
|
488
|
+
};
|
|
489
|
+
|
|
490
|
+
const captureThrown = (
|
|
491
|
+
run: () => unknown,
|
|
492
|
+
stackStartFn: (...args: Array<never>) => unknown,
|
|
493
|
+
): unknown => {
|
|
494
|
+
let value: unknown;
|
|
495
|
+
|
|
496
|
+
try {
|
|
497
|
+
value = run();
|
|
498
|
+
} catch (error) {
|
|
499
|
+
return error;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
assert(false, "Expected function to throw.", {
|
|
503
|
+
actual: value,
|
|
504
|
+
expected: "throw",
|
|
505
|
+
operator: "throws",
|
|
506
|
+
stackStartFn,
|
|
507
|
+
});
|
|
508
|
+
};
|
|
509
|
+
|
|
510
|
+
/**
|
|
511
|
+
* Asserts that a promise rejects with the expected value.
|
|
190
512
|
*
|
|
191
|
-
*
|
|
513
|
+
* Expected values use {@link assertEqual} semantics. To perform several or
|
|
514
|
+
* specialized assertions, pass an assertion function that receives the
|
|
515
|
+
* rejection reason and returns nothing. Returning a predicate result fails. Use
|
|
516
|
+
* {@link assertRejectsSame} for SameValue or reference identity and
|
|
517
|
+
* {@link assertRejectsInstanceOf} for a runtime type.
|
|
518
|
+
*
|
|
519
|
+
* Because JavaScript permits rejecting with functions, a function second
|
|
520
|
+
* argument is always treated as an assertion function. Use `assertRejectsSame`
|
|
521
|
+
* to assert that a particular function was rejected.
|
|
192
522
|
*
|
|
193
|
-
*
|
|
194
|
-
*
|
|
195
|
-
*
|
|
523
|
+
* ### Example
|
|
524
|
+
*
|
|
525
|
+
* ```ts
|
|
526
|
+
* import { assertRejects } from "@evolu/common";
|
|
527
|
+
*
|
|
528
|
+
* const expected = new Error("Unavailable.");
|
|
529
|
+
* await assertRejects(Promise.reject(expected), expected);
|
|
196
530
|
* ```
|
|
531
|
+
*
|
|
532
|
+
* @group Assertions
|
|
197
533
|
*/
|
|
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;
|
|
534
|
+
export function assertRejects(
|
|
535
|
+
promise: PromiseLike<unknown>,
|
|
536
|
+
assertRejected: (reason: unknown) => void,
|
|
537
|
+
): Promise<void>;
|
|
538
|
+
export function assertRejects(
|
|
539
|
+
promise: PromiseLike<unknown>,
|
|
540
|
+
expected: unknown,
|
|
541
|
+
): Promise<void>;
|
|
542
|
+
export async function assertRejects(
|
|
543
|
+
promise: PromiseLike<unknown>,
|
|
544
|
+
expectedOrAssert: unknown,
|
|
545
|
+
): Promise<void> {
|
|
546
|
+
const rejected = await captureRejected(promise, assertRejects);
|
|
547
|
+
|
|
548
|
+
if (typeof expectedOrAssert === "function") {
|
|
549
|
+
const result = (expectedOrAssert as (reason: unknown) => unknown)(rejected);
|
|
550
|
+
assert(
|
|
551
|
+
result === undefined,
|
|
552
|
+
"Expected the rejection assertion to return undefined.",
|
|
553
|
+
{
|
|
554
|
+
actual: result,
|
|
555
|
+
expected: undefined,
|
|
556
|
+
operator: "strictEqual",
|
|
557
|
+
stackStartFn: assertRejects,
|
|
558
|
+
},
|
|
559
|
+
);
|
|
560
|
+
return;
|
|
561
|
+
}
|
|
227
562
|
|
|
228
|
-
const expectedValue = comparison[0];
|
|
229
|
-
const eq: Eq<any> = comparison[1] ?? eqData;
|
|
230
563
|
assert(
|
|
231
|
-
|
|
232
|
-
"Expected the
|
|
564
|
+
eqUnknown(rejected, expectedOrAssert),
|
|
565
|
+
"Expected the rejection reason to equal the expected value.",
|
|
566
|
+
{
|
|
567
|
+
actual: rejected,
|
|
568
|
+
expected: expectedOrAssert,
|
|
569
|
+
operator: "eqData",
|
|
570
|
+
diff: "full",
|
|
571
|
+
stackStartFn: assertRejects,
|
|
572
|
+
},
|
|
233
573
|
);
|
|
234
574
|
}
|
|
235
575
|
|
|
236
576
|
/**
|
|
237
|
-
* Asserts that a
|
|
238
|
-
* and narrows the Result.
|
|
577
|
+
* Asserts that a promise rejects with the same value using `Object.is`.
|
|
239
578
|
*
|
|
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.
|
|
579
|
+
* Use this to verify that a rejection reason was propagated unchanged. For
|
|
580
|
+
* value comparisons, use {@link assertRejects}.
|
|
245
581
|
*
|
|
246
582
|
* ### Example
|
|
247
583
|
*
|
|
248
584
|
* ```ts
|
|
249
|
-
* import {
|
|
250
|
-
* assertEqual,
|
|
251
|
-
* assertErr,
|
|
252
|
-
* assertType,
|
|
253
|
-
* err,
|
|
254
|
-
* type Err,
|
|
255
|
-
* type Result,
|
|
256
|
-
* type Typed,
|
|
257
|
-
* } from "@evolu/common";
|
|
585
|
+
* import { assertRejectsSame } from "@evolu/common";
|
|
258
586
|
*
|
|
259
|
-
*
|
|
260
|
-
*
|
|
261
|
-
*
|
|
587
|
+
* const expected = new Error("Unavailable.");
|
|
588
|
+
* await assertRejectsSame(Promise.reject(expected), expected);
|
|
589
|
+
* ```
|
|
262
590
|
*
|
|
263
|
-
*
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
591
|
+
* @group Assertions
|
|
592
|
+
*/
|
|
593
|
+
export const assertRejectsSame = async (
|
|
594
|
+
promise: PromiseLike<unknown>,
|
|
595
|
+
expected: unknown,
|
|
596
|
+
): Promise<void> => {
|
|
597
|
+
const rejected = await captureRejected(promise, assertRejectsSame);
|
|
598
|
+
assert(
|
|
599
|
+
eqSameValue(rejected, expected),
|
|
600
|
+
"Expected the rejection reason to be the same as the expected value.",
|
|
601
|
+
{
|
|
602
|
+
actual: rejected,
|
|
603
|
+
expected,
|
|
604
|
+
operator: "strictEqual",
|
|
605
|
+
stackStartFn: assertRejectsSame,
|
|
606
|
+
},
|
|
607
|
+
);
|
|
608
|
+
};
|
|
609
|
+
|
|
610
|
+
/**
|
|
611
|
+
* Asserts that a promise rejects with an instance of a constructor.
|
|
267
612
|
*
|
|
268
|
-
*
|
|
269
|
-
*
|
|
270
|
-
*
|
|
613
|
+
* Returns the narrowed instance so additional properties can be asserted.
|
|
614
|
+
*
|
|
615
|
+
* ### Example
|
|
616
|
+
*
|
|
617
|
+
* ```ts
|
|
618
|
+
* import { assertEqual, assertRejectsInstanceOf } from "@evolu/common";
|
|
619
|
+
*
|
|
620
|
+
* const error = await assertRejectsInstanceOf(
|
|
621
|
+
* Promise.reject(new TypeError("Unavailable.")),
|
|
622
|
+
* TypeError,
|
|
623
|
+
* );
|
|
624
|
+
* assertEqual(error.message, "Unavailable.");
|
|
271
625
|
* ```
|
|
626
|
+
*
|
|
627
|
+
* @group Assertions
|
|
272
628
|
*/
|
|
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;
|
|
629
|
+
export const assertRejectsInstanceOf = async <
|
|
630
|
+
T extends {
|
|
631
|
+
readonly name: string;
|
|
632
|
+
} & (abstract new (...args: Array<never>) => unknown),
|
|
633
|
+
>(
|
|
634
|
+
promise: PromiseLike<unknown>,
|
|
635
|
+
constructor: T,
|
|
636
|
+
): Promise<InstanceType<T>> => {
|
|
637
|
+
const rejected = await captureRejected(promise, assertRejectsInstanceOf);
|
|
638
|
+
assert(
|
|
639
|
+
rejected instanceof constructor,
|
|
640
|
+
constructor.name === ""
|
|
641
|
+
? "Expected the rejection reason to be an instance of the provided constructor."
|
|
642
|
+
: `Expected the rejection reason to be an instance of ${constructor.name}.`,
|
|
643
|
+
{
|
|
644
|
+
actual: rejected,
|
|
645
|
+
expected: constructor,
|
|
646
|
+
operator: "instanceof",
|
|
647
|
+
stackStartFn: assertRejectsInstanceOf,
|
|
648
|
+
},
|
|
649
|
+
);
|
|
650
|
+
return rejected as InstanceType<T>;
|
|
651
|
+
};
|
|
302
652
|
|
|
303
|
-
|
|
304
|
-
|
|
653
|
+
const captureRejected = async (
|
|
654
|
+
promise: PromiseLike<unknown>,
|
|
655
|
+
stackStartFn: (...args: Array<never>) => unknown,
|
|
656
|
+
): Promise<unknown> => {
|
|
657
|
+
let value: unknown;
|
|
658
|
+
|
|
659
|
+
try {
|
|
660
|
+
value = await promise;
|
|
661
|
+
} catch (error) {
|
|
662
|
+
return error;
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
assert(false, "Expected promise to reject.", {
|
|
666
|
+
actual: value,
|
|
667
|
+
expected: "rejection",
|
|
668
|
+
operator: "rejects",
|
|
669
|
+
stackStartFn,
|
|
670
|
+
});
|
|
671
|
+
};
|
|
672
|
+
|
|
673
|
+
/**
|
|
674
|
+
* Asserts that a value is an instance of a constructor and narrows it.
|
|
675
|
+
*
|
|
676
|
+
* ### Example
|
|
677
|
+
*
|
|
678
|
+
* ```ts
|
|
679
|
+
* import { assertInstanceOf, assertType } from "@evolu/common";
|
|
680
|
+
*
|
|
681
|
+
* const value: unknown = new TypeError("Invalid value.");
|
|
682
|
+
* assertInstanceOf(value, TypeError);
|
|
683
|
+
* assertType<typeof value, TypeError>();
|
|
684
|
+
* ```
|
|
685
|
+
*
|
|
686
|
+
* @group Assertions
|
|
687
|
+
*/
|
|
688
|
+
export const assertInstanceOf: <
|
|
689
|
+
Constructor extends {
|
|
690
|
+
readonly name: string;
|
|
691
|
+
} & (abstract new (...args: Array<never>) => unknown),
|
|
692
|
+
>(
|
|
693
|
+
value: unknown,
|
|
694
|
+
constructor: Constructor,
|
|
695
|
+
) => asserts value is InstanceType<Constructor> = (value, constructor) => {
|
|
305
696
|
assert(
|
|
306
|
-
|
|
307
|
-
|
|
697
|
+
value instanceof constructor,
|
|
698
|
+
constructor.name === ""
|
|
699
|
+
? "Expected an instance of the provided constructor."
|
|
700
|
+
: `Expected an instance of ${constructor.name}.`,
|
|
701
|
+
{
|
|
702
|
+
actual: value,
|
|
703
|
+
expected: constructor,
|
|
704
|
+
operator: "instanceof",
|
|
705
|
+
stackStartFn: assertInstanceOf,
|
|
706
|
+
},
|
|
308
707
|
);
|
|
309
|
-
}
|
|
708
|
+
};
|
|
310
709
|
|
|
311
710
|
/**
|
|
312
711
|
* Asserts that a value is non-nullable.
|
|
313
712
|
*
|
|
314
713
|
* Following TypeScript's {@link NonNullable}, non-nullable here means neither
|
|
315
|
-
* null nor undefined.
|
|
316
|
-
*
|
|
714
|
+
* null nor undefined.
|
|
715
|
+
*
|
|
716
|
+
* @group Assertions
|
|
317
717
|
*/
|
|
318
718
|
export const assertNonNullable: <T>(
|
|
319
719
|
value: T,
|
|
@@ -322,22 +722,24 @@ export const assertNonNullable: <T>(
|
|
|
322
722
|
value,
|
|
323
723
|
message = "Expected value to be non-nullable.",
|
|
324
724
|
) => {
|
|
325
|
-
assert(value != null, message
|
|
725
|
+
assert(value != null, message, {
|
|
726
|
+
actual: value,
|
|
727
|
+
expected: null,
|
|
728
|
+
operator: "!=",
|
|
729
|
+
stackStartFn: assertNonNullable,
|
|
730
|
+
});
|
|
326
731
|
};
|
|
327
732
|
|
|
328
733
|
/**
|
|
329
734
|
* Asserts that a value is not null while preserving undefined.
|
|
330
735
|
*
|
|
331
|
-
* Use this when a value is logically guaranteed not to be null but TypeScript
|
|
332
|
-
* cannot prove it.
|
|
333
|
-
*
|
|
334
736
|
* ### Example
|
|
335
737
|
*
|
|
336
738
|
* ```ts
|
|
337
739
|
* import {
|
|
338
|
-
* assert,
|
|
339
740
|
* assertEqual,
|
|
340
741
|
* assertErr,
|
|
742
|
+
* assertInstanceOf,
|
|
341
743
|
* assertNotNull,
|
|
342
744
|
* assertType,
|
|
343
745
|
* trySync,
|
|
@@ -345,13 +747,17 @@ export const assertNonNullable: <T>(
|
|
|
345
747
|
*
|
|
346
748
|
* const value = undefined as string | null | undefined;
|
|
347
749
|
* assertNotNull(value);
|
|
348
|
-
*
|
|
750
|
+
*
|
|
751
|
+
* assertType<typeof value, string | undefined>();
|
|
349
752
|
* assertEqual(value, undefined);
|
|
753
|
+
*
|
|
350
754
|
* const result = trySync(() => assertNotNull(null));
|
|
351
755
|
* assertErr(result);
|
|
352
|
-
*
|
|
756
|
+
* assertInstanceOf(result.error, Error);
|
|
353
757
|
* assertEqual(result.error.message, "Expected value not to be null.");
|
|
354
758
|
* ```
|
|
759
|
+
*
|
|
760
|
+
* @group Assertions
|
|
355
761
|
*/
|
|
356
762
|
export const assertNotNull: <T>(
|
|
357
763
|
value: T,
|
|
@@ -360,22 +766,24 @@ export const assertNotNull: <T>(
|
|
|
360
766
|
value,
|
|
361
767
|
message = "Expected value not to be null.",
|
|
362
768
|
) => {
|
|
363
|
-
assert(value !== null, message
|
|
769
|
+
assert(value !== null, message, {
|
|
770
|
+
actual: value,
|
|
771
|
+
expected: null,
|
|
772
|
+
operator: "notStrictEqual",
|
|
773
|
+
stackStartFn: assertNotNull,
|
|
774
|
+
});
|
|
364
775
|
};
|
|
365
776
|
|
|
366
777
|
/**
|
|
367
778
|
* Asserts that a value is not undefined while preserving null.
|
|
368
779
|
*
|
|
369
|
-
* Use this when a value is logically guaranteed not to be undefined but
|
|
370
|
-
* TypeScript cannot prove it.
|
|
371
|
-
*
|
|
372
780
|
* ### Example
|
|
373
781
|
*
|
|
374
782
|
* ```ts
|
|
375
783
|
* import {
|
|
376
|
-
* assert,
|
|
377
784
|
* assertEqual,
|
|
378
785
|
* assertErr,
|
|
786
|
+
* assertInstanceOf,
|
|
379
787
|
* assertNotUndefined,
|
|
380
788
|
* assertType,
|
|
381
789
|
* trySync,
|
|
@@ -383,13 +791,15 @@ export const assertNotNull: <T>(
|
|
|
383
791
|
*
|
|
384
792
|
* const value = null as string | null | undefined;
|
|
385
793
|
* assertNotUndefined(value);
|
|
386
|
-
* assertType<string | null
|
|
794
|
+
* assertType<typeof value, string | null>();
|
|
387
795
|
* assertEqual(value, null);
|
|
388
796
|
* const result = trySync(() => assertNotUndefined(undefined));
|
|
389
797
|
* assertErr(result);
|
|
390
|
-
*
|
|
798
|
+
* assertInstanceOf(result.error, Error);
|
|
391
799
|
* assertEqual(result.error.message, "Expected value not to be undefined.");
|
|
392
800
|
* ```
|
|
801
|
+
*
|
|
802
|
+
* @group Assertions
|
|
393
803
|
*/
|
|
394
804
|
export const assertNotUndefined: <T>(
|
|
395
805
|
value: T,
|
|
@@ -398,14 +808,53 @@ export const assertNotUndefined: <T>(
|
|
|
398
808
|
value,
|
|
399
809
|
message = "Expected value not to be undefined.",
|
|
400
810
|
) => {
|
|
401
|
-
assert(value !== undefined, message
|
|
811
|
+
assert(value !== undefined, message, {
|
|
812
|
+
actual: value,
|
|
813
|
+
expected: undefined,
|
|
814
|
+
operator: "notStrictEqual",
|
|
815
|
+
stackStartFn: assertNotUndefined,
|
|
816
|
+
});
|
|
402
817
|
};
|
|
403
818
|
|
|
404
819
|
/**
|
|
405
|
-
* Asserts that
|
|
820
|
+
* Asserts that a value has the expected length and narrows its length.
|
|
821
|
+
*
|
|
822
|
+
* ### Example
|
|
406
823
|
*
|
|
407
|
-
*
|
|
408
|
-
*
|
|
824
|
+
* ```ts
|
|
825
|
+
* import { assertLength, assertType } from "@evolu/common";
|
|
826
|
+
*
|
|
827
|
+
* const values: ReadonlyArray<string> = ["Ada", "Grace"];
|
|
828
|
+
* assertLength(values, 2);
|
|
829
|
+
* assertType<typeof values.length, 2>();
|
|
830
|
+
* ```
|
|
831
|
+
*
|
|
832
|
+
* @group Assertions
|
|
833
|
+
*/
|
|
834
|
+
export const assertLength: <
|
|
835
|
+
Value extends ValueWithLength,
|
|
836
|
+
const Length extends number,
|
|
837
|
+
>(
|
|
838
|
+
value: Value,
|
|
839
|
+
expectedLength: Length,
|
|
840
|
+
) => asserts value is Value & { readonly length: Length } = (
|
|
841
|
+
value,
|
|
842
|
+
expectedLength,
|
|
843
|
+
) => {
|
|
844
|
+
assert(
|
|
845
|
+
eqSameValue(value.length, expectedLength),
|
|
846
|
+
`Expected value to have length ${expectedLength}.`,
|
|
847
|
+
{
|
|
848
|
+
actual: value.length,
|
|
849
|
+
expected: expectedLength,
|
|
850
|
+
operator: "strictEqual",
|
|
851
|
+
stackStartFn: assertLength,
|
|
852
|
+
},
|
|
853
|
+
);
|
|
854
|
+
};
|
|
855
|
+
|
|
856
|
+
/**
|
|
857
|
+
* Asserts that an array is non-empty.
|
|
409
858
|
*
|
|
410
859
|
* ### Example
|
|
411
860
|
*
|
|
@@ -421,11 +870,13 @@ export const assertNotUndefined: <T>(
|
|
|
421
870
|
*
|
|
422
871
|
* const values = [1, 2, 3];
|
|
423
872
|
* assertNonEmptyArray(values);
|
|
424
|
-
* assertType<NonEmptyArray<number
|
|
873
|
+
* assertType<typeof values, NonEmptyArray<number>>();
|
|
425
874
|
* assertEqual(values[0], 1);
|
|
426
875
|
* const result = trySync(() => assertNonEmptyArray([]));
|
|
427
876
|
* assertErr(result);
|
|
428
877
|
* ```
|
|
878
|
+
*
|
|
879
|
+
* @group Assertions
|
|
429
880
|
*/
|
|
430
881
|
export const assertNonEmptyArray: <T>(
|
|
431
882
|
arr: Array<T>,
|
|
@@ -434,15 +885,17 @@ export const assertNonEmptyArray: <T>(
|
|
|
434
885
|
arr,
|
|
435
886
|
message = "Expected a non-empty array.",
|
|
436
887
|
) => {
|
|
437
|
-
assert(arr.length > 0, message
|
|
888
|
+
assert(arr.length > 0, message, {
|
|
889
|
+
actual: arr.length,
|
|
890
|
+
expected: 0,
|
|
891
|
+
operator: ">",
|
|
892
|
+
stackStartFn: assertNonEmptyArray,
|
|
893
|
+
});
|
|
438
894
|
};
|
|
439
895
|
|
|
440
896
|
/**
|
|
441
897
|
* Asserts that a readonly array is non-empty.
|
|
442
898
|
*
|
|
443
|
-
* Use this when a readonly array is logically guaranteed to be non-empty but
|
|
444
|
-
* TypeScript cannot prove it.
|
|
445
|
-
*
|
|
446
899
|
* ### Example
|
|
447
900
|
*
|
|
448
901
|
* ```ts
|
|
@@ -457,11 +910,13 @@ export const assertNonEmptyArray: <T>(
|
|
|
457
910
|
*
|
|
458
911
|
* const values: ReadonlyArray<number> = [1, 2, 3];
|
|
459
912
|
* assertNonEmptyReadonlyArray(values);
|
|
460
|
-
* assertType<NonEmptyReadonlyArray<number
|
|
913
|
+
* assertType<typeof values, NonEmptyReadonlyArray<number>>();
|
|
461
914
|
* assertEqual(values[0], 1);
|
|
462
915
|
* const result = trySync(() => assertNonEmptyReadonlyArray([]));
|
|
463
916
|
* assertErr(result);
|
|
464
917
|
* ```
|
|
918
|
+
*
|
|
919
|
+
* @group Assertions
|
|
465
920
|
*/
|
|
466
921
|
export const assertNonEmptyReadonlyArray: <T>(
|
|
467
922
|
arr: ReadonlyArray<T>,
|
|
@@ -470,7 +925,12 @@ export const assertNonEmptyReadonlyArray: <T>(
|
|
|
470
925
|
arr,
|
|
471
926
|
message = "Expected a non-empty readonly array.",
|
|
472
927
|
) => {
|
|
473
|
-
assert(arr.length > 0, message
|
|
928
|
+
assert(arr.length > 0, message, {
|
|
929
|
+
actual: arr.length,
|
|
930
|
+
expected: 0,
|
|
931
|
+
operator: ">",
|
|
932
|
+
stackStartFn: assertNonEmptyReadonlyArray,
|
|
933
|
+
});
|
|
474
934
|
};
|
|
475
935
|
|
|
476
936
|
/**
|
|
@@ -482,30 +942,366 @@ export const assertNonEmptyReadonlyArray: <T>(
|
|
|
482
942
|
*
|
|
483
943
|
* ```ts
|
|
484
944
|
* import {
|
|
485
|
-
* assert,
|
|
486
945
|
* assertEqual,
|
|
487
946
|
* assertErr,
|
|
947
|
+
* assertInstanceOf,
|
|
488
948
|
* assertNotDisposed,
|
|
489
949
|
* trySync,
|
|
490
950
|
* } from "@evolu/common";
|
|
491
951
|
*
|
|
492
|
-
* await using disposer = new
|
|
952
|
+
* await using disposer = new AsyncDisposableStack();
|
|
493
953
|
* assertNotDisposed(disposer);
|
|
494
954
|
* await disposer.disposeAsync();
|
|
495
955
|
* const result = trySync(() => assertNotDisposed(disposer));
|
|
496
956
|
* assertErr(result);
|
|
497
|
-
*
|
|
957
|
+
* assertInstanceOf(result.error, Error);
|
|
498
958
|
* assertEqual(result.error.message, "Cannot use a disposed object.");
|
|
499
959
|
* ```
|
|
500
960
|
*
|
|
501
961
|
* This is the JavaScript equivalent of the .NET `ObjectDisposedException`
|
|
502
962
|
* pattern: once a helper has been disposed, calling its synchronous methods is
|
|
503
|
-
* a
|
|
504
|
-
*
|
|
963
|
+
* a bug and should throw immediately instead of continuing with invalid state.
|
|
964
|
+
*
|
|
965
|
+
* @group Assertions
|
|
505
966
|
*/
|
|
506
967
|
export const assertNotDisposed = (
|
|
507
968
|
value:
|
|
508
969
|
DisposableStack | AsyncDisposableStack | { readonly disposed: boolean },
|
|
509
970
|
): void => {
|
|
510
|
-
assert(!value.disposed, "Cannot use a disposed object."
|
|
971
|
+
assert(!value.disposed, "Cannot use a disposed object.", {
|
|
972
|
+
actual: value.disposed,
|
|
973
|
+
expected: false,
|
|
974
|
+
operator: "==",
|
|
975
|
+
stackStartFn: assertNotDisposed,
|
|
976
|
+
});
|
|
977
|
+
};
|
|
978
|
+
|
|
979
|
+
/**
|
|
980
|
+
* Asserts that two values are the same using `Object.is`.
|
|
981
|
+
*
|
|
982
|
+
* Use this when exact sameness is the contract, such as asserting reference
|
|
983
|
+
* identity or narrowing the actual value to the expected value's type, as shown
|
|
984
|
+
* below. For value comparisons, use {@link assertEqual}.
|
|
985
|
+
*
|
|
986
|
+
* Uses the same equality semantics as `assert.strictEqual` from
|
|
987
|
+
* `node:assert/strict`, but is platform-agnostic. See {@link eqSameValue} for
|
|
988
|
+
* the equality semantics.
|
|
989
|
+
*
|
|
990
|
+
* ### Example
|
|
991
|
+
*
|
|
992
|
+
* ```ts
|
|
993
|
+
* import { assertSame, assertType } from "@evolu/common";
|
|
994
|
+
*
|
|
995
|
+
* interface User {
|
|
996
|
+
* readonly name: string;
|
|
997
|
+
* }
|
|
998
|
+
*
|
|
999
|
+
* const user: User = { name: "Ada" };
|
|
1000
|
+
* const value: unknown = user;
|
|
1001
|
+
*
|
|
1002
|
+
* assertSame(value, user);
|
|
1003
|
+
* // `assertSame` narrows `value` from `unknown` to `User`.
|
|
1004
|
+
* assertType<typeof value, User>();
|
|
1005
|
+
* ```
|
|
1006
|
+
*
|
|
1007
|
+
* @group Assertions
|
|
1008
|
+
*/
|
|
1009
|
+
export const assertSame: <Expected>(
|
|
1010
|
+
actual: unknown,
|
|
1011
|
+
expected: Expected,
|
|
1012
|
+
) => asserts actual is Expected = (actual, expected) => {
|
|
1013
|
+
// `Object.is` implements ECMAScript SameValue, whereas strict equality means
|
|
1014
|
+
// `===`. Keep the platform-agnostic "same" wording instead of reproducing
|
|
1015
|
+
// Node.js's value-dependent messages, which sometimes say "strictly equal".
|
|
1016
|
+
assert(eqSameValue(actual, expected), "Expected values to be the same.", {
|
|
1017
|
+
actual,
|
|
1018
|
+
expected,
|
|
1019
|
+
operator: "strictEqual",
|
|
1020
|
+
stackStartFn: assertSame,
|
|
1021
|
+
});
|
|
1022
|
+
};
|
|
1023
|
+
|
|
1024
|
+
/**
|
|
1025
|
+
* Asserts that two values are not the same using `Object.is`.
|
|
1026
|
+
*
|
|
1027
|
+
* The opposite of {@link assertSame}.
|
|
1028
|
+
*
|
|
1029
|
+
* ### Example
|
|
1030
|
+
*
|
|
1031
|
+
* ```ts
|
|
1032
|
+
* import { assertNotSame } from "@evolu/common";
|
|
1033
|
+
*
|
|
1034
|
+
* const first = { name: "Ada" };
|
|
1035
|
+
* const second = { name: "Ada" };
|
|
1036
|
+
*
|
|
1037
|
+
* assertNotSame(first, second);
|
|
1038
|
+
* assertNotSame(0, -0);
|
|
1039
|
+
* ```
|
|
1040
|
+
*
|
|
1041
|
+
* @group Assertions
|
|
1042
|
+
*/
|
|
1043
|
+
export const assertNotSame = (actual: unknown, expected: unknown): void => {
|
|
1044
|
+
// `Object.is` implements ECMAScript SameValue, whereas strict equality means
|
|
1045
|
+
// `===`. Keep the platform-agnostic "same" wording instead of reproducing
|
|
1046
|
+
// Node.js's value-dependent messages, which sometimes say "strictly equal".
|
|
1047
|
+
assert(
|
|
1048
|
+
!eqSameValue(actual, expected),
|
|
1049
|
+
"Expected values not to be the same.",
|
|
1050
|
+
{
|
|
1051
|
+
actual,
|
|
1052
|
+
expected,
|
|
1053
|
+
operator: "notStrictEqual",
|
|
1054
|
+
stackStartFn: assertNotSame,
|
|
1055
|
+
},
|
|
1056
|
+
);
|
|
1057
|
+
};
|
|
1058
|
+
|
|
1059
|
+
// oxlint-disable eslint/func-style -- Function declarations make TypeDoc categorize these APIs as functions.
|
|
1060
|
+
|
|
1061
|
+
/**
|
|
1062
|
+
* Asserts that two values are equal.
|
|
1063
|
+
*
|
|
1064
|
+
* Use this for value comparisons of both primitive and deeply structured
|
|
1065
|
+
* values. Use {@link assertSame} when exact sameness or narrowing is required.
|
|
1066
|
+
*
|
|
1067
|
+
* Like `assert.deepEqual` from `node:assert/strict`, it performs deep
|
|
1068
|
+
* structural comparison, but uses deliberately smaller, platform-agnostic
|
|
1069
|
+
* equality logic based on {@link eqData}. More complex comparison logic is
|
|
1070
|
+
* difficult to reason about and rarely needed. Other values are opaque and
|
|
1071
|
+
* compare equal only by identity. If broader comparison semantics are needed in
|
|
1072
|
+
* Node.js, use the native assertion instead.
|
|
1073
|
+
*
|
|
1074
|
+
* ### Example
|
|
1075
|
+
*
|
|
1076
|
+
* ```ts
|
|
1077
|
+
* import { assertEqual } from "@evolu/common";
|
|
1078
|
+
*
|
|
1079
|
+
* assertEqual(21 * 2, 42);
|
|
1080
|
+
*
|
|
1081
|
+
* const actual: unknown = new Map([
|
|
1082
|
+
* ["roles", new Set(["admin", "author"])],
|
|
1083
|
+
* ]);
|
|
1084
|
+
* const expected = new Map([["roles", new Set(["author", "admin"])]]);
|
|
1085
|
+
*
|
|
1086
|
+
* assertEqual(actual, expected);
|
|
1087
|
+
* ```
|
|
1088
|
+
*
|
|
1089
|
+
* @group Assertions
|
|
1090
|
+
*/
|
|
1091
|
+
export function assertEqual(actual: unknown, expected: unknown): void {
|
|
1092
|
+
assert(eqUnknown(actual, expected), "Expected values to be equal.", {
|
|
1093
|
+
actual,
|
|
1094
|
+
expected,
|
|
1095
|
+
operator: "eqData",
|
|
1096
|
+
diff: "full",
|
|
1097
|
+
stackStartFn: assertEqual,
|
|
1098
|
+
});
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
/**
|
|
1102
|
+
* Asserts that a `Uint8Array` contains the expected bytes.
|
|
1103
|
+
*
|
|
1104
|
+
* Uses {@link eqArrayNumber}, so the runtime representations may differ. For
|
|
1105
|
+
* example, a `Uint8Array` can be compared directly with a regular array.
|
|
1106
|
+
*
|
|
1107
|
+
* ### Example
|
|
1108
|
+
*
|
|
1109
|
+
* ```ts
|
|
1110
|
+
* import { assertEqualBytes } from "@evolu/common";
|
|
1111
|
+
*
|
|
1112
|
+
* const bytes = new Uint8Array([1, 5, 39, 254]);
|
|
1113
|
+
*
|
|
1114
|
+
* assertEqualBytes(bytes, [1, 5, 39, 254]);
|
|
1115
|
+
* ```
|
|
1116
|
+
*
|
|
1117
|
+
* @group Assertions
|
|
1118
|
+
*/
|
|
1119
|
+
export const assertEqualBytes = (
|
|
1120
|
+
actual: Uint8Array,
|
|
1121
|
+
expected: ArrayLike<number>,
|
|
1122
|
+
): void => {
|
|
1123
|
+
assert(eqArrayNumber(actual, expected), "Expected bytes to be equal.", {
|
|
1124
|
+
actual,
|
|
1125
|
+
expected,
|
|
1126
|
+
operator: "eqArrayNumber",
|
|
1127
|
+
diff: "full",
|
|
1128
|
+
stackStartFn: assertEqualBytes,
|
|
1129
|
+
});
|
|
511
1130
|
};
|
|
1131
|
+
|
|
1132
|
+
/**
|
|
1133
|
+
* Asserts that two values are not equal.
|
|
1134
|
+
*
|
|
1135
|
+
* The opposite of {@link assertEqual}.
|
|
1136
|
+
*
|
|
1137
|
+
* ### Example
|
|
1138
|
+
*
|
|
1139
|
+
* ```ts
|
|
1140
|
+
* import { assertNotEqual } from "@evolu/common";
|
|
1141
|
+
*
|
|
1142
|
+
* assertNotEqual({ name: "Ada" }, { name: "Grace" });
|
|
1143
|
+
* assertNotEqual([1, 2], [2, 1]);
|
|
1144
|
+
* ```
|
|
1145
|
+
*
|
|
1146
|
+
* @group Assertions
|
|
1147
|
+
*/
|
|
1148
|
+
export function assertNotEqual(actual: unknown, expected: unknown): void {
|
|
1149
|
+
assert(!eqUnknown(actual, expected), "Expected values not to be equal.", {
|
|
1150
|
+
actual,
|
|
1151
|
+
expected,
|
|
1152
|
+
operator: "notEqData",
|
|
1153
|
+
stackStartFn: assertNotEqual,
|
|
1154
|
+
});
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
// oxlint-enable eslint/func-style
|
|
1158
|
+
|
|
1159
|
+
// Assertion equality extends eqData to unknown values. The comparator's
|
|
1160
|
+
// SameValue fast path treats identical opaque values as equal, while its
|
|
1161
|
+
// unsupported branches make distinct opaque values unequal.
|
|
1162
|
+
const eqUnknown = eqData as Eq<unknown>;
|
|
1163
|
+
|
|
1164
|
+
/**
|
|
1165
|
+
* Asserts that a {@link Result} is an {@link Ok} and narrows it, optionally
|
|
1166
|
+
* comparing its value.
|
|
1167
|
+
*
|
|
1168
|
+
* When an expected value is provided, it is compared using {@link assertEqual}
|
|
1169
|
+
* semantics by default. Pass a custom {@link Eq} for different equality
|
|
1170
|
+
* semantics.
|
|
1171
|
+
*
|
|
1172
|
+
* ### Example
|
|
1173
|
+
*
|
|
1174
|
+
* ```ts
|
|
1175
|
+
* import {
|
|
1176
|
+
* assertOk,
|
|
1177
|
+
* assertType,
|
|
1178
|
+
* ok,
|
|
1179
|
+
* type Ok,
|
|
1180
|
+
* type Result,
|
|
1181
|
+
* type Typed,
|
|
1182
|
+
* } from "@evolu/common";
|
|
1183
|
+
*
|
|
1184
|
+
* interface User {
|
|
1185
|
+
* readonly id: string;
|
|
1186
|
+
* }
|
|
1187
|
+
*
|
|
1188
|
+
* interface UserNotFoundError extends Typed<"UserNotFound"> {}
|
|
1189
|
+
*
|
|
1190
|
+
* const result: Result<User, UserNotFoundError> = ok({ id: "user-1" });
|
|
1191
|
+
*
|
|
1192
|
+
* assertOk(result, { id: "user-1" });
|
|
1193
|
+
* assertType<typeof result, Ok<User>>();
|
|
1194
|
+
* ```
|
|
1195
|
+
*
|
|
1196
|
+
* @group Assertions
|
|
1197
|
+
*/
|
|
1198
|
+
export function assertOk<R extends Result<unknown, unknown>>(
|
|
1199
|
+
result: R,
|
|
1200
|
+
): asserts result is Extract<R, Ok<unknown>>;
|
|
1201
|
+
export function assertOk<R extends Result<unknown, unknown>>(
|
|
1202
|
+
result: R,
|
|
1203
|
+
expectedValue: InferOk<R>,
|
|
1204
|
+
eq: Eq<InferOk<R>>,
|
|
1205
|
+
): asserts result is Extract<R, Ok<unknown>>;
|
|
1206
|
+
export function assertOk<R extends Result<unknown, unknown>>(
|
|
1207
|
+
result: R,
|
|
1208
|
+
expectedValue: unknown,
|
|
1209
|
+
): asserts result is Extract<R, Ok<unknown>>;
|
|
1210
|
+
export function assertOk(
|
|
1211
|
+
result: AnyResult,
|
|
1212
|
+
...comparison: [] | [expectedValue: unknown, eq?: Eq<any>]
|
|
1213
|
+
): asserts result is Ok<unknown> {
|
|
1214
|
+
assert(result.ok, "Expected an Ok result.", {
|
|
1215
|
+
actual: result.ok,
|
|
1216
|
+
expected: true,
|
|
1217
|
+
operator: "strictEqual",
|
|
1218
|
+
stackStartFn: assertOk,
|
|
1219
|
+
});
|
|
1220
|
+
if (comparison.length === 0) return;
|
|
1221
|
+
|
|
1222
|
+
const expectedValue = comparison[0];
|
|
1223
|
+
assert(
|
|
1224
|
+
(comparison[1] ?? eqUnknown)(result.value, expectedValue),
|
|
1225
|
+
"Expected the value to equal the expected value.",
|
|
1226
|
+
{
|
|
1227
|
+
actual: result.value,
|
|
1228
|
+
expected: expectedValue,
|
|
1229
|
+
operator: comparison[1] === undefined ? "eqData" : "Eq",
|
|
1230
|
+
diff: comparison[1] === undefined ? "full" : undefined,
|
|
1231
|
+
stackStartFn: assertOk,
|
|
1232
|
+
},
|
|
1233
|
+
);
|
|
1234
|
+
}
|
|
1235
|
+
|
|
1236
|
+
/**
|
|
1237
|
+
* Asserts that a {@link Result} is an {@link Err} and narrows it, optionally
|
|
1238
|
+
* comparing its error.
|
|
1239
|
+
*
|
|
1240
|
+
* When an expected error is provided, it is compared using {@link assertEqual}
|
|
1241
|
+
* semantics by default. Pass a custom {@link Eq} for different equality
|
|
1242
|
+
* semantics.
|
|
1243
|
+
*
|
|
1244
|
+
* ### Example
|
|
1245
|
+
*
|
|
1246
|
+
* ```ts
|
|
1247
|
+
* import {
|
|
1248
|
+
* assertErr,
|
|
1249
|
+
* assertType,
|
|
1250
|
+
* err,
|
|
1251
|
+
* type Err,
|
|
1252
|
+
* type Result,
|
|
1253
|
+
* type Typed,
|
|
1254
|
+
* } from "@evolu/common";
|
|
1255
|
+
*
|
|
1256
|
+
* interface UserNotFoundError extends Typed<"UserNotFound"> {
|
|
1257
|
+
* readonly id: string;
|
|
1258
|
+
* }
|
|
1259
|
+
*
|
|
1260
|
+
* const result: Result<string, UserNotFoundError> = err({
|
|
1261
|
+
* type: "UserNotFound",
|
|
1262
|
+
* id: "user-1",
|
|
1263
|
+
* });
|
|
1264
|
+
*
|
|
1265
|
+
* assertErr(result, { type: "UserNotFound", id: "user-1" });
|
|
1266
|
+
* assertType<typeof result, Err<UserNotFoundError>>();
|
|
1267
|
+
* ```
|
|
1268
|
+
*
|
|
1269
|
+
* @group Assertions
|
|
1270
|
+
*/
|
|
1271
|
+
export function assertErr<R extends Result<unknown, unknown>>(
|
|
1272
|
+
result: R,
|
|
1273
|
+
): asserts result is Extract<R, Err<unknown>>;
|
|
1274
|
+
export function assertErr<R extends Result<unknown, unknown>>(
|
|
1275
|
+
result: R,
|
|
1276
|
+
expectedError: InferErr<R>,
|
|
1277
|
+
eq: Eq<InferErr<R>>,
|
|
1278
|
+
): asserts result is Extract<R, Err<unknown>>;
|
|
1279
|
+
export function assertErr<R extends Result<unknown, unknown>>(
|
|
1280
|
+
result: R,
|
|
1281
|
+
expectedError: unknown,
|
|
1282
|
+
): asserts result is Extract<R, Err<unknown>>;
|
|
1283
|
+
export function assertErr(
|
|
1284
|
+
result: AnyResult,
|
|
1285
|
+
...comparison: [] | [expectedError: unknown, eq?: Eq<any>]
|
|
1286
|
+
): asserts result is Err<unknown> {
|
|
1287
|
+
assert(!result.ok, "Expected an Err result.", {
|
|
1288
|
+
actual: result.ok,
|
|
1289
|
+
expected: false,
|
|
1290
|
+
operator: "strictEqual",
|
|
1291
|
+
stackStartFn: assertErr,
|
|
1292
|
+
});
|
|
1293
|
+
if (comparison.length === 0) return;
|
|
1294
|
+
|
|
1295
|
+
const expectedError = comparison[0];
|
|
1296
|
+
assert(
|
|
1297
|
+
(comparison[1] ?? eqUnknown)(result.error, expectedError),
|
|
1298
|
+
"Expected the error to equal the expected error.",
|
|
1299
|
+
{
|
|
1300
|
+
actual: result.error,
|
|
1301
|
+
expected: expectedError,
|
|
1302
|
+
operator: comparison[1] === undefined ? "eqData" : "Eq",
|
|
1303
|
+
diff: comparison[1] === undefined ? "full" : undefined,
|
|
1304
|
+
stackStartFn: assertErr,
|
|
1305
|
+
},
|
|
1306
|
+
);
|
|
1307
|
+
}
|