@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/Set.test.ts
ADDED
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
import { describe, it } from "node:test";
|
|
2
|
+
import { assertEqual, assertFalse, assertTrue } from "./Assert.ts";
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
addToSet,
|
|
6
|
+
createSet,
|
|
7
|
+
deleteFromSet,
|
|
8
|
+
emptySet,
|
|
9
|
+
filterSet,
|
|
10
|
+
firstInSet,
|
|
11
|
+
isNonEmptySet,
|
|
12
|
+
mapSet,
|
|
13
|
+
type NonEmptyReadonlySet,
|
|
14
|
+
} from "./Set.ts";
|
|
15
|
+
import { assertType } from "./Type.ts";
|
|
16
|
+
|
|
17
|
+
describe("Constants", () => {
|
|
18
|
+
describe("emptySet", () => {
|
|
19
|
+
it("is empty", () => {
|
|
20
|
+
assertEqual(emptySet.size, 0);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it("is assignable to any ReadonlySet<T>", () => {
|
|
24
|
+
const numbers: ReadonlySet<number> = emptySet;
|
|
25
|
+
const objects: ReadonlySet<{ id: number }> = emptySet;
|
|
26
|
+
|
|
27
|
+
assertType<typeof numbers, ReadonlySet<number>>();
|
|
28
|
+
assertType<typeof objects, ReadonlySet<{ id: number }>>();
|
|
29
|
+
|
|
30
|
+
assertEqual(numbers.size, 0);
|
|
31
|
+
assertEqual(objects.size, 0);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("enables fast empty check via reference equality", () => {
|
|
35
|
+
const children: ReadonlySet<number> = emptySet;
|
|
36
|
+
assertTrue(children === emptySet);
|
|
37
|
+
|
|
38
|
+
const nonEmpty = addToSet(emptySet, 1);
|
|
39
|
+
assertFalse((nonEmpty as ReadonlySet<number>) === emptySet);
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
describe("Type guards", () => {
|
|
45
|
+
describe("isNonEmptySet", () => {
|
|
46
|
+
it("returns true for non-empty set", () => {
|
|
47
|
+
const set = new Set([1, 2, 3]);
|
|
48
|
+
assertTrue(isNonEmptySet(set));
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("returns false for empty set", () => {
|
|
52
|
+
const set = new Set<number>();
|
|
53
|
+
assertFalse(isNonEmptySet(set));
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it("returns true for single element set", () => {
|
|
57
|
+
const set = new Set([1]);
|
|
58
|
+
assertTrue(isNonEmptySet(set));
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("narrows mutable set to NonEmptyReadonlySet", () => {
|
|
62
|
+
const set = new Set<number>([1, 2, 3]);
|
|
63
|
+
if (isNonEmptySet(set)) {
|
|
64
|
+
// Mutable set intersected with branded readonly type
|
|
65
|
+
assertType<
|
|
66
|
+
typeof set extends NonEmptyReadonlySet<number> ? true : false,
|
|
67
|
+
true
|
|
68
|
+
>();
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it("narrows readonly set to NonEmptyReadonlySet", () => {
|
|
73
|
+
const set: ReadonlySet<number> = new Set([1, 2, 3]);
|
|
74
|
+
if (isNonEmptySet(set)) {
|
|
75
|
+
assertType<typeof set, NonEmptyReadonlySet<number>>();
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("returns false for empty readonly set", () => {
|
|
80
|
+
const set: ReadonlySet<number> = new Set();
|
|
81
|
+
assertFalse(isNonEmptySet(set));
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
describe("Transformations", () => {
|
|
87
|
+
describe("createSet", () => {
|
|
88
|
+
it("creates empty set from empty array", () => {
|
|
89
|
+
const result = createSet([] as ReadonlyArray<number>);
|
|
90
|
+
assertEqual(result, new Set());
|
|
91
|
+
assertType<
|
|
92
|
+
typeof result extends ReadonlySet<number> ? true : false,
|
|
93
|
+
true
|
|
94
|
+
>();
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it("creates non-empty set from non-empty array", () => {
|
|
98
|
+
const result = createSet([1, 2, 3] as const);
|
|
99
|
+
assertEqual(result, new Set([1, 2, 3]));
|
|
100
|
+
assertType<typeof result, NonEmptyReadonlySet<1 | 2 | 3>>();
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it("deduplicates duplicate values", () => {
|
|
104
|
+
const result = createSet([1, 1, 2, 2, 3]);
|
|
105
|
+
assertEqual(result, new Set([1, 2, 3]));
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it("returns new set instance each call", () => {
|
|
109
|
+
const a = createSet([1, 2, 3]);
|
|
110
|
+
const b = createSet([1, 2, 3]);
|
|
111
|
+
assertFalse(Object.is(a, b));
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
describe("addToSet", () => {
|
|
116
|
+
it("adds item to empty set", () => {
|
|
117
|
+
const set: ReadonlySet<number> = new Set();
|
|
118
|
+
const result = addToSet(set, 1);
|
|
119
|
+
assertEqual(result, new Set([1]));
|
|
120
|
+
assertType<typeof result, NonEmptyReadonlySet<number>>();
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it("adds item to non-empty set", () => {
|
|
124
|
+
const set: ReadonlySet<number> = new Set([1, 2]);
|
|
125
|
+
const result = addToSet(set, 3);
|
|
126
|
+
assertEqual(result, new Set([1, 2, 3]));
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it("does not mutate original set", () => {
|
|
130
|
+
const set: ReadonlySet<number> = new Set([1, 2]);
|
|
131
|
+
addToSet(set, 3);
|
|
132
|
+
assertEqual(set, new Set([1, 2]));
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it("returns new reference even when item already exists", () => {
|
|
136
|
+
const set: ReadonlySet<number> = new Set([1, 2]);
|
|
137
|
+
const result = addToSet(set, 2);
|
|
138
|
+
assertEqual(result, new Set([1, 2]));
|
|
139
|
+
assertFalse(Object.is(result, set));
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it("accepts mutable set and returns readonly", () => {
|
|
143
|
+
const mutableSet = new Set<number>([1, 2]);
|
|
144
|
+
const result = addToSet(mutableSet, 3);
|
|
145
|
+
assertEqual(result, new Set([1, 2, 3]));
|
|
146
|
+
assertType<typeof result, NonEmptyReadonlySet<number>>();
|
|
147
|
+
assertEqual(mutableSet, new Set([1, 2]));
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
describe("deleteFromSet", () => {
|
|
152
|
+
it("removes item from set", () => {
|
|
153
|
+
const set: ReadonlySet<number> = new Set([1, 2, 3]);
|
|
154
|
+
const result = deleteFromSet(set, 2);
|
|
155
|
+
assertEqual(result, new Set([1, 3]));
|
|
156
|
+
assertType<typeof result, ReadonlySet<number>>();
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it("does not mutate original set", () => {
|
|
160
|
+
const set: ReadonlySet<number> = new Set([1, 2, 3]);
|
|
161
|
+
deleteFromSet(set, 2);
|
|
162
|
+
assertEqual(set, new Set([1, 2, 3]));
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it("returns new reference even when item does not exist", () => {
|
|
166
|
+
const set: ReadonlySet<number> = new Set([1, 2]);
|
|
167
|
+
const result = deleteFromSet(set, 5);
|
|
168
|
+
assertEqual(result, new Set([1, 2]));
|
|
169
|
+
assertFalse(Object.is(result, set));
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
it("can delete to empty set", () => {
|
|
173
|
+
const set: ReadonlySet<number> = new Set([1]);
|
|
174
|
+
const result = deleteFromSet(set, 1);
|
|
175
|
+
assertEqual(result.size, 0);
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
it("accepts mutable set and returns readonly", () => {
|
|
179
|
+
const mutableSet = new Set<number>([1, 2, 3]);
|
|
180
|
+
const result = deleteFromSet(mutableSet, 2);
|
|
181
|
+
assertEqual(result, new Set([1, 3]));
|
|
182
|
+
assertType<typeof result, ReadonlySet<number>>();
|
|
183
|
+
assertEqual(mutableSet, new Set([1, 2, 3]));
|
|
184
|
+
});
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
describe("mapSet", () => {
|
|
188
|
+
it("maps set values", () => {
|
|
189
|
+
const set: ReadonlySet<number> = new Set([1, 2, 3]);
|
|
190
|
+
const result = mapSet(set, (item) => item * 2);
|
|
191
|
+
assertEqual(result, new Set([2, 4, 6]));
|
|
192
|
+
assertType<typeof result, ReadonlySet<number>>();
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
it("deduplicates mapped duplicates", () => {
|
|
196
|
+
const set: ReadonlySet<number> = new Set([1, 2, 3]);
|
|
197
|
+
const result = mapSet(set, (item) => item % 2);
|
|
198
|
+
assertEqual(result, new Set([1, 0]));
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
it("preserves non-empty type for non-empty input", () => {
|
|
202
|
+
const set = addToSet(emptySet, 1);
|
|
203
|
+
const result = mapSet(set, (item) => item.toString());
|
|
204
|
+
assertType<typeof result, NonEmptyReadonlySet<string>>();
|
|
205
|
+
assertEqual(result, new Set(["1"]));
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
it("does not mutate original set", () => {
|
|
209
|
+
const set: ReadonlySet<number> = new Set([1, 2, 3]);
|
|
210
|
+
mapSet(set, (item) => item + 1);
|
|
211
|
+
assertEqual(set, new Set([1, 2, 3]));
|
|
212
|
+
});
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
describe("filterSet", () => {
|
|
216
|
+
it("filters set values with predicate", () => {
|
|
217
|
+
const set: ReadonlySet<number> = new Set([1, 2, 3, 4]);
|
|
218
|
+
const result = filterSet(set, (item) => item % 2 === 0);
|
|
219
|
+
assertEqual(result, new Set([2, 4]));
|
|
220
|
+
assertType<typeof result, ReadonlySet<number>>();
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
it("passes index to predicate", () => {
|
|
224
|
+
const set: ReadonlySet<number> = new Set([10, 20, 30]);
|
|
225
|
+
const result = filterSet(set, (_item, index) => index % 2 === 0);
|
|
226
|
+
assertEqual(result, new Set([10, 30]));
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
it("supports refinement predicates", () => {
|
|
230
|
+
const set: ReadonlySet<string | number> = new Set([1, "a", 2, "b"]);
|
|
231
|
+
const result = filterSet(
|
|
232
|
+
set,
|
|
233
|
+
(item): item is string => typeof item === "string",
|
|
234
|
+
);
|
|
235
|
+
assertEqual(result, new Set(["a", "b"]));
|
|
236
|
+
assertType<typeof result, ReadonlySet<string>>();
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
it("does not mutate original set", () => {
|
|
240
|
+
const set: ReadonlySet<number> = new Set([1, 2, 3]);
|
|
241
|
+
filterSet(set, (item) => item > 1);
|
|
242
|
+
assertEqual(set, new Set([1, 2, 3]));
|
|
243
|
+
});
|
|
244
|
+
});
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
describe("Accessors", () => {
|
|
248
|
+
describe("firstInSet", () => {
|
|
249
|
+
it("requires NonEmptyReadonlySet (branded type prevents unguarded access)", () => {
|
|
250
|
+
const set: ReadonlySet<number> = new Set([1, 2, 3]);
|
|
251
|
+
// @ts-expect-error - ReadonlySet is not assignable to NonEmptyReadonlySet
|
|
252
|
+
firstInSet(set);
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
it("returns first element by insertion order", () => {
|
|
256
|
+
const set: ReadonlySet<number> = new Set([1, 2, 3]);
|
|
257
|
+
if (isNonEmptySet(set)) {
|
|
258
|
+
const result = firstInSet(set);
|
|
259
|
+
assertEqual(result, 1);
|
|
260
|
+
assertType<typeof result, number>();
|
|
261
|
+
}
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
it("returns only element from single element set", () => {
|
|
265
|
+
const set: ReadonlySet<string> = new Set(["only"]);
|
|
266
|
+
if (isNonEmptySet(set)) {
|
|
267
|
+
assertEqual(firstInSet(set), "only");
|
|
268
|
+
}
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
it("does not mutate original set", () => {
|
|
272
|
+
const set: ReadonlySet<number> = new Set([1, 2, 3]);
|
|
273
|
+
if (isNonEmptySet(set)) {
|
|
274
|
+
firstInSet(set);
|
|
275
|
+
assertEqual(set, new Set([1, 2, 3]));
|
|
276
|
+
}
|
|
277
|
+
});
|
|
278
|
+
});
|
|
279
|
+
});
|
package/src/Set.ts
CHANGED
|
@@ -69,8 +69,8 @@ export const emptySet: ReadonlySet<never> = /*#__PURE__*/ new Set();
|
|
|
69
69
|
* const values = createSet([1, 2, 3]);
|
|
70
70
|
* const empty = createSet([] as ReadonlyArray<number>);
|
|
71
71
|
*
|
|
72
|
-
* assertType<NonEmptyReadonlySet<number
|
|
73
|
-
* assertType<ReadonlySet<number
|
|
72
|
+
* assertType<typeof values, NonEmptyReadonlySet<number>>();
|
|
73
|
+
* assertType<typeof empty, ReadonlySet<number>>();
|
|
74
74
|
* assertEqual(values, new Set([1, 2, 3]));
|
|
75
75
|
* ```
|
|
76
76
|
*
|
|
@@ -124,7 +124,7 @@ export type NonEmptyReadonlySet<T> = ReadonlySet<T> & Brand<"NonEmpty">;
|
|
|
124
124
|
* const set: ReadonlySet<number> = new Set([1, 2, 3]);
|
|
125
125
|
* if (!isNonEmptySet(set)) throw new Error("Expected a non-empty set");
|
|
126
126
|
*
|
|
127
|
-
* assertType<NonEmptyReadonlySet<number
|
|
127
|
+
* assertType<typeof set, NonEmptyReadonlySet<number>>();
|
|
128
128
|
* assertEqual(set.size, 3);
|
|
129
129
|
* ```
|
|
130
130
|
*
|
|
@@ -155,7 +155,7 @@ export const isNonEmptySet = <T>(
|
|
|
155
155
|
* const added = addToSet(original, 3);
|
|
156
156
|
* const unchanged = addToSet(original, 2);
|
|
157
157
|
*
|
|
158
|
-
* assertType<NonEmptyReadonlySet<number
|
|
158
|
+
* assertType<typeof added, NonEmptyReadonlySet<number>>();
|
|
159
159
|
* assertEqual(added, new Set([1, 2, 3]));
|
|
160
160
|
* assertTrue(unchanged !== original);
|
|
161
161
|
* ```
|
|
@@ -221,7 +221,7 @@ export const deleteFromSet = <T>(
|
|
|
221
221
|
* const doubled = mapSet(original, (x) => x * 2);
|
|
222
222
|
* const parity = mapSet(original, (x) => x % 2);
|
|
223
223
|
*
|
|
224
|
-
* assertType<NonEmptyReadonlySet<number
|
|
224
|
+
* assertType<typeof doubled, NonEmptyReadonlySet<number>>();
|
|
225
225
|
* assertEqual(doubled, new Set([2, 4, 6]));
|
|
226
226
|
* assertEqual(parity, new Set([1, 0]));
|
|
227
227
|
* ```
|
|
@@ -268,7 +268,7 @@ export function mapSet<T, U>(
|
|
|
268
268
|
* mixed,
|
|
269
269
|
* (value): value is string => typeof value === "string",
|
|
270
270
|
* );
|
|
271
|
-
* assertType<ReadonlySet<string
|
|
271
|
+
* assertType<typeof strings, ReadonlySet<string>>();
|
|
272
272
|
* assertEqual(strings, new Set(["a", "b"]));
|
|
273
273
|
* ```
|
|
274
274
|
*
|
package/src/Sqlite.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import type { Brand } from "./Brand.ts";
|
|
8
|
-
import { bytesToHex, hexToBytes } from "./
|
|
8
|
+
import { bytesToHex, hexToBytes } from "./Binary.ts";
|
|
9
9
|
import type { EncryptionKey } from "./Crypto.ts";
|
|
10
10
|
import type { Eq } from "./Eq.ts";
|
|
11
11
|
import { createEqObject, eqString, eqUint8Array } from "./Eq.ts";
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
import { describe, it, mock } from "node:test";
|
|
2
|
+
import { assertEqual } from "./Assert.ts";
|
|
3
|
+
|
|
4
|
+
import { createStore } from "./Store.ts";
|
|
5
|
+
|
|
6
|
+
describe("get", () => {
|
|
7
|
+
it("get returns initial state", () => {
|
|
8
|
+
const store = createStore(42);
|
|
9
|
+
assertEqual(store.get(), 42);
|
|
10
|
+
});
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
describe("set", () => {
|
|
14
|
+
it("updates state", () => {
|
|
15
|
+
const store = createStore(0);
|
|
16
|
+
store.set(1);
|
|
17
|
+
assertEqual(store.get(), 1);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it("notifies listeners when state changes", () => {
|
|
21
|
+
const store = createStore(0);
|
|
22
|
+
const listener = mock.fn<() => void>();
|
|
23
|
+
store.subscribe(listener);
|
|
24
|
+
|
|
25
|
+
store.set(1);
|
|
26
|
+
|
|
27
|
+
assertEqual(listener.mock.callCount(), 1);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("does not notify listeners when state is equal", () => {
|
|
31
|
+
const store = createStore(1);
|
|
32
|
+
const listener = mock.fn<() => void>();
|
|
33
|
+
store.subscribe(listener);
|
|
34
|
+
|
|
35
|
+
store.set(1);
|
|
36
|
+
|
|
37
|
+
assertEqual(listener.mock.callCount(), 0);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("does not notify listeners when repeated state is NaN", () => {
|
|
41
|
+
const store = createStore(NaN);
|
|
42
|
+
const listener = mock.fn<() => void>();
|
|
43
|
+
store.subscribe(listener);
|
|
44
|
+
|
|
45
|
+
store.set(NaN);
|
|
46
|
+
|
|
47
|
+
assertEqual(listener.mock.callCount(), 0);
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
describe("getAndSet", () => {
|
|
52
|
+
it("returns previous state and updates state", () => {
|
|
53
|
+
const store = createStore(1);
|
|
54
|
+
|
|
55
|
+
assertEqual(store.getAndSet(2), 1);
|
|
56
|
+
assertEqual(store.get(), 2);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it("notifies listeners when state changes", () => {
|
|
60
|
+
const store = createStore(1);
|
|
61
|
+
const listener = mock.fn<() => void>();
|
|
62
|
+
store.subscribe(listener);
|
|
63
|
+
|
|
64
|
+
assertEqual(store.getAndSet(2), 1);
|
|
65
|
+
assertEqual(listener.mock.callCount(), 1);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it("does not notify listeners when state is equal", () => {
|
|
69
|
+
const store = createStore(1);
|
|
70
|
+
const listener = mock.fn<() => void>();
|
|
71
|
+
store.subscribe(listener);
|
|
72
|
+
|
|
73
|
+
assertEqual(store.getAndSet(1), 1);
|
|
74
|
+
assertEqual(listener.mock.callCount(), 0);
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
describe("setAndGet", () => {
|
|
79
|
+
it("returns updated state", () => {
|
|
80
|
+
const store = createStore(1);
|
|
81
|
+
|
|
82
|
+
assertEqual(store.setAndGet(2), 2);
|
|
83
|
+
assertEqual(store.get(), 2);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it("does not notify listeners when state is equal", () => {
|
|
87
|
+
const store = createStore(1);
|
|
88
|
+
const listener = mock.fn<() => void>();
|
|
89
|
+
store.subscribe(listener);
|
|
90
|
+
|
|
91
|
+
assertEqual(store.setAndGet(1), 1);
|
|
92
|
+
assertEqual(listener.mock.callCount(), 0);
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
describe("update", () => {
|
|
97
|
+
it("updates state", () => {
|
|
98
|
+
const store = createStore(1);
|
|
99
|
+
|
|
100
|
+
store.update((n) => n + 1);
|
|
101
|
+
|
|
102
|
+
assertEqual(store.get(), 2);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it("notifies listeners when state changes", () => {
|
|
106
|
+
const store = createStore(1);
|
|
107
|
+
const listener = mock.fn<() => void>();
|
|
108
|
+
store.subscribe(listener);
|
|
109
|
+
|
|
110
|
+
store.update((n) => n + 1);
|
|
111
|
+
|
|
112
|
+
assertEqual(listener.mock.callCount(), 1);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it("does not notify listeners when state is equal", () => {
|
|
116
|
+
const store = createStore(1);
|
|
117
|
+
const listener = mock.fn<() => void>();
|
|
118
|
+
store.subscribe(listener);
|
|
119
|
+
|
|
120
|
+
store.update((n) => n);
|
|
121
|
+
|
|
122
|
+
assertEqual(listener.mock.callCount(), 0);
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
describe("getAndUpdate", () => {
|
|
127
|
+
it("returns previous state and updates state", () => {
|
|
128
|
+
const store = createStore(1);
|
|
129
|
+
|
|
130
|
+
assertEqual(
|
|
131
|
+
store.getAndUpdate((n: number) => n + 1),
|
|
132
|
+
1,
|
|
133
|
+
);
|
|
134
|
+
assertEqual(store.get(), 2);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it("notifies listeners when state changes", () => {
|
|
138
|
+
const store = createStore(1);
|
|
139
|
+
const listener = mock.fn<() => void>();
|
|
140
|
+
store.subscribe(listener);
|
|
141
|
+
|
|
142
|
+
assertEqual(
|
|
143
|
+
store.getAndUpdate((n: number) => n + 1),
|
|
144
|
+
1,
|
|
145
|
+
);
|
|
146
|
+
assertEqual(listener.mock.callCount(), 1);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it("does not notify listeners when state is equal", () => {
|
|
150
|
+
const store = createStore(1);
|
|
151
|
+
const listener = mock.fn<() => void>();
|
|
152
|
+
store.subscribe(listener);
|
|
153
|
+
|
|
154
|
+
assertEqual(
|
|
155
|
+
store.getAndUpdate((n) => n),
|
|
156
|
+
1,
|
|
157
|
+
);
|
|
158
|
+
assertEqual(listener.mock.callCount(), 0);
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
describe("updateAndGet", () => {
|
|
163
|
+
it("returns updated state", () => {
|
|
164
|
+
const store = createStore(1);
|
|
165
|
+
|
|
166
|
+
assertEqual(
|
|
167
|
+
store.updateAndGet((n: number) => n + 1),
|
|
168
|
+
2,
|
|
169
|
+
);
|
|
170
|
+
assertEqual(store.get(), 2);
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it("does not notify listeners when state is equal", () => {
|
|
174
|
+
const store = createStore(1);
|
|
175
|
+
const listener = mock.fn<() => void>();
|
|
176
|
+
store.subscribe(listener);
|
|
177
|
+
|
|
178
|
+
assertEqual(
|
|
179
|
+
store.updateAndGet((n) => n),
|
|
180
|
+
1,
|
|
181
|
+
);
|
|
182
|
+
assertEqual(listener.mock.callCount(), 0);
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
describe("modify", () => {
|
|
187
|
+
it("returns a computed result and updates state", () => {
|
|
188
|
+
const store = createStore(0);
|
|
189
|
+
const result = store.modify((current) => [current, current + 1]);
|
|
190
|
+
|
|
191
|
+
assertEqual(result, 0);
|
|
192
|
+
assertEqual(store.get(), 1);
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
it("returns computed result and updates state", () => {
|
|
196
|
+
const store = createStore(1);
|
|
197
|
+
|
|
198
|
+
const result = store.modify((current: number) => [
|
|
199
|
+
`previous:${current}`,
|
|
200
|
+
current + 1,
|
|
201
|
+
]);
|
|
202
|
+
|
|
203
|
+
assertEqual(result, "previous:1");
|
|
204
|
+
assertEqual(store.get(), 2);
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
it("notifies listeners when state changes", () => {
|
|
208
|
+
const store = createStore(0);
|
|
209
|
+
const listener = mock.fn<() => void>();
|
|
210
|
+
store.subscribe(listener);
|
|
211
|
+
|
|
212
|
+
const result = store.modify((current: number) => [current, current + 1]);
|
|
213
|
+
|
|
214
|
+
assertEqual(result, 0);
|
|
215
|
+
assertEqual(listener.mock.callCount(), 1);
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
it("does not notify listeners when next state is equal", () => {
|
|
219
|
+
const store = createStore(1);
|
|
220
|
+
const listener = mock.fn<() => void>();
|
|
221
|
+
store.subscribe(listener);
|
|
222
|
+
|
|
223
|
+
const result = store.modify((current) => [current, current]);
|
|
224
|
+
|
|
225
|
+
assertEqual(result, 1);
|
|
226
|
+
assertEqual(listener.mock.callCount(), 0);
|
|
227
|
+
});
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
describe("subscribe", () => {
|
|
231
|
+
it("returns unsubscribe function", () => {
|
|
232
|
+
const store = createStore(0);
|
|
233
|
+
const listener = mock.fn<() => void>();
|
|
234
|
+
const unsubscribe = store.subscribe(listener);
|
|
235
|
+
|
|
236
|
+
store.set(1);
|
|
237
|
+
assertEqual(listener.mock.callCount(), 1);
|
|
238
|
+
|
|
239
|
+
unsubscribe();
|
|
240
|
+
store.set(2);
|
|
241
|
+
assertEqual(listener.mock.callCount(), 1);
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
it("supports multiple listeners", () => {
|
|
245
|
+
const store = createStore(0);
|
|
246
|
+
const listener1 = mock.fn<() => void>();
|
|
247
|
+
const listener2 = mock.fn<() => void>();
|
|
248
|
+
|
|
249
|
+
store.subscribe(listener1);
|
|
250
|
+
store.subscribe(listener2);
|
|
251
|
+
|
|
252
|
+
store.set(1);
|
|
253
|
+
|
|
254
|
+
assertEqual(listener1.mock.callCount(), 1);
|
|
255
|
+
assertEqual(listener2.mock.callCount(), 1);
|
|
256
|
+
});
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
describe("dispose", () => {
|
|
260
|
+
it("clears all listeners", () => {
|
|
261
|
+
const store = createStore(0);
|
|
262
|
+
const listener = mock.fn<() => void>();
|
|
263
|
+
store.subscribe(listener);
|
|
264
|
+
|
|
265
|
+
store[Symbol.dispose]();
|
|
266
|
+
store.set(1);
|
|
267
|
+
|
|
268
|
+
assertEqual(listener.mock.callCount(), 0);
|
|
269
|
+
});
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
describe("custom eq", () => {
|
|
273
|
+
it("suppresses notifications for equal states under the provided equality", () => {
|
|
274
|
+
const eqModulo10 = (a: number, b: number) => a % 10 === b % 10;
|
|
275
|
+
const store = createStore<number>(5, eqModulo10);
|
|
276
|
+
|
|
277
|
+
const listener = mock.fn<() => void>();
|
|
278
|
+
store.subscribe(listener);
|
|
279
|
+
|
|
280
|
+
store.set(15);
|
|
281
|
+
assertEqual(store.get(), 15);
|
|
282
|
+
assertEqual(listener.mock.callCount(), 0);
|
|
283
|
+
|
|
284
|
+
store.set(16);
|
|
285
|
+
assertEqual(store.get(), 16);
|
|
286
|
+
assertEqual(listener.mock.callCount(), 1);
|
|
287
|
+
});
|
|
288
|
+
});
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { test } from "node:test";
|
|
2
|
+
import { assertEqual, assertSame, assertTrue } from "./Assert.ts";
|
|
3
|
+
|
|
4
|
+
import { escapeRegExp, safelyStringifyUnknownValue } from "./String.ts";
|
|
5
|
+
|
|
6
|
+
test("escapeRegExp", () => {
|
|
7
|
+
const value = "value.*+?^${}()|[]\\end";
|
|
8
|
+
|
|
9
|
+
assertEqual(
|
|
10
|
+
escapeRegExp(value),
|
|
11
|
+
"value\\.\\*\\+\\?\\^\\$\\{\\}\\(\\)\\|\\[\\]\\\\end",
|
|
12
|
+
);
|
|
13
|
+
assertTrue(new RegExp(`^${escapeRegExp(value)}$`, "u").test(value));
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
test("safelyStringifyUnknownValue", () => {
|
|
17
|
+
assertEqual(
|
|
18
|
+
safelyStringifyUnknownValue('line 1\n"line 2"'),
|
|
19
|
+
'"line 1\\n\\"line 2\\""',
|
|
20
|
+
);
|
|
21
|
+
assertEqual(safelyStringifyUnknownValue(undefined), "undefined");
|
|
22
|
+
assertEqual(safelyStringifyUnknownValue(null), "null");
|
|
23
|
+
assertEqual(safelyStringifyUnknownValue(true), "true");
|
|
24
|
+
assertEqual(safelyStringifyUnknownValue(NaN), "NaN");
|
|
25
|
+
assertEqual(safelyStringifyUnknownValue(Infinity), "Infinity");
|
|
26
|
+
assertEqual(safelyStringifyUnknownValue(42n), "42");
|
|
27
|
+
assertEqual(safelyStringifyUnknownValue(Symbol("id")), "Symbol(id)");
|
|
28
|
+
|
|
29
|
+
const fn = (): undefined => undefined;
|
|
30
|
+
assertSame(safelyStringifyUnknownValue(fn), String(fn));
|
|
31
|
+
assertEqual(safelyStringifyUnknownValue({ answer: 42 }), '{"answer":42}');
|
|
32
|
+
assertEqual(
|
|
33
|
+
safelyStringifyUnknownValue({ toJSON: () => undefined }),
|
|
34
|
+
"[object Object]",
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
const circularValue: { circular?: unknown } = {};
|
|
38
|
+
circularValue.circular = circularValue;
|
|
39
|
+
assertEqual(safelyStringifyUnknownValue(circularValue), "[object Object]");
|
|
40
|
+
|
|
41
|
+
const unserializableValue: {
|
|
42
|
+
self?: unknown;
|
|
43
|
+
readonly toString: () => never;
|
|
44
|
+
} = {
|
|
45
|
+
toString: () => {
|
|
46
|
+
throw new Error("Cannot stringify.");
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
unserializableValue.self = unserializableValue;
|
|
50
|
+
assertEqual(
|
|
51
|
+
safelyStringifyUnknownValue(unserializableValue),
|
|
52
|
+
"[Unserializable value]",
|
|
53
|
+
);
|
|
54
|
+
});
|
package/src/String.ts
CHANGED
|
@@ -14,8 +14,7 @@ export const escapeRegExp = (value: string): string =>
|
|
|
14
14
|
/** Converts an unknown value to a compact, human-readable diagnostic string. */
|
|
15
15
|
export const safelyStringifyUnknownValue = (value: unknown): string => {
|
|
16
16
|
if (typeof value === "string") return JSON.stringify(value);
|
|
17
|
-
if (value === null || typeof value !== "object")
|
|
18
|
-
return globalThis.String(value);
|
|
17
|
+
if (value === null || typeof value !== "object") return String(value);
|
|
19
18
|
|
|
20
19
|
try {
|
|
21
20
|
// `JSON.stringify` can return `undefined` when `toJSON` does.
|
|
@@ -26,7 +25,8 @@ export const safelyStringifyUnknownValue = (value: unknown): string => {
|
|
|
26
25
|
}
|
|
27
26
|
|
|
28
27
|
try {
|
|
29
|
-
|
|
28
|
+
// oxlint-disable-next-line typescript/no-base-to-string -- This is the intentional last-resort representation.
|
|
29
|
+
return String(value);
|
|
30
30
|
} catch {
|
|
31
31
|
return "[Unserializable value]";
|
|
32
32
|
}
|