@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/dist/src/Assert.js
CHANGED
|
@@ -1,143 +1,439 @@
|
|
|
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
|
-
import { eqData,
|
|
44
|
+
import { eqArrayNumber, eqData, eqSameValue } from "./Eq.js";
|
|
7
45
|
/**
|
|
8
|
-
*
|
|
9
|
-
* not.
|
|
46
|
+
* Asserts that a condition is truthy.
|
|
10
47
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
48
|
+
* Throws an `AssertionError` with the provided message if the condition is
|
|
49
|
+
* falsy, preventing invalid state from propagating and making the failure
|
|
50
|
+
* easier to diagnose.
|
|
13
51
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
52
|
+
* Use `assert` only when a more specific assertion, such as {@link assertTrue}
|
|
53
|
+
* or {@link assertEqual}, does not apply. For that reason, `assert` requires a
|
|
54
|
+
* custom message explaining the expected condition.
|
|
55
|
+
*
|
|
56
|
+
* Options provide structured diagnostics for custom assertions. Pass an
|
|
57
|
+
* underlying failure as `cause` when it explains why the asserted condition
|
|
58
|
+
* failed, and pass the custom assertion as `stackStartFn` to omit its
|
|
59
|
+
* implementation from the stack trace.
|
|
17
60
|
*
|
|
18
61
|
* ### Example
|
|
19
62
|
*
|
|
20
63
|
* ```ts
|
|
21
|
-
* import { assert, assertEqual,
|
|
64
|
+
* import { assert, assertEqual, assertType } from "@evolu/common";
|
|
22
65
|
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
* assertEqual(
|
|
66
|
+
* const value: unknown = "Evolu";
|
|
67
|
+
* assert(typeof value === "string", "Expected a string.");
|
|
68
|
+
*
|
|
69
|
+
* assertType<typeof value, string>();
|
|
70
|
+
* assertEqual(value, "Evolu");
|
|
28
71
|
* ```
|
|
72
|
+
*
|
|
73
|
+
* @group Assertions
|
|
29
74
|
*/
|
|
30
|
-
export const assert = (condition, message) => {
|
|
75
|
+
export const assert = (condition, message, options = {}) => {
|
|
31
76
|
// oxlint-disable-next-line typescript/strict-boolean-expressions -- JavaScript truthiness is the contract of assert.
|
|
32
|
-
if (
|
|
33
|
-
|
|
77
|
+
if (condition)
|
|
78
|
+
return;
|
|
79
|
+
const actual = Object.hasOwn(options, "actual") ? options.actual : condition;
|
|
80
|
+
const expected = Object.hasOwn(options, "expected") ? options.expected : true;
|
|
81
|
+
const operator = options.operator ?? "==";
|
|
82
|
+
const stackStartFn = options.stackStartFn ?? assert;
|
|
83
|
+
if (NodeAssert !== undefined) {
|
|
84
|
+
const error = new NodeAssert.AssertionError({
|
|
85
|
+
message,
|
|
86
|
+
actual,
|
|
87
|
+
expected,
|
|
88
|
+
operator: options.diff === "full" ? "deepStrictEqual" : operator,
|
|
89
|
+
diff: options.diff,
|
|
90
|
+
stackStartFn,
|
|
91
|
+
});
|
|
92
|
+
if (options.diff === "full") {
|
|
93
|
+
error.operator = operator;
|
|
94
|
+
}
|
|
95
|
+
if (options.cause !== undefined) {
|
|
96
|
+
Object.defineProperty(error, "cause", {
|
|
97
|
+
configurable: true,
|
|
98
|
+
value: options.cause,
|
|
99
|
+
writable: true,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
throw error;
|
|
34
103
|
}
|
|
104
|
+
const error = Object.assign(new Error(message, options.cause === undefined ? undefined : { cause: options.cause }), {
|
|
105
|
+
name: "AssertionError",
|
|
106
|
+
code: "ERR_ASSERTION",
|
|
107
|
+
actual,
|
|
108
|
+
expected,
|
|
109
|
+
generatedMessage: false,
|
|
110
|
+
operator,
|
|
111
|
+
});
|
|
112
|
+
Error.captureStackTrace?.(error, stackStartFn);
|
|
113
|
+
throw error;
|
|
35
114
|
};
|
|
115
|
+
// Node.js generates assertion diffs inside its AssertionError constructor; the
|
|
116
|
+
// test runner does not derive them from actual and expected metadata alone. Use
|
|
117
|
+
// the native constructor when available and the portable fallback elsewhere.
|
|
118
|
+
const NodeAssert = globalThis.process?.getBuiltinModule?.("node:assert/strict");
|
|
119
|
+
export function assertTrue(value) {
|
|
120
|
+
assert(value === true, "Expected true.", {
|
|
121
|
+
actual: value,
|
|
122
|
+
expected: true,
|
|
123
|
+
operator: "strictEqual",
|
|
124
|
+
stackStartFn: assertTrue,
|
|
125
|
+
});
|
|
126
|
+
}
|
|
36
127
|
/**
|
|
37
|
-
* Asserts that
|
|
128
|
+
* Asserts that a value is exactly `false` and narrows it to `false`.
|
|
38
129
|
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
130
|
+
* Unlike {@link assert}, this checks an exact boolean value instead of falsiness
|
|
131
|
+
* and does not require a custom message.
|
|
41
132
|
*
|
|
42
133
|
* ### Example
|
|
43
134
|
*
|
|
44
135
|
* ```ts
|
|
45
|
-
* import {
|
|
136
|
+
* import { assertFalse, assertType } from "@evolu/common";
|
|
46
137
|
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
138
|
+
* const value: unknown = false;
|
|
139
|
+
* assertFalse(value);
|
|
140
|
+
* assertType<typeof value, false>();
|
|
141
|
+
* ```
|
|
50
142
|
*
|
|
51
|
-
*
|
|
52
|
-
|
|
143
|
+
* @group Assertions
|
|
144
|
+
*/
|
|
145
|
+
export const assertFalse = (value) => {
|
|
146
|
+
assert(value === false, "Expected false.", {
|
|
147
|
+
actual: value,
|
|
148
|
+
expected: false,
|
|
149
|
+
operator: "strictEqual",
|
|
150
|
+
stackStartFn: assertFalse,
|
|
151
|
+
});
|
|
152
|
+
};
|
|
153
|
+
export async function assertConditionAfterMicrotasks(condition, expectedMicrotaskCount, stackStartFn = assertConditionAfterMicrotasks) {
|
|
154
|
+
for (let microtaskCount = 0; microtaskCount < expectedMicrotaskCount; microtaskCount++) {
|
|
155
|
+
const actual = condition();
|
|
156
|
+
assert(!actual, `Expected condition to be false after ${microtaskCount} microtasks.`, {
|
|
157
|
+
actual,
|
|
158
|
+
expected: false,
|
|
159
|
+
operator: "strictEqual",
|
|
160
|
+
stackStartFn,
|
|
161
|
+
});
|
|
162
|
+
await Promise.resolve();
|
|
163
|
+
}
|
|
164
|
+
const actual = condition();
|
|
165
|
+
assert(actual, `Expected condition to be true after exactly ${expectedMicrotaskCount} microtasks.`, {
|
|
166
|
+
actual,
|
|
167
|
+
expected: true,
|
|
168
|
+
operator: "strictEqual",
|
|
169
|
+
stackStartFn,
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Asserts that a Promise continuation runs after exactly the specified number
|
|
174
|
+
* of microtasks.
|
|
53
175
|
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
176
|
+
* Both fulfillment and rejection count as running the continuation. Use this in
|
|
177
|
+
* tests that intentionally specify async scheduling behavior. Application code
|
|
178
|
+
* must not depend on exact microtask counts. Maintainers should review count
|
|
179
|
+
* changes because they indicate that an async pipeline changed.
|
|
180
|
+
*
|
|
181
|
+
* ### Example
|
|
182
|
+
*
|
|
183
|
+
* ```ts
|
|
184
|
+
* import { assertContinuationAfterMicrotasks } from "@evolu/common";
|
|
185
|
+
*
|
|
186
|
+
* await assertContinuationAfterMicrotasks(Promise.resolve("ready"), 1);
|
|
56
187
|
* ```
|
|
188
|
+
*
|
|
189
|
+
* @group Assertions
|
|
57
190
|
*/
|
|
58
|
-
export const
|
|
59
|
-
|
|
191
|
+
export const assertContinuationAfterMicrotasks = async (promise, expectedMicrotaskCount) => {
|
|
192
|
+
let continuationCalled = false;
|
|
193
|
+
const markContinuationCalled = () => {
|
|
194
|
+
continuationCalled = true;
|
|
195
|
+
};
|
|
196
|
+
void promise.then(markContinuationCalled, markContinuationCalled);
|
|
197
|
+
await assertConditionAfterMicrotasksWithStackStartFn(() => continuationCalled, expectedMicrotaskCount, assertContinuationAfterMicrotasks);
|
|
60
198
|
};
|
|
199
|
+
const assertConditionAfterMicrotasksWithStackStartFn = assertConditionAfterMicrotasks;
|
|
200
|
+
export function assertThrows(run, expectedOrAssert) {
|
|
201
|
+
const thrown = captureThrown(run, assertThrows);
|
|
202
|
+
if (typeof expectedOrAssert === "function") {
|
|
203
|
+
const result = expectedOrAssert(thrown);
|
|
204
|
+
assert(result === undefined, "Expected the thrown value assertion to return undefined.", {
|
|
205
|
+
actual: result,
|
|
206
|
+
expected: undefined,
|
|
207
|
+
operator: "strictEqual",
|
|
208
|
+
stackStartFn: assertThrows,
|
|
209
|
+
});
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
assert(eqUnknown(thrown, expectedOrAssert), "Expected the thrown value to equal the expected value.", {
|
|
213
|
+
actual: thrown,
|
|
214
|
+
expected: expectedOrAssert,
|
|
215
|
+
operator: "eqData",
|
|
216
|
+
diff: "full",
|
|
217
|
+
stackStartFn: assertThrows,
|
|
218
|
+
});
|
|
219
|
+
}
|
|
61
220
|
/**
|
|
62
|
-
* Asserts that a
|
|
221
|
+
* Asserts that a function throws the same value using `Object.is`.
|
|
63
222
|
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
223
|
+
* Use this to verify that a thrown value was propagated unchanged. For value
|
|
224
|
+
* comparisons, use {@link assertThrows}.
|
|
66
225
|
*
|
|
67
226
|
* ### Example
|
|
68
227
|
*
|
|
69
228
|
* ```ts
|
|
70
|
-
* import {
|
|
229
|
+
* import { assertThrowsSame } from "@evolu/common";
|
|
71
230
|
*
|
|
72
|
-
* const
|
|
73
|
-
*
|
|
74
|
-
*
|
|
231
|
+
* const expected = new Error("Unavailable.");
|
|
232
|
+
* assertThrowsSame(() => {
|
|
233
|
+
* throw expected;
|
|
234
|
+
* }, expected);
|
|
75
235
|
* ```
|
|
236
|
+
*
|
|
237
|
+
* @group Assertions
|
|
76
238
|
*/
|
|
77
|
-
export const
|
|
78
|
-
|
|
239
|
+
export const assertThrowsSame = (run, expected) => {
|
|
240
|
+
const thrown = captureThrown(run, assertThrowsSame);
|
|
241
|
+
assert(eqSameValue(thrown, expected), "Expected the thrown value to be the same as the expected value.", {
|
|
242
|
+
actual: thrown,
|
|
243
|
+
expected,
|
|
244
|
+
operator: "strictEqual",
|
|
245
|
+
stackStartFn: assertThrowsSame,
|
|
246
|
+
});
|
|
79
247
|
};
|
|
80
248
|
/**
|
|
81
|
-
* Asserts that a
|
|
249
|
+
* Asserts that a function throws an instance of a constructor.
|
|
82
250
|
*
|
|
83
|
-
*
|
|
84
|
-
*
|
|
251
|
+
* Returns the narrowed instance so additional properties can be asserted
|
|
252
|
+
* without running the function again.
|
|
85
253
|
*
|
|
86
254
|
* ### Example
|
|
87
255
|
*
|
|
88
256
|
* ```ts
|
|
89
|
-
* import {
|
|
257
|
+
* import { assertEqual, assertThrowsInstanceOf } from "@evolu/common";
|
|
90
258
|
*
|
|
91
|
-
* const
|
|
92
|
-
*
|
|
93
|
-
*
|
|
259
|
+
* const error = assertThrowsInstanceOf(() => {
|
|
260
|
+
* throw new TypeError("Expected a string.");
|
|
261
|
+
* }, TypeError);
|
|
262
|
+
* assertEqual(error.message, "Expected a string.");
|
|
94
263
|
* ```
|
|
264
|
+
*
|
|
265
|
+
* @group Assertions
|
|
95
266
|
*/
|
|
96
|
-
export const
|
|
97
|
-
|
|
267
|
+
export const assertThrowsInstanceOf = (run, constructor) => {
|
|
268
|
+
const thrown = captureThrown(run, assertThrowsInstanceOf);
|
|
269
|
+
assert(thrown instanceof constructor, constructor.name === ""
|
|
270
|
+
? "Expected the thrown value to be an instance of the provided constructor."
|
|
271
|
+
: `Expected the thrown value to be an instance of ${constructor.name}.`, {
|
|
272
|
+
actual: thrown,
|
|
273
|
+
expected: constructor,
|
|
274
|
+
operator: "instanceof",
|
|
275
|
+
stackStartFn: assertThrowsInstanceOf,
|
|
276
|
+
});
|
|
277
|
+
return thrown;
|
|
98
278
|
};
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
return;
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
279
|
+
const captureThrown = (run, stackStartFn) => {
|
|
280
|
+
let value;
|
|
281
|
+
try {
|
|
282
|
+
value = run();
|
|
283
|
+
}
|
|
284
|
+
catch (error) {
|
|
285
|
+
return error;
|
|
286
|
+
}
|
|
287
|
+
assert(false, "Expected function to throw.", {
|
|
288
|
+
actual: value,
|
|
289
|
+
expected: "throw",
|
|
290
|
+
operator: "throws",
|
|
291
|
+
stackStartFn,
|
|
292
|
+
});
|
|
293
|
+
};
|
|
294
|
+
export async function assertRejects(promise, expectedOrAssert) {
|
|
295
|
+
const rejected = await captureRejected(promise, assertRejects);
|
|
296
|
+
if (typeof expectedOrAssert === "function") {
|
|
297
|
+
const result = expectedOrAssert(rejected);
|
|
298
|
+
assert(result === undefined, "Expected the rejection assertion to return undefined.", {
|
|
299
|
+
actual: result,
|
|
300
|
+
expected: undefined,
|
|
301
|
+
operator: "strictEqual",
|
|
302
|
+
stackStartFn: assertRejects,
|
|
303
|
+
});
|
|
113
304
|
return;
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
305
|
+
}
|
|
306
|
+
assert(eqUnknown(rejected, expectedOrAssert), "Expected the rejection reason to equal the expected value.", {
|
|
307
|
+
actual: rejected,
|
|
308
|
+
expected: expectedOrAssert,
|
|
309
|
+
operator: "eqData",
|
|
310
|
+
diff: "full",
|
|
311
|
+
stackStartFn: assertRejects,
|
|
312
|
+
});
|
|
117
313
|
}
|
|
314
|
+
/**
|
|
315
|
+
* Asserts that a promise rejects with the same value using `Object.is`.
|
|
316
|
+
*
|
|
317
|
+
* Use this to verify that a rejection reason was propagated unchanged. For
|
|
318
|
+
* value comparisons, use {@link assertRejects}.
|
|
319
|
+
*
|
|
320
|
+
* ### Example
|
|
321
|
+
*
|
|
322
|
+
* ```ts
|
|
323
|
+
* import { assertRejectsSame } from "@evolu/common";
|
|
324
|
+
*
|
|
325
|
+
* const expected = new Error("Unavailable.");
|
|
326
|
+
* await assertRejectsSame(Promise.reject(expected), expected);
|
|
327
|
+
* ```
|
|
328
|
+
*
|
|
329
|
+
* @group Assertions
|
|
330
|
+
*/
|
|
331
|
+
export const assertRejectsSame = async (promise, expected) => {
|
|
332
|
+
const rejected = await captureRejected(promise, assertRejectsSame);
|
|
333
|
+
assert(eqSameValue(rejected, expected), "Expected the rejection reason to be the same as the expected value.", {
|
|
334
|
+
actual: rejected,
|
|
335
|
+
expected,
|
|
336
|
+
operator: "strictEqual",
|
|
337
|
+
stackStartFn: assertRejectsSame,
|
|
338
|
+
});
|
|
339
|
+
};
|
|
340
|
+
/**
|
|
341
|
+
* Asserts that a promise rejects with an instance of a constructor.
|
|
342
|
+
*
|
|
343
|
+
* Returns the narrowed instance so additional properties can be asserted.
|
|
344
|
+
*
|
|
345
|
+
* ### Example
|
|
346
|
+
*
|
|
347
|
+
* ```ts
|
|
348
|
+
* import { assertEqual, assertRejectsInstanceOf } from "@evolu/common";
|
|
349
|
+
*
|
|
350
|
+
* const error = await assertRejectsInstanceOf(
|
|
351
|
+
* Promise.reject(new TypeError("Unavailable.")),
|
|
352
|
+
* TypeError,
|
|
353
|
+
* );
|
|
354
|
+
* assertEqual(error.message, "Unavailable.");
|
|
355
|
+
* ```
|
|
356
|
+
*
|
|
357
|
+
* @group Assertions
|
|
358
|
+
*/
|
|
359
|
+
export const assertRejectsInstanceOf = async (promise, constructor) => {
|
|
360
|
+
const rejected = await captureRejected(promise, assertRejectsInstanceOf);
|
|
361
|
+
assert(rejected instanceof constructor, constructor.name === ""
|
|
362
|
+
? "Expected the rejection reason to be an instance of the provided constructor."
|
|
363
|
+
: `Expected the rejection reason to be an instance of ${constructor.name}.`, {
|
|
364
|
+
actual: rejected,
|
|
365
|
+
expected: constructor,
|
|
366
|
+
operator: "instanceof",
|
|
367
|
+
stackStartFn: assertRejectsInstanceOf,
|
|
368
|
+
});
|
|
369
|
+
return rejected;
|
|
370
|
+
};
|
|
371
|
+
const captureRejected = async (promise, stackStartFn) => {
|
|
372
|
+
let value;
|
|
373
|
+
try {
|
|
374
|
+
value = await promise;
|
|
375
|
+
}
|
|
376
|
+
catch (error) {
|
|
377
|
+
return error;
|
|
378
|
+
}
|
|
379
|
+
assert(false, "Expected promise to reject.", {
|
|
380
|
+
actual: value,
|
|
381
|
+
expected: "rejection",
|
|
382
|
+
operator: "rejects",
|
|
383
|
+
stackStartFn,
|
|
384
|
+
});
|
|
385
|
+
};
|
|
386
|
+
/**
|
|
387
|
+
* Asserts that a value is an instance of a constructor and narrows it.
|
|
388
|
+
*
|
|
389
|
+
* ### Example
|
|
390
|
+
*
|
|
391
|
+
* ```ts
|
|
392
|
+
* import { assertInstanceOf, assertType } from "@evolu/common";
|
|
393
|
+
*
|
|
394
|
+
* const value: unknown = new TypeError("Invalid value.");
|
|
395
|
+
* assertInstanceOf(value, TypeError);
|
|
396
|
+
* assertType<typeof value, TypeError>();
|
|
397
|
+
* ```
|
|
398
|
+
*
|
|
399
|
+
* @group Assertions
|
|
400
|
+
*/
|
|
401
|
+
export const assertInstanceOf = (value, constructor) => {
|
|
402
|
+
assert(value instanceof constructor, constructor.name === ""
|
|
403
|
+
? "Expected an instance of the provided constructor."
|
|
404
|
+
: `Expected an instance of ${constructor.name}.`, {
|
|
405
|
+
actual: value,
|
|
406
|
+
expected: constructor,
|
|
407
|
+
operator: "instanceof",
|
|
408
|
+
stackStartFn: assertInstanceOf,
|
|
409
|
+
});
|
|
410
|
+
};
|
|
118
411
|
/**
|
|
119
412
|
* Asserts that a value is non-nullable.
|
|
120
413
|
*
|
|
121
414
|
* Following TypeScript's {@link NonNullable}, non-nullable here means neither
|
|
122
|
-
* null nor undefined.
|
|
123
|
-
*
|
|
415
|
+
* null nor undefined.
|
|
416
|
+
*
|
|
417
|
+
* @group Assertions
|
|
124
418
|
*/
|
|
125
419
|
export const assertNonNullable = (value, message = "Expected value to be non-nullable.") => {
|
|
126
|
-
assert(value != null, message
|
|
420
|
+
assert(value != null, message, {
|
|
421
|
+
actual: value,
|
|
422
|
+
expected: null,
|
|
423
|
+
operator: "!=",
|
|
424
|
+
stackStartFn: assertNonNullable,
|
|
425
|
+
});
|
|
127
426
|
};
|
|
128
427
|
/**
|
|
129
428
|
* Asserts that a value is not null while preserving undefined.
|
|
130
429
|
*
|
|
131
|
-
* Use this when a value is logically guaranteed not to be null but TypeScript
|
|
132
|
-
* cannot prove it.
|
|
133
|
-
*
|
|
134
430
|
* ### Example
|
|
135
431
|
*
|
|
136
432
|
* ```ts
|
|
137
433
|
* import {
|
|
138
|
-
* assert,
|
|
139
434
|
* assertEqual,
|
|
140
435
|
* assertErr,
|
|
436
|
+
* assertInstanceOf,
|
|
141
437
|
* assertNotNull,
|
|
142
438
|
* assertType,
|
|
143
439
|
* trySync,
|
|
@@ -145,30 +441,36 @@ export const assertNonNullable = (value, message = "Expected value to be non-nul
|
|
|
145
441
|
*
|
|
146
442
|
* const value = undefined as string | null | undefined;
|
|
147
443
|
* assertNotNull(value);
|
|
148
|
-
*
|
|
444
|
+
*
|
|
445
|
+
* assertType<typeof value, string | undefined>();
|
|
149
446
|
* assertEqual(value, undefined);
|
|
447
|
+
*
|
|
150
448
|
* const result = trySync(() => assertNotNull(null));
|
|
151
449
|
* assertErr(result);
|
|
152
|
-
*
|
|
450
|
+
* assertInstanceOf(result.error, Error);
|
|
153
451
|
* assertEqual(result.error.message, "Expected value not to be null.");
|
|
154
452
|
* ```
|
|
453
|
+
*
|
|
454
|
+
* @group Assertions
|
|
155
455
|
*/
|
|
156
456
|
export const assertNotNull = (value, message = "Expected value not to be null.") => {
|
|
157
|
-
assert(value !== null, message
|
|
457
|
+
assert(value !== null, message, {
|
|
458
|
+
actual: value,
|
|
459
|
+
expected: null,
|
|
460
|
+
operator: "notStrictEqual",
|
|
461
|
+
stackStartFn: assertNotNull,
|
|
462
|
+
});
|
|
158
463
|
};
|
|
159
464
|
/**
|
|
160
465
|
* Asserts that a value is not undefined while preserving null.
|
|
161
466
|
*
|
|
162
|
-
* Use this when a value is logically guaranteed not to be undefined but
|
|
163
|
-
* TypeScript cannot prove it.
|
|
164
|
-
*
|
|
165
467
|
* ### Example
|
|
166
468
|
*
|
|
167
469
|
* ```ts
|
|
168
470
|
* import {
|
|
169
|
-
* assert,
|
|
170
471
|
* assertEqual,
|
|
171
472
|
* assertErr,
|
|
473
|
+
* assertInstanceOf,
|
|
172
474
|
* assertNotUndefined,
|
|
173
475
|
* assertType,
|
|
174
476
|
* trySync,
|
|
@@ -176,22 +478,49 @@ export const assertNotNull = (value, message = "Expected value not to be null.")
|
|
|
176
478
|
*
|
|
177
479
|
* const value = null as string | null | undefined;
|
|
178
480
|
* assertNotUndefined(value);
|
|
179
|
-
* assertType<string | null
|
|
481
|
+
* assertType<typeof value, string | null>();
|
|
180
482
|
* assertEqual(value, null);
|
|
181
483
|
* const result = trySync(() => assertNotUndefined(undefined));
|
|
182
484
|
* assertErr(result);
|
|
183
|
-
*
|
|
485
|
+
* assertInstanceOf(result.error, Error);
|
|
184
486
|
* assertEqual(result.error.message, "Expected value not to be undefined.");
|
|
185
487
|
* ```
|
|
488
|
+
*
|
|
489
|
+
* @group Assertions
|
|
186
490
|
*/
|
|
187
491
|
export const assertNotUndefined = (value, message = "Expected value not to be undefined.") => {
|
|
188
|
-
assert(value !== undefined, message
|
|
492
|
+
assert(value !== undefined, message, {
|
|
493
|
+
actual: value,
|
|
494
|
+
expected: undefined,
|
|
495
|
+
operator: "notStrictEqual",
|
|
496
|
+
stackStartFn: assertNotUndefined,
|
|
497
|
+
});
|
|
189
498
|
};
|
|
190
499
|
/**
|
|
191
|
-
* Asserts that
|
|
500
|
+
* Asserts that a value has the expected length and narrows its length.
|
|
501
|
+
*
|
|
502
|
+
* ### Example
|
|
503
|
+
*
|
|
504
|
+
* ```ts
|
|
505
|
+
* import { assertLength, assertType } from "@evolu/common";
|
|
192
506
|
*
|
|
193
|
-
*
|
|
194
|
-
*
|
|
507
|
+
* const values: ReadonlyArray<string> = ["Ada", "Grace"];
|
|
508
|
+
* assertLength(values, 2);
|
|
509
|
+
* assertType<typeof values.length, 2>();
|
|
510
|
+
* ```
|
|
511
|
+
*
|
|
512
|
+
* @group Assertions
|
|
513
|
+
*/
|
|
514
|
+
export const assertLength = (value, expectedLength) => {
|
|
515
|
+
assert(eqSameValue(value.length, expectedLength), `Expected value to have length ${expectedLength}.`, {
|
|
516
|
+
actual: value.length,
|
|
517
|
+
expected: expectedLength,
|
|
518
|
+
operator: "strictEqual",
|
|
519
|
+
stackStartFn: assertLength,
|
|
520
|
+
});
|
|
521
|
+
};
|
|
522
|
+
/**
|
|
523
|
+
* Asserts that an array is non-empty.
|
|
195
524
|
*
|
|
196
525
|
* ### Example
|
|
197
526
|
*
|
|
@@ -207,21 +536,25 @@ export const assertNotUndefined = (value, message = "Expected value not to be un
|
|
|
207
536
|
*
|
|
208
537
|
* const values = [1, 2, 3];
|
|
209
538
|
* assertNonEmptyArray(values);
|
|
210
|
-
* assertType<NonEmptyArray<number
|
|
539
|
+
* assertType<typeof values, NonEmptyArray<number>>();
|
|
211
540
|
* assertEqual(values[0], 1);
|
|
212
541
|
* const result = trySync(() => assertNonEmptyArray([]));
|
|
213
542
|
* assertErr(result);
|
|
214
543
|
* ```
|
|
544
|
+
*
|
|
545
|
+
* @group Assertions
|
|
215
546
|
*/
|
|
216
547
|
export const assertNonEmptyArray = (arr, message = "Expected a non-empty array.") => {
|
|
217
|
-
assert(arr.length > 0, message
|
|
548
|
+
assert(arr.length > 0, message, {
|
|
549
|
+
actual: arr.length,
|
|
550
|
+
expected: 0,
|
|
551
|
+
operator: ">",
|
|
552
|
+
stackStartFn: assertNonEmptyArray,
|
|
553
|
+
});
|
|
218
554
|
};
|
|
219
555
|
/**
|
|
220
556
|
* Asserts that a readonly array is non-empty.
|
|
221
557
|
*
|
|
222
|
-
* Use this when a readonly array is logically guaranteed to be non-empty but
|
|
223
|
-
* TypeScript cannot prove it.
|
|
224
|
-
*
|
|
225
558
|
* ### Example
|
|
226
559
|
*
|
|
227
560
|
* ```ts
|
|
@@ -236,14 +569,21 @@ export const assertNonEmptyArray = (arr, message = "Expected a non-empty array."
|
|
|
236
569
|
*
|
|
237
570
|
* const values: ReadonlyArray<number> = [1, 2, 3];
|
|
238
571
|
* assertNonEmptyReadonlyArray(values);
|
|
239
|
-
* assertType<NonEmptyReadonlyArray<number
|
|
572
|
+
* assertType<typeof values, NonEmptyReadonlyArray<number>>();
|
|
240
573
|
* assertEqual(values[0], 1);
|
|
241
574
|
* const result = trySync(() => assertNonEmptyReadonlyArray([]));
|
|
242
575
|
* assertErr(result);
|
|
243
576
|
* ```
|
|
577
|
+
*
|
|
578
|
+
* @group Assertions
|
|
244
579
|
*/
|
|
245
580
|
export const assertNonEmptyReadonlyArray = (arr, message = "Expected a non-empty readonly array.") => {
|
|
246
|
-
assert(arr.length > 0, message
|
|
581
|
+
assert(arr.length > 0, message, {
|
|
582
|
+
actual: arr.length,
|
|
583
|
+
expected: 0,
|
|
584
|
+
operator: ">",
|
|
585
|
+
stackStartFn: assertNonEmptyReadonlyArray,
|
|
586
|
+
});
|
|
247
587
|
};
|
|
248
588
|
/**
|
|
249
589
|
* Guards synchronous methods on objects that may be called after disposal.
|
|
@@ -254,27 +594,236 @@ export const assertNonEmptyReadonlyArray = (arr, message = "Expected a non-empty
|
|
|
254
594
|
*
|
|
255
595
|
* ```ts
|
|
256
596
|
* import {
|
|
257
|
-
* assert,
|
|
258
597
|
* assertEqual,
|
|
259
598
|
* assertErr,
|
|
599
|
+
* assertInstanceOf,
|
|
260
600
|
* assertNotDisposed,
|
|
261
601
|
* trySync,
|
|
262
602
|
* } from "@evolu/common";
|
|
263
603
|
*
|
|
264
|
-
* await using disposer = new
|
|
604
|
+
* await using disposer = new AsyncDisposableStack();
|
|
265
605
|
* assertNotDisposed(disposer);
|
|
266
606
|
* await disposer.disposeAsync();
|
|
267
607
|
* const result = trySync(() => assertNotDisposed(disposer));
|
|
268
608
|
* assertErr(result);
|
|
269
|
-
*
|
|
609
|
+
* assertInstanceOf(result.error, Error);
|
|
270
610
|
* assertEqual(result.error.message, "Cannot use a disposed object.");
|
|
271
611
|
* ```
|
|
272
612
|
*
|
|
273
613
|
* This is the JavaScript equivalent of the .NET `ObjectDisposedException`
|
|
274
614
|
* pattern: once a helper has been disposed, calling its synchronous methods is
|
|
275
|
-
* a
|
|
276
|
-
*
|
|
615
|
+
* a bug and should throw immediately instead of continuing with invalid state.
|
|
616
|
+
*
|
|
617
|
+
* @group Assertions
|
|
277
618
|
*/
|
|
278
619
|
export const assertNotDisposed = (value) => {
|
|
279
|
-
assert(!value.disposed, "Cannot use a disposed object."
|
|
620
|
+
assert(!value.disposed, "Cannot use a disposed object.", {
|
|
621
|
+
actual: value.disposed,
|
|
622
|
+
expected: false,
|
|
623
|
+
operator: "==",
|
|
624
|
+
stackStartFn: assertNotDisposed,
|
|
625
|
+
});
|
|
626
|
+
};
|
|
627
|
+
/**
|
|
628
|
+
* Asserts that two values are the same using `Object.is`.
|
|
629
|
+
*
|
|
630
|
+
* Use this when exact sameness is the contract, such as asserting reference
|
|
631
|
+
* identity or narrowing the actual value to the expected value's type, as shown
|
|
632
|
+
* below. For value comparisons, use {@link assertEqual}.
|
|
633
|
+
*
|
|
634
|
+
* Uses the same equality semantics as `assert.strictEqual` from
|
|
635
|
+
* `node:assert/strict`, but is platform-agnostic. See {@link eqSameValue} for
|
|
636
|
+
* the equality semantics.
|
|
637
|
+
*
|
|
638
|
+
* ### Example
|
|
639
|
+
*
|
|
640
|
+
* ```ts
|
|
641
|
+
* import { assertSame, assertType } from "@evolu/common";
|
|
642
|
+
*
|
|
643
|
+
* interface User {
|
|
644
|
+
* readonly name: string;
|
|
645
|
+
* }
|
|
646
|
+
*
|
|
647
|
+
* const user: User = { name: "Ada" };
|
|
648
|
+
* const value: unknown = user;
|
|
649
|
+
*
|
|
650
|
+
* assertSame(value, user);
|
|
651
|
+
* // `assertSame` narrows `value` from `unknown` to `User`.
|
|
652
|
+
* assertType<typeof value, User>();
|
|
653
|
+
* ```
|
|
654
|
+
*
|
|
655
|
+
* @group Assertions
|
|
656
|
+
*/
|
|
657
|
+
export const assertSame = (actual, expected) => {
|
|
658
|
+
// `Object.is` implements ECMAScript SameValue, whereas strict equality means
|
|
659
|
+
// `===`. Keep the platform-agnostic "same" wording instead of reproducing
|
|
660
|
+
// Node.js's value-dependent messages, which sometimes say "strictly equal".
|
|
661
|
+
assert(eqSameValue(actual, expected), "Expected values to be the same.", {
|
|
662
|
+
actual,
|
|
663
|
+
expected,
|
|
664
|
+
operator: "strictEqual",
|
|
665
|
+
stackStartFn: assertSame,
|
|
666
|
+
});
|
|
280
667
|
};
|
|
668
|
+
/**
|
|
669
|
+
* Asserts that two values are not the same using `Object.is`.
|
|
670
|
+
*
|
|
671
|
+
* The opposite of {@link assertSame}.
|
|
672
|
+
*
|
|
673
|
+
* ### Example
|
|
674
|
+
*
|
|
675
|
+
* ```ts
|
|
676
|
+
* import { assertNotSame } from "@evolu/common";
|
|
677
|
+
*
|
|
678
|
+
* const first = { name: "Ada" };
|
|
679
|
+
* const second = { name: "Ada" };
|
|
680
|
+
*
|
|
681
|
+
* assertNotSame(first, second);
|
|
682
|
+
* assertNotSame(0, -0);
|
|
683
|
+
* ```
|
|
684
|
+
*
|
|
685
|
+
* @group Assertions
|
|
686
|
+
*/
|
|
687
|
+
export const assertNotSame = (actual, expected) => {
|
|
688
|
+
// `Object.is` implements ECMAScript SameValue, whereas strict equality means
|
|
689
|
+
// `===`. Keep the platform-agnostic "same" wording instead of reproducing
|
|
690
|
+
// Node.js's value-dependent messages, which sometimes say "strictly equal".
|
|
691
|
+
assert(!eqSameValue(actual, expected), "Expected values not to be the same.", {
|
|
692
|
+
actual,
|
|
693
|
+
expected,
|
|
694
|
+
operator: "notStrictEqual",
|
|
695
|
+
stackStartFn: assertNotSame,
|
|
696
|
+
});
|
|
697
|
+
};
|
|
698
|
+
// oxlint-disable eslint/func-style -- Function declarations make TypeDoc categorize these APIs as functions.
|
|
699
|
+
/**
|
|
700
|
+
* Asserts that two values are equal.
|
|
701
|
+
*
|
|
702
|
+
* Use this for value comparisons of both primitive and deeply structured
|
|
703
|
+
* values. Use {@link assertSame} when exact sameness or narrowing is required.
|
|
704
|
+
*
|
|
705
|
+
* Like `assert.deepEqual` from `node:assert/strict`, it performs deep
|
|
706
|
+
* structural comparison, but uses deliberately smaller, platform-agnostic
|
|
707
|
+
* equality logic based on {@link eqData}. More complex comparison logic is
|
|
708
|
+
* difficult to reason about and rarely needed. Other values are opaque and
|
|
709
|
+
* compare equal only by identity. If broader comparison semantics are needed in
|
|
710
|
+
* Node.js, use the native assertion instead.
|
|
711
|
+
*
|
|
712
|
+
* ### Example
|
|
713
|
+
*
|
|
714
|
+
* ```ts
|
|
715
|
+
* import { assertEqual } from "@evolu/common";
|
|
716
|
+
*
|
|
717
|
+
* assertEqual(21 * 2, 42);
|
|
718
|
+
*
|
|
719
|
+
* const actual: unknown = new Map([
|
|
720
|
+
* ["roles", new Set(["admin", "author"])],
|
|
721
|
+
* ]);
|
|
722
|
+
* const expected = new Map([["roles", new Set(["author", "admin"])]]);
|
|
723
|
+
*
|
|
724
|
+
* assertEqual(actual, expected);
|
|
725
|
+
* ```
|
|
726
|
+
*
|
|
727
|
+
* @group Assertions
|
|
728
|
+
*/
|
|
729
|
+
export function assertEqual(actual, expected) {
|
|
730
|
+
assert(eqUnknown(actual, expected), "Expected values to be equal.", {
|
|
731
|
+
actual,
|
|
732
|
+
expected,
|
|
733
|
+
operator: "eqData",
|
|
734
|
+
diff: "full",
|
|
735
|
+
stackStartFn: assertEqual,
|
|
736
|
+
});
|
|
737
|
+
}
|
|
738
|
+
/**
|
|
739
|
+
* Asserts that a `Uint8Array` contains the expected bytes.
|
|
740
|
+
*
|
|
741
|
+
* Uses {@link eqArrayNumber}, so the runtime representations may differ. For
|
|
742
|
+
* example, a `Uint8Array` can be compared directly with a regular array.
|
|
743
|
+
*
|
|
744
|
+
* ### Example
|
|
745
|
+
*
|
|
746
|
+
* ```ts
|
|
747
|
+
* import { assertEqualBytes } from "@evolu/common";
|
|
748
|
+
*
|
|
749
|
+
* const bytes = new Uint8Array([1, 5, 39, 254]);
|
|
750
|
+
*
|
|
751
|
+
* assertEqualBytes(bytes, [1, 5, 39, 254]);
|
|
752
|
+
* ```
|
|
753
|
+
*
|
|
754
|
+
* @group Assertions
|
|
755
|
+
*/
|
|
756
|
+
export const assertEqualBytes = (actual, expected) => {
|
|
757
|
+
assert(eqArrayNumber(actual, expected), "Expected bytes to be equal.", {
|
|
758
|
+
actual,
|
|
759
|
+
expected,
|
|
760
|
+
operator: "eqArrayNumber",
|
|
761
|
+
diff: "full",
|
|
762
|
+
stackStartFn: assertEqualBytes,
|
|
763
|
+
});
|
|
764
|
+
};
|
|
765
|
+
/**
|
|
766
|
+
* Asserts that two values are not equal.
|
|
767
|
+
*
|
|
768
|
+
* The opposite of {@link assertEqual}.
|
|
769
|
+
*
|
|
770
|
+
* ### Example
|
|
771
|
+
*
|
|
772
|
+
* ```ts
|
|
773
|
+
* import { assertNotEqual } from "@evolu/common";
|
|
774
|
+
*
|
|
775
|
+
* assertNotEqual({ name: "Ada" }, { name: "Grace" });
|
|
776
|
+
* assertNotEqual([1, 2], [2, 1]);
|
|
777
|
+
* ```
|
|
778
|
+
*
|
|
779
|
+
* @group Assertions
|
|
780
|
+
*/
|
|
781
|
+
export function assertNotEqual(actual, expected) {
|
|
782
|
+
assert(!eqUnknown(actual, expected), "Expected values not to be equal.", {
|
|
783
|
+
actual,
|
|
784
|
+
expected,
|
|
785
|
+
operator: "notEqData",
|
|
786
|
+
stackStartFn: assertNotEqual,
|
|
787
|
+
});
|
|
788
|
+
}
|
|
789
|
+
// oxlint-enable eslint/func-style
|
|
790
|
+
// Assertion equality extends eqData to unknown values. The comparator's
|
|
791
|
+
// SameValue fast path treats identical opaque values as equal, while its
|
|
792
|
+
// unsupported branches make distinct opaque values unequal.
|
|
793
|
+
const eqUnknown = eqData;
|
|
794
|
+
export function assertOk(result, ...comparison) {
|
|
795
|
+
assert(result.ok, "Expected an Ok result.", {
|
|
796
|
+
actual: result.ok,
|
|
797
|
+
expected: true,
|
|
798
|
+
operator: "strictEqual",
|
|
799
|
+
stackStartFn: assertOk,
|
|
800
|
+
});
|
|
801
|
+
if (comparison.length === 0)
|
|
802
|
+
return;
|
|
803
|
+
const expectedValue = comparison[0];
|
|
804
|
+
assert((comparison[1] ?? eqUnknown)(result.value, expectedValue), "Expected the value to equal the expected value.", {
|
|
805
|
+
actual: result.value,
|
|
806
|
+
expected: expectedValue,
|
|
807
|
+
operator: comparison[1] === undefined ? "eqData" : "Eq",
|
|
808
|
+
diff: comparison[1] === undefined ? "full" : undefined,
|
|
809
|
+
stackStartFn: assertOk,
|
|
810
|
+
});
|
|
811
|
+
}
|
|
812
|
+
export function assertErr(result, ...comparison) {
|
|
813
|
+
assert(!result.ok, "Expected an Err result.", {
|
|
814
|
+
actual: result.ok,
|
|
815
|
+
expected: false,
|
|
816
|
+
operator: "strictEqual",
|
|
817
|
+
stackStartFn: assertErr,
|
|
818
|
+
});
|
|
819
|
+
if (comparison.length === 0)
|
|
820
|
+
return;
|
|
821
|
+
const expectedError = comparison[0];
|
|
822
|
+
assert((comparison[1] ?? eqUnknown)(result.error, expectedError), "Expected the error to equal the expected error.", {
|
|
823
|
+
actual: result.error,
|
|
824
|
+
expected: expectedError,
|
|
825
|
+
operator: comparison[1] === undefined ? "eqData" : "Eq",
|
|
826
|
+
diff: comparison[1] === undefined ? "full" : undefined,
|
|
827
|
+
stackStartFn: assertErr,
|
|
828
|
+
});
|
|
829
|
+
}
|