@evolu/common 8.6.2 → 8.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/Array.d.ts +21 -21
- package/dist/src/Array.js +5 -5
- package/dist/src/Assert.d.ts +478 -141
- package/dist/src/Assert.d.ts.map +1 -1
- package/dist/src/Assert.js +644 -99
- package/dist/src/Binary.d.ts +254 -0
- package/dist/src/Binary.d.ts.map +1 -0
- package/dist/src/Binary.js +958 -0
- package/dist/src/Console.d.ts +2 -10
- package/dist/src/Console.d.ts.map +1 -1
- package/dist/src/Console.js +6 -12
- package/dist/src/Crypto.d.ts +5 -5
- package/dist/src/Crypto.js +1 -1
- package/dist/src/Eq.d.ts +35 -40
- package/dist/src/Eq.d.ts.map +1 -1
- package/dist/src/Eq.js +51 -87
- package/dist/src/Function.d.ts +9 -14
- package/dist/src/Function.d.ts.map +1 -1
- package/dist/src/Function.js +7 -9
- package/dist/src/Http.d.ts +6 -7
- package/dist/src/Http.d.ts.map +1 -1
- package/dist/src/LeakDetector.d.ts.map +1 -1
- package/dist/src/LeakDetector.js +1 -0
- package/dist/src/LockManager.d.ts +1 -1
- package/dist/src/LockManager.js +1 -1
- package/dist/src/Lookup.d.ts +3 -2
- package/dist/src/Lookup.d.ts.map +1 -1
- package/dist/src/Lookup.js +4 -3
- package/dist/src/Number.d.ts +1 -1
- package/dist/src/Number.d.ts.map +1 -1
- package/dist/src/Number.js +1 -1
- package/dist/src/Object.d.ts +4 -4
- package/dist/src/Object.d.ts.map +1 -1
- package/dist/src/Object.js +5 -4
- package/dist/src/Option.d.ts +1 -2
- package/dist/src/Option.d.ts.map +1 -1
- package/dist/src/Option.js +1 -2
- package/dist/src/Platform.js +2 -0
- package/dist/src/Polyfills.js +4 -1
- package/dist/src/Random.d.ts +1 -1
- package/dist/src/Result.d.ts +49 -54
- package/dist/src/Result.d.ts.map +1 -1
- package/dist/src/Result.js +17 -22
- package/dist/src/Set.d.ts +6 -6
- package/dist/src/Set.js +2 -2
- package/dist/src/Sqlite.js +1 -1
- package/dist/src/String.js +3 -2
- package/dist/src/Task.d.ts +32 -32
- package/dist/src/Task.d.ts.map +1 -1
- package/dist/src/Task.js +20 -22
- package/dist/src/Test.d.ts +31 -3
- package/dist/src/Test.d.ts.map +1 -1
- package/dist/src/Test.js +46 -3
- package/dist/src/Time.d.ts +3 -11
- package/dist/src/Time.d.ts.map +1 -1
- package/dist/src/Time.js +10 -18
- package/dist/src/Type.d.ts +87 -85
- package/dist/src/Type.d.ts.map +1 -1
- package/dist/src/Type.js +70 -63
- package/dist/src/Types.d.ts +12 -12
- package/dist/src/Types.js +1 -1
- package/dist/src/WebSocket.d.ts +4 -7
- package/dist/src/WebSocket.d.ts.map +1 -1
- package/dist/src/Worker.d.ts +1 -1
- package/dist/src/Worker.js +7 -7
- package/dist/src/index.d.ts +1 -1
- package/dist/src/index.js +1 -1
- package/dist/src/intl/_en.d.ts.map +1 -1
- package/dist/src/intl/_en.js +1 -1
- package/dist/src/intl/ar.d.ts.map +1 -1
- package/dist/src/intl/ar.js +1 -1
- package/dist/src/intl/bn.d.ts.map +1 -1
- package/dist/src/intl/bn.js +1 -1
- package/dist/src/intl/ca.d.ts.map +1 -1
- package/dist/src/intl/ca.js +1 -1
- package/dist/src/intl/cs.d.ts.map +1 -1
- package/dist/src/intl/cs.js +1 -1
- package/dist/src/intl/da.d.ts.map +1 -1
- package/dist/src/intl/da.js +1 -1
- package/dist/src/intl/de.d.ts.map +1 -1
- package/dist/src/intl/de.js +1 -1
- package/dist/src/intl/el.d.ts.map +1 -1
- package/dist/src/intl/el.js +1 -1
- package/dist/src/intl/es.d.ts.map +1 -1
- package/dist/src/intl/es.js +1 -1
- package/dist/src/intl/fa.d.ts.map +1 -1
- package/dist/src/intl/fa.js +1 -1
- package/dist/src/intl/fi.d.ts.map +1 -1
- package/dist/src/intl/fi.js +1 -1
- package/dist/src/intl/fil.d.ts.map +1 -1
- package/dist/src/intl/fil.js +1 -1
- package/dist/src/intl/fr.d.ts.map +1 -1
- package/dist/src/intl/fr.js +1 -1
- package/dist/src/intl/he.d.ts.map +1 -1
- package/dist/src/intl/he.js +1 -1
- package/dist/src/intl/hi.d.ts.map +1 -1
- package/dist/src/intl/hi.js +1 -1
- package/dist/src/intl/hr.d.ts.map +1 -1
- package/dist/src/intl/hr.js +1 -1
- package/dist/src/intl/hu.d.ts.map +1 -1
- package/dist/src/intl/hu.js +1 -1
- package/dist/src/intl/id.d.ts.map +1 -1
- package/dist/src/intl/id.js +1 -1
- package/dist/src/intl/it.d.ts.map +1 -1
- package/dist/src/intl/it.js +1 -1
- package/dist/src/intl/ja.d.ts.map +1 -1
- package/dist/src/intl/ja.js +1 -1
- package/dist/src/intl/ko.d.ts.map +1 -1
- package/dist/src/intl/ko.js +1 -1
- package/dist/src/intl/ml.d.ts.map +1 -1
- package/dist/src/intl/ml.js +1 -1
- package/dist/src/intl/mr.d.ts.map +1 -1
- package/dist/src/intl/mr.js +1 -1
- package/dist/src/intl/ms.d.ts.map +1 -1
- package/dist/src/intl/ms.js +1 -1
- package/dist/src/intl/nb.d.ts.map +1 -1
- package/dist/src/intl/nb.js +1 -1
- package/dist/src/intl/nl.d.ts.map +1 -1
- package/dist/src/intl/nl.js +1 -1
- package/dist/src/intl/pa.d.ts.map +1 -1
- package/dist/src/intl/pa.js +1 -1
- package/dist/src/intl/pl.d.ts.map +1 -1
- package/dist/src/intl/pl.js +1 -1
- package/dist/src/intl/pt-BR.d.ts.map +1 -1
- package/dist/src/intl/pt-BR.js +1 -1
- package/dist/src/intl/pt.d.ts.map +1 -1
- package/dist/src/intl/pt.js +1 -1
- package/dist/src/intl/ro.d.ts.map +1 -1
- package/dist/src/intl/ro.js +1 -1
- package/dist/src/intl/sk.d.ts.map +1 -1
- package/dist/src/intl/sk.js +1 -1
- package/dist/src/intl/sl.d.ts.map +1 -1
- package/dist/src/intl/sl.js +1 -1
- package/dist/src/intl/sv.d.ts.map +1 -1
- package/dist/src/intl/sv.js +1 -1
- package/dist/src/intl/sw.d.ts.map +1 -1
- package/dist/src/intl/sw.js +1 -1
- package/dist/src/intl/ta.d.ts.map +1 -1
- package/dist/src/intl/ta.js +1 -1
- package/dist/src/intl/te.d.ts.map +1 -1
- package/dist/src/intl/te.js +1 -1
- package/dist/src/intl/th.d.ts.map +1 -1
- package/dist/src/intl/th.js +1 -1
- package/dist/src/intl/tr.d.ts.map +1 -1
- package/dist/src/intl/tr.js +1 -1
- package/dist/src/intl/uk.d.ts.map +1 -1
- package/dist/src/intl/uk.js +1 -1
- package/dist/src/intl/ur.d.ts.map +1 -1
- package/dist/src/intl/ur.js +1 -1
- package/dist/src/intl/vi.d.ts.map +1 -1
- package/dist/src/intl/vi.js +1 -1
- package/dist/src/intl/zh-CN.d.ts.map +1 -1
- package/dist/src/intl/zh-CN.js +1 -1
- package/dist/src/intl/zh-TW.d.ts.map +1 -1
- package/dist/src/intl/zh-TW.js +1 -1
- package/dist/src/local-first/Evolu.d.ts +12 -12
- package/dist/src/local-first/Evolu.d.ts.map +1 -1
- package/dist/src/local-first/Evolu.js +2 -2
- package/dist/src/local-first/Protocol.d.ts +28 -76
- package/dist/src/local-first/Protocol.d.ts.map +1 -1
- package/dist/src/local-first/Protocol.js +45 -229
- 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 -5
- package/src/Array.test.ts +809 -0
- package/src/Array.ts +21 -21
- package/src/Assert.test.ts +1181 -0
- package/src/Assert.ts +971 -227
- package/src/BigInt.test.ts +28 -0
- package/src/Binary.test.ts +1128 -0
- package/src/Binary.ts +1377 -0
- package/src/Brand.test.ts +90 -0
- package/src/Cache.test.ts +183 -0
- package/src/Callbacks.test.ts +66 -0
- package/src/Console.test.ts +809 -0
- package/src/Console.ts +9 -17
- package/src/Crypto.test.ts +138 -0
- package/src/Crypto.ts +5 -5
- package/src/Eq.test.ts +333 -0
- package/src/Eq.ts +110 -176
- package/src/Error.test.ts +118 -0
- package/src/Function.test.ts +204 -0
- package/src/Function.ts +10 -15
- package/src/Http.test.ts +816 -0
- package/src/Http.ts +6 -7
- package/src/LeakDetector.test.ts +210 -0
- package/src/LeakDetector.ts +1 -0
- package/src/LockManager.ts +2 -2
- package/src/Lookup.test.ts +418 -0
- package/src/Lookup.ts +4 -3
- package/src/Microtask.test.ts +140 -0
- package/src/Number.test.ts +218 -0
- package/src/Number.ts +2 -2
- package/src/Object.test.ts +293 -0
- package/src/Object.ts +5 -4
- package/src/Option.test.ts +104 -0
- package/src/Option.ts +1 -2
- package/src/Order.test.ts +56 -0
- package/src/Platform.ts +2 -0
- package/src/Polyfills.ts +3 -4
- package/src/Random.test.ts +89 -0
- package/src/Random.ts +1 -1
- package/src/Ref.test.ts +139 -0
- package/src/RefCount.test.ts +184 -0
- package/src/Relation.test.ts +505 -0
- package/src/Resource.test.ts +4623 -0
- package/src/Result.test.ts +1829 -0
- package/src/Result.ts +49 -54
- package/src/Schedule.test.ts +1554 -0
- package/src/Set.test.ts +279 -0
- package/src/Set.ts +6 -6
- package/src/Sqlite.ts +1 -1
- package/src/Store.test.ts +288 -0
- package/src/String.test.ts +54 -0
- package/src/String.ts +3 -3
- package/src/Task.test.ts +10820 -0
- package/src/Task.ts +38 -43
- package/src/Test.test.ts +88 -0
- package/src/Test.ts +49 -3
- package/src/Time.test.ts +926 -0
- package/src/Time.ts +13 -21
- package/src/Type.base64.test.ts +88 -0
- package/src/Type.test.ts +20465 -0
- package/src/Type.ts +114 -111
- package/src/Types.test.ts +324 -0
- package/src/Types.ts +13 -13
- package/src/WebSocket.ts +4 -7
- package/src/Worker.test.ts +755 -0
- package/src/Worker.ts +13 -18
- package/src/index.ts +1 -1
- package/src/intl/_en.ts +1 -1
- package/src/intl/ar.ts +1 -1
- package/src/intl/bn.ts +1 -1
- package/src/intl/ca.ts +1 -1
- package/src/intl/cs.ts +1 -1
- package/src/intl/da.ts +1 -1
- package/src/intl/de.ts +1 -1
- package/src/intl/el.ts +1 -1
- package/src/intl/es.ts +1 -1
- package/src/intl/fa.ts +1 -1
- package/src/intl/fi.ts +1 -1
- package/src/intl/fil.ts +1 -1
- package/src/intl/fr.ts +1 -1
- package/src/intl/he.ts +1 -1
- package/src/intl/hi.ts +1 -1
- package/src/intl/hr.ts +1 -1
- package/src/intl/hu.ts +1 -1
- package/src/intl/id.ts +1 -1
- package/src/intl/intl.test.ts +3106 -0
- package/src/intl/it.ts +1 -1
- package/src/intl/ja.ts +1 -1
- package/src/intl/ko.ts +1 -1
- package/src/intl/ml.ts +1 -1
- package/src/intl/mr.ts +1 -1
- package/src/intl/ms.ts +1 -1
- package/src/intl/nb.ts +1 -1
- package/src/intl/nl.ts +1 -1
- package/src/intl/pa.ts +1 -1
- package/src/intl/pl.ts +1 -1
- package/src/intl/pt-BR.ts +1 -1
- package/src/intl/pt.ts +1 -1
- package/src/intl/ro.ts +1 -1
- package/src/intl/sk.ts +1 -1
- package/src/intl/sl.ts +1 -1
- package/src/intl/sv.ts +1 -1
- package/src/intl/sw.ts +1 -1
- package/src/intl/ta.ts +1 -1
- package/src/intl/te.ts +1 -1
- package/src/intl/th.ts +1 -1
- package/src/intl/tr.ts +1 -1
- package/src/intl/uk.ts +1 -1
- package/src/intl/ur.ts +1 -1
- package/src/intl/vi.ts +1 -1
- package/src/intl/zh-CN.ts +1 -1
- package/src/intl/zh-TW.ts +1 -1
- package/src/local-first/Evolu.test.ts +1921 -0
- package/src/local-first/Evolu.ts +18 -14
- package/src/local-first/Owner.test.ts +83 -0
- package/src/local-first/Protocol.test.ts +1037 -0
- package/src/local-first/Protocol.ts +75 -290
- 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 +0 -122
- package/dist/src/Buffer.d.ts.map +0 -1
- package/dist/src/Buffer.js +0 -69
- package/src/Buffer.ts +0 -206
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { describe, it } from "node:test";
|
|
2
|
+
import { assertEqual, assertThrowsInstanceOf, assertTrue } from "./Assert.ts";
|
|
3
|
+
|
|
4
|
+
import { createMicrotaskBatch } from "./Microtask.ts";
|
|
5
|
+
|
|
6
|
+
describe("createMicrotaskBatch", () => {
|
|
7
|
+
it("coalesces multiple pushes in one tick into one flush", async () => {
|
|
8
|
+
const flushed: Array<ReadonlyArray<number>> = [];
|
|
9
|
+
const batch = createMicrotaskBatch<number>((items) => {
|
|
10
|
+
flushed.push(items);
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
batch.push(1);
|
|
14
|
+
batch.push(2);
|
|
15
|
+
|
|
16
|
+
assertEqual(flushed, []);
|
|
17
|
+
|
|
18
|
+
await Promise.resolve();
|
|
19
|
+
|
|
20
|
+
assertEqual(flushed, [[1, 2]]);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it("flushNow flushes immediately and pending microtask becomes no-op", async () => {
|
|
24
|
+
const flushed: Array<ReadonlyArray<number>> = [];
|
|
25
|
+
const batch = createMicrotaskBatch<number>((items) => {
|
|
26
|
+
flushed.push(items);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
batch.push(1);
|
|
30
|
+
batch.flushNow();
|
|
31
|
+
|
|
32
|
+
assertEqual(flushed, [[1]]);
|
|
33
|
+
|
|
34
|
+
await Promise.resolve();
|
|
35
|
+
|
|
36
|
+
assertEqual(flushed, [[1]]);
|
|
37
|
+
|
|
38
|
+
batch.push(2);
|
|
39
|
+
await Promise.resolve();
|
|
40
|
+
|
|
41
|
+
assertEqual(flushed, [[1], [2]]);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it("flushNow on empty queue does nothing", () => {
|
|
45
|
+
const flushed: Array<ReadonlyArray<number>> = [];
|
|
46
|
+
const batch = createMicrotaskBatch<number>((items) => {
|
|
47
|
+
flushed.push(items);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
batch.flushNow();
|
|
51
|
+
|
|
52
|
+
assertEqual(flushed, []);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("passes snapshot so previous flushed arrays stay unchanged", async () => {
|
|
56
|
+
const flushed: Array<ReadonlyArray<number>> = [];
|
|
57
|
+
const batch = createMicrotaskBatch<number>((items) => {
|
|
58
|
+
flushed.push(items);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
batch.push(1);
|
|
62
|
+
batch.push(2);
|
|
63
|
+
await Promise.resolve();
|
|
64
|
+
|
|
65
|
+
batch.push(3);
|
|
66
|
+
await Promise.resolve();
|
|
67
|
+
|
|
68
|
+
assertEqual(flushed[0], [1, 2]);
|
|
69
|
+
assertEqual(flushed[1], [3]);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it("reentrant push during onFlush is processed in next microtask", async () => {
|
|
73
|
+
const flushed: Array<ReadonlyArray<number>> = [];
|
|
74
|
+
const batch = createMicrotaskBatch<number>((items) => {
|
|
75
|
+
flushed.push(items);
|
|
76
|
+
if (items[0] === 1) {
|
|
77
|
+
batch.push(2);
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
batch.push(1);
|
|
82
|
+
|
|
83
|
+
await Promise.resolve();
|
|
84
|
+
await Promise.resolve();
|
|
85
|
+
|
|
86
|
+
assertEqual(flushed, [[1], [2]]);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it("dispose cancels queued flush", async () => {
|
|
90
|
+
const flushed: Array<ReadonlyArray<number>> = [];
|
|
91
|
+
const batch = createMicrotaskBatch<number>((items) => {
|
|
92
|
+
flushed.push(items);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
batch.push(1);
|
|
96
|
+
batch[Symbol.dispose]();
|
|
97
|
+
|
|
98
|
+
await Promise.resolve();
|
|
99
|
+
|
|
100
|
+
assertEqual(flushed, []);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it("push and flushNow throw after dispose", async () => {
|
|
104
|
+
const flushed: Array<ReadonlyArray<number>> = [];
|
|
105
|
+
const batch = createMicrotaskBatch<number>((items) => {
|
|
106
|
+
flushed.push(items);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
batch[Symbol.dispose]();
|
|
110
|
+
|
|
111
|
+
const pushError = assertThrowsInstanceOf(() => {
|
|
112
|
+
batch.push(1);
|
|
113
|
+
}, Error);
|
|
114
|
+
assertTrue(pushError.message.includes("Cannot use a disposed object."));
|
|
115
|
+
|
|
116
|
+
const flushNowError = assertThrowsInstanceOf(() => {
|
|
117
|
+
batch.flushNow();
|
|
118
|
+
}, Error);
|
|
119
|
+
assertTrue(flushNowError.message.includes("Cannot use a disposed object."));
|
|
120
|
+
|
|
121
|
+
await Promise.resolve();
|
|
122
|
+
|
|
123
|
+
assertEqual(flushed, []);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it("dispose is idempotent", async () => {
|
|
127
|
+
const flushed: Array<ReadonlyArray<number>> = [];
|
|
128
|
+
const batch = createMicrotaskBatch<number>((items) => {
|
|
129
|
+
flushed.push(items);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
batch.push(1);
|
|
133
|
+
batch[Symbol.dispose]();
|
|
134
|
+
batch[Symbol.dispose]();
|
|
135
|
+
|
|
136
|
+
await Promise.resolve();
|
|
137
|
+
|
|
138
|
+
assertEqual(flushed, []);
|
|
139
|
+
});
|
|
140
|
+
});
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import { test } from "node:test";
|
|
2
|
+
import {
|
|
3
|
+
assertEqual,
|
|
4
|
+
assertFalse,
|
|
5
|
+
assertOk,
|
|
6
|
+
assertSame,
|
|
7
|
+
assertTrue,
|
|
8
|
+
} from "./Assert.ts";
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
clamp,
|
|
12
|
+
computeBalancedBuckets,
|
|
13
|
+
decrement,
|
|
14
|
+
fibonacciAt,
|
|
15
|
+
FibonacciIndex,
|
|
16
|
+
increment,
|
|
17
|
+
type Int0To100OrNonNegativeInt,
|
|
18
|
+
type Int1To100,
|
|
19
|
+
type Int1To100OrPositiveInt,
|
|
20
|
+
type Int1To99,
|
|
21
|
+
isBetween,
|
|
22
|
+
max,
|
|
23
|
+
min,
|
|
24
|
+
type Percentage,
|
|
25
|
+
PercentageLiteral,
|
|
26
|
+
percentageToRatio,
|
|
27
|
+
} from "./Number.ts";
|
|
28
|
+
import { err, ok } from "./Result.ts";
|
|
29
|
+
import { assertType, NonNegativeInt, PositiveInt, Ratio } from "./Type.ts";
|
|
30
|
+
|
|
31
|
+
test("bounded integer literal types", () => {
|
|
32
|
+
assertType<1 extends Int1To99 ? true : false, true>();
|
|
33
|
+
assertType<50 extends Int1To99 ? true : false, true>();
|
|
34
|
+
assertType<99 extends Int1To99 ? true : false, true>();
|
|
35
|
+
assertType<0 extends Int1To99 ? true : false, false>();
|
|
36
|
+
assertType<100 extends Int1To99 ? true : false, false>();
|
|
37
|
+
assertType<"1" extends Int1To99 ? true : false, false>();
|
|
38
|
+
|
|
39
|
+
assertType<1 extends Int1To100 ? true : false, true>();
|
|
40
|
+
assertType<100 extends Int1To100 ? true : false, true>();
|
|
41
|
+
assertType<0 extends Int1To100 ? true : false, false>();
|
|
42
|
+
assertType<101 extends Int1To100 ? true : false, false>();
|
|
43
|
+
|
|
44
|
+
assertType<0 extends Int0To100OrNonNegativeInt ? true : false, true>();
|
|
45
|
+
assertType<100 extends Int0To100OrNonNegativeInt ? true : false, true>();
|
|
46
|
+
assertType<
|
|
47
|
+
NonNegativeInt extends Int0To100OrNonNegativeInt ? true : false,
|
|
48
|
+
true
|
|
49
|
+
>();
|
|
50
|
+
|
|
51
|
+
assertType<1 extends Int1To100OrPositiveInt ? true : false, true>();
|
|
52
|
+
assertType<100 extends Int1To100OrPositiveInt ? true : false, true>();
|
|
53
|
+
assertType<PositiveInt extends Int1To100OrPositiveInt ? true : false, true>();
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test("Percentage accepts canonical literals or Ratio", () => {
|
|
57
|
+
assertType<"0%" extends PercentageLiteral ? true : false, true>();
|
|
58
|
+
assertType<"25%" extends PercentageLiteral ? true : false, true>();
|
|
59
|
+
assertType<"12.5%" extends PercentageLiteral ? true : false, true>();
|
|
60
|
+
assertType<"100%" extends PercentageLiteral ? true : false, true>();
|
|
61
|
+
assertType<"01%" extends PercentageLiteral ? true : false, false>();
|
|
62
|
+
assertType<"10.0%" extends PercentageLiteral ? true : false, false>();
|
|
63
|
+
assertType<"100.1%" extends PercentageLiteral ? true : false, false>();
|
|
64
|
+
assertType<Ratio extends Percentage ? true : false, true>();
|
|
65
|
+
assertTrue(PercentageLiteral.is("0%"));
|
|
66
|
+
assertTrue(PercentageLiteral.is("25%"));
|
|
67
|
+
assertTrue(PercentageLiteral.is("12.5%"));
|
|
68
|
+
assertTrue(PercentageLiteral.is("100%"));
|
|
69
|
+
assertFalse(PercentageLiteral.is("01%"));
|
|
70
|
+
assertFalse(PercentageLiteral.is("10.0%"));
|
|
71
|
+
assertFalse(PercentageLiteral.is("100.1%"));
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test("percentageToRatio converts percentage literals and preserves Ratio", () => {
|
|
75
|
+
assertEqual(percentageToRatio("0%"), 0);
|
|
76
|
+
assertEqual(percentageToRatio("12.5%"), 0.125);
|
|
77
|
+
assertEqual(percentageToRatio("100%"), 1);
|
|
78
|
+
|
|
79
|
+
const ratio = Ratio.orThrow(0.123456);
|
|
80
|
+
assertSame(percentageToRatio(ratio), ratio);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test("increment", () => {
|
|
84
|
+
assertEqual(increment(1), 2);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test("decrement", () => {
|
|
88
|
+
assertEqual(decrement(1), 0);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
test("clamp", () => {
|
|
92
|
+
assertEqual(clamp(0, 2)(1), 1);
|
|
93
|
+
assertEqual(clamp(0, 2)(3), 2);
|
|
94
|
+
assertEqual(clamp(0, 10)(5), 5);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test("isBetween", () => {
|
|
98
|
+
const isBetween10And20 = isBetween(10, 20);
|
|
99
|
+
|
|
100
|
+
assertTrue(isBetween10And20(10));
|
|
101
|
+
assertTrue(isBetween10And20(15));
|
|
102
|
+
assertTrue(isBetween10And20(20));
|
|
103
|
+
|
|
104
|
+
assertFalse(isBetween10And20(9));
|
|
105
|
+
assertFalse(isBetween10And20(21));
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
test("computeBalancedBuckets", () => {
|
|
109
|
+
assertEqual(
|
|
110
|
+
computeBalancedBuckets(32 as NonNegativeInt),
|
|
111
|
+
ok([2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32]),
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
assertEqual(computeBalancedBuckets(31 as NonNegativeInt), err(32));
|
|
115
|
+
assertEqual(computeBalancedBuckets(0 as NonNegativeInt), err(32));
|
|
116
|
+
assertEqual(
|
|
117
|
+
computeBalancedBuckets(10 as NonNegativeInt, 3 as PositiveInt),
|
|
118
|
+
ok([4, 7, 10]),
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
assertEqual(
|
|
122
|
+
computeBalancedBuckets(5 as NonNegativeInt, 1 as PositiveInt),
|
|
123
|
+
ok([5]),
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
assertEqual(
|
|
127
|
+
computeBalancedBuckets(
|
|
128
|
+
6 as NonNegativeInt,
|
|
129
|
+
3 as PositiveInt,
|
|
130
|
+
2 as PositiveInt,
|
|
131
|
+
),
|
|
132
|
+
ok([2, 4, 6]),
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
assertEqual(
|
|
136
|
+
computeBalancedBuckets(
|
|
137
|
+
5 as NonNegativeInt,
|
|
138
|
+
3 as PositiveInt,
|
|
139
|
+
2 as PositiveInt,
|
|
140
|
+
),
|
|
141
|
+
err(6),
|
|
142
|
+
);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
test("min", () => {
|
|
146
|
+
const a = 5 as PositiveInt;
|
|
147
|
+
const b = 3 as PositiveInt;
|
|
148
|
+
const c = 7 as PositiveInt;
|
|
149
|
+
|
|
150
|
+
const result = min(a, b, c);
|
|
151
|
+
assertSame(result, b);
|
|
152
|
+
assertType<typeof result, PositiveInt>();
|
|
153
|
+
|
|
154
|
+
const result2 = min(5, 3, 7);
|
|
155
|
+
assertEqual(result2, 3);
|
|
156
|
+
assertType<typeof result2, number>();
|
|
157
|
+
|
|
158
|
+
const e = 1 as PositiveInt;
|
|
159
|
+
const f = 4 as NonNegativeInt;
|
|
160
|
+
|
|
161
|
+
const result3 = min(e, f);
|
|
162
|
+
assertEqual(result3, 1);
|
|
163
|
+
assertType<typeof result3, NonNegativeInt>();
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
test("max", () => {
|
|
167
|
+
const a = 5 as PositiveInt;
|
|
168
|
+
const b = 3 as PositiveInt;
|
|
169
|
+
const c = 7 as PositiveInt;
|
|
170
|
+
|
|
171
|
+
const result = max(a, b, c);
|
|
172
|
+
assertSame(result, c);
|
|
173
|
+
assertType<typeof result, PositiveInt>();
|
|
174
|
+
|
|
175
|
+
const result2 = max(5, 3, 7);
|
|
176
|
+
assertEqual(result2, 7);
|
|
177
|
+
assertType<typeof result2, number>();
|
|
178
|
+
|
|
179
|
+
const e = 1 as PositiveInt;
|
|
180
|
+
const f = 4 as NonNegativeInt;
|
|
181
|
+
|
|
182
|
+
const result3 = max(e, f);
|
|
183
|
+
assertEqual(result3, 4);
|
|
184
|
+
assertType<typeof result3, NonNegativeInt>();
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
test("FibonacciIndex", () => {
|
|
188
|
+
assertOk(FibonacciIndex.fromUnknown(1));
|
|
189
|
+
assertOk(FibonacciIndex.fromUnknown(78));
|
|
190
|
+
assertFalse(FibonacciIndex.fromUnknown(79).ok);
|
|
191
|
+
assertFalse(FibonacciIndex.fromUnknown(0).ok);
|
|
192
|
+
assertFalse(FibonacciIndex.fromUnknown(-1).ok);
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
test("fibonacciAt", () => {
|
|
196
|
+
const at = (n: number) => fibonacciAt(FibonacciIndex.orThrow(n));
|
|
197
|
+
|
|
198
|
+
// First 10 Fibonacci numbers (1-indexed)
|
|
199
|
+
assertEqual(at(1), 1);
|
|
200
|
+
assertEqual(at(2), 1);
|
|
201
|
+
assertEqual(at(3), 2);
|
|
202
|
+
assertEqual(at(4), 3);
|
|
203
|
+
assertEqual(at(5), 5);
|
|
204
|
+
assertEqual(at(6), 8);
|
|
205
|
+
assertEqual(at(7), 13);
|
|
206
|
+
assertEqual(at(8), 21);
|
|
207
|
+
assertEqual(at(9), 34);
|
|
208
|
+
assertEqual(at(10), 55);
|
|
209
|
+
|
|
210
|
+
// F(78) is the largest Fibonacci within MAX_SAFE_INTEGER
|
|
211
|
+
assertTrue(at(78) < Number.MAX_SAFE_INTEGER);
|
|
212
|
+
|
|
213
|
+
// Return type is PositiveInt
|
|
214
|
+
{
|
|
215
|
+
const actual = at(1);
|
|
216
|
+
assertType<typeof actual, PositiveInt>();
|
|
217
|
+
}
|
|
218
|
+
});
|
package/src/Number.ts
CHANGED
|
@@ -93,7 +93,7 @@ export type Int1To100OrPositiveInt = Int1To100 | PositiveInt;
|
|
|
93
93
|
* const readableJitter = jitter("50%");
|
|
94
94
|
* const computedJitter = jitter(Ratio.orThrow(0.5));
|
|
95
95
|
*
|
|
96
|
-
* assertType<typeof
|
|
96
|
+
* assertType<typeof readableJitter, typeof computedJitter>();
|
|
97
97
|
* ```
|
|
98
98
|
*/
|
|
99
99
|
export type Percentage = PercentageLiteral | Ratio;
|
|
@@ -121,7 +121,7 @@ export type PercentageLiteral = typeof PercentageLiteral.Output;
|
|
|
121
121
|
export const percentageToRatio = (percentage: Percentage): Ratio =>
|
|
122
122
|
typeof percentage === "number"
|
|
123
123
|
? percentage
|
|
124
|
-
: Ratio.orThrow(
|
|
124
|
+
: Ratio.orThrow(Number.parseFloat(percentage) / 100);
|
|
125
125
|
|
|
126
126
|
export const increment = (n: number): number => n + 1;
|
|
127
127
|
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
import { test } from "node:test";
|
|
2
|
+
import { assertEqual, assertFalse, assertSame, assertTrue } from "./Assert.ts";
|
|
3
|
+
|
|
4
|
+
import type { Brand } from "./Brand.ts";
|
|
5
|
+
import type { ReadonlyRecord } from "./Object.ts";
|
|
6
|
+
import {
|
|
7
|
+
createObjectURL,
|
|
8
|
+
createMutableRecord,
|
|
9
|
+
emptyRecord,
|
|
10
|
+
excludeProp,
|
|
11
|
+
getObjectKind,
|
|
12
|
+
getOwnProp,
|
|
13
|
+
isFunction,
|
|
14
|
+
isIterable,
|
|
15
|
+
isPlainObject,
|
|
16
|
+
mapObject,
|
|
17
|
+
objectFrom,
|
|
18
|
+
objectFromEntries,
|
|
19
|
+
objectToEntries,
|
|
20
|
+
} from "./Object.ts";
|
|
21
|
+
import { assertType } from "./Type.ts";
|
|
22
|
+
|
|
23
|
+
class NullBase extends null {}
|
|
24
|
+
|
|
25
|
+
const createNullBase = (): NullBase =>
|
|
26
|
+
Object.create(NullBase.prototype) as NullBase;
|
|
27
|
+
|
|
28
|
+
test("getObjectKind", () => {
|
|
29
|
+
assertEqual(getObjectKind([]), "Array");
|
|
30
|
+
assertEqual(getObjectKind(new ArrayBuffer(0)), "Unsupported");
|
|
31
|
+
assertEqual(getObjectKind(new Date()), "Date");
|
|
32
|
+
assertEqual(getObjectKind(new Map()), "Map");
|
|
33
|
+
assertEqual(getObjectKind({}), "Object");
|
|
34
|
+
assertEqual(getObjectKind(Object.create(null) as object), "Object");
|
|
35
|
+
assertEqual(getObjectKind(new Set()), "Set");
|
|
36
|
+
assertEqual(getObjectKind(new Uint8Array()), "Uint8Array");
|
|
37
|
+
assertEqual(getObjectKind(/value/u), "Unsupported");
|
|
38
|
+
assertEqual(getObjectKind(createNullBase()), "Unsupported");
|
|
39
|
+
|
|
40
|
+
const emptyRoot = Object.create(null) as object;
|
|
41
|
+
assertEqual(getObjectKind(Object.create(emptyRoot) as object), "Unsupported");
|
|
42
|
+
|
|
43
|
+
const accessorRoot = Object.defineProperty(
|
|
44
|
+
Object.create(null) as object,
|
|
45
|
+
"constructor",
|
|
46
|
+
{ get: () => Object },
|
|
47
|
+
);
|
|
48
|
+
assertEqual(
|
|
49
|
+
getObjectKind(Object.create(accessorRoot) as object),
|
|
50
|
+
"Unsupported",
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
const nonFunctionRoot = Object.defineProperty(
|
|
54
|
+
Object.create(null) as object,
|
|
55
|
+
"constructor",
|
|
56
|
+
{ value: 1 },
|
|
57
|
+
);
|
|
58
|
+
assertEqual(
|
|
59
|
+
getObjectKind(Object.create(nonFunctionRoot) as object),
|
|
60
|
+
"Unsupported",
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
let reads = 0;
|
|
64
|
+
const value = Object.defineProperty({}, Symbol.toStringTag, {
|
|
65
|
+
get: () => {
|
|
66
|
+
reads++;
|
|
67
|
+
return "Custom";
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
assertEqual(getObjectKind(value), "Object");
|
|
71
|
+
assertEqual(reads, 0);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test("isPlainObject", () => {
|
|
75
|
+
assertTrue(isPlainObject({}));
|
|
76
|
+
assertTrue(isPlainObject(Object.create(null)));
|
|
77
|
+
assertFalse(isPlainObject(new Date()));
|
|
78
|
+
|
|
79
|
+
class Example {
|
|
80
|
+
readonly id = "a";
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
assertFalse(isPlainObject(new Example()));
|
|
84
|
+
assertFalse(isPlainObject(createNullBase()));
|
|
85
|
+
assertFalse(isPlainObject([]));
|
|
86
|
+
assertFalse(isPlainObject(null));
|
|
87
|
+
|
|
88
|
+
const root = Object.create(null) as object;
|
|
89
|
+
assertFalse(isPlainObject(Object.create(root)));
|
|
90
|
+
|
|
91
|
+
const rootWithObjectConstructor = Object.defineProperty(
|
|
92
|
+
Object.create(null) as object,
|
|
93
|
+
"constructor",
|
|
94
|
+
{ value: Object },
|
|
95
|
+
);
|
|
96
|
+
assertFalse(isPlainObject(Object.create(rootWithObjectConstructor)));
|
|
97
|
+
|
|
98
|
+
const partialObjectPrototype = Object.defineProperty(
|
|
99
|
+
Object.create(null) as object,
|
|
100
|
+
"hasOwnProperty",
|
|
101
|
+
{ value: () => false },
|
|
102
|
+
);
|
|
103
|
+
assertFalse(isPlainObject(Object.create(partialObjectPrototype)));
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
test("isFunction", () => {
|
|
107
|
+
assertTrue(isFunction(() => {}));
|
|
108
|
+
assertTrue(isFunction(function () {}));
|
|
109
|
+
|
|
110
|
+
const constructable: unknown = class Example {
|
|
111
|
+
readonly value = 1;
|
|
112
|
+
};
|
|
113
|
+
assertTrue(isFunction(constructable));
|
|
114
|
+
// oxlint-disable-next-line evolu/no-unnecessary-global-this -- @evolu/common also exports an Evolu Function Type, while isFunction recognizes JavaScript function objects.
|
|
115
|
+
assertType<typeof constructable, globalThis.Function>();
|
|
116
|
+
|
|
117
|
+
assertFalse(isFunction({}));
|
|
118
|
+
assertFalse(isFunction([]));
|
|
119
|
+
assertFalse(isFunction("fn"));
|
|
120
|
+
assertFalse(isFunction(123));
|
|
121
|
+
assertFalse(isFunction(null));
|
|
122
|
+
assertFalse(isFunction(undefined));
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
test("isIterable", () => {
|
|
126
|
+
assertTrue(isIterable([1, 2, 3]));
|
|
127
|
+
assertTrue(isIterable("abc"));
|
|
128
|
+
assertTrue(isIterable(new Set([1])));
|
|
129
|
+
assertTrue(isIterable(new Map([["a", 1]])));
|
|
130
|
+
assertFalse(isIterable({}));
|
|
131
|
+
assertFalse(isIterable(0));
|
|
132
|
+
assertFalse(isIterable(null));
|
|
133
|
+
assertFalse(isIterable(undefined));
|
|
134
|
+
assertFalse(isIterable({ [Symbol.iterator]: 1 }));
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
test("objectToEntries", () => {
|
|
138
|
+
const record = { a: 1, b: 2 };
|
|
139
|
+
const entries = objectToEntries(record);
|
|
140
|
+
|
|
141
|
+
assertEqual(entries, [
|
|
142
|
+
["a", 1],
|
|
143
|
+
["b", 2],
|
|
144
|
+
]);
|
|
145
|
+
|
|
146
|
+
// Preserves branded key types
|
|
147
|
+
type UserId = string & Brand<"UserId">;
|
|
148
|
+
const users: Record<UserId, string> = { ["u1" as UserId]: "Alice" };
|
|
149
|
+
const userEntries = objectToEntries(users);
|
|
150
|
+
assertType<typeof userEntries, ReadonlyArray<[UserId, string]>>();
|
|
151
|
+
|
|
152
|
+
assertEqual(userEntries, [["u1", "Alice"]]);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
test("objectFromEntries", () => {
|
|
156
|
+
const entries: ReadonlyArray<[string, number]> = [
|
|
157
|
+
["a", 1],
|
|
158
|
+
["b", 2],
|
|
159
|
+
];
|
|
160
|
+
const record = objectFromEntries(entries);
|
|
161
|
+
|
|
162
|
+
assertEqual(record, { a: 1, b: 2 });
|
|
163
|
+
|
|
164
|
+
// Preserves branded key types
|
|
165
|
+
type UserId = string & Brand<"UserId">;
|
|
166
|
+
const userEntries: ReadonlyArray<[UserId, string]> = [
|
|
167
|
+
["u1" as UserId, "Alice"],
|
|
168
|
+
];
|
|
169
|
+
const users = objectFromEntries(userEntries);
|
|
170
|
+
assertType<typeof users, ReadonlyRecord<UserId, string>>();
|
|
171
|
+
|
|
172
|
+
assertEqual(users, { u1: "Alice" });
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
test("objectFrom", () => {
|
|
176
|
+
const result = objectFrom(["a", "b", "c"], (key) => key.toUpperCase());
|
|
177
|
+
assertEqual(result, { a: "A", b: "B", c: "C" });
|
|
178
|
+
|
|
179
|
+
// Key is available in the mapper
|
|
180
|
+
const indexed = objectFrom(["x", "y"], (key) => `value-${key}`);
|
|
181
|
+
assertEqual(indexed, { x: "value-x", y: "value-y" });
|
|
182
|
+
|
|
183
|
+
// Preserves key types
|
|
184
|
+
type Lang = "en" | "fr" | "de";
|
|
185
|
+
const langs: ReadonlyArray<Lang> = ["en", "fr", "de"];
|
|
186
|
+
const translations = objectFrom(langs, (lang) => `Hello in ${lang}`);
|
|
187
|
+
assertType<typeof translations, ReadonlyRecord<Lang, string>>();
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
test("mapObject", () => {
|
|
191
|
+
const record = { a: 1, b: 2, c: 3 };
|
|
192
|
+
const doubled = mapObject(record, (value) => value * 2);
|
|
193
|
+
|
|
194
|
+
assertEqual(doubled, { a: 2, b: 4, c: 6 });
|
|
195
|
+
|
|
196
|
+
// Preserves branded key types
|
|
197
|
+
type UserId = string & Brand<"UserId">;
|
|
198
|
+
const users: ReadonlyRecord<UserId, number> = {
|
|
199
|
+
["u1" as UserId]: 10,
|
|
200
|
+
["u2" as UserId]: 20,
|
|
201
|
+
};
|
|
202
|
+
const mapped = mapObject(users, (value, key) => `${key}:${value}`);
|
|
203
|
+
assertType<typeof mapped, ReadonlyRecord<UserId, string>>();
|
|
204
|
+
|
|
205
|
+
assertEqual(mapped, { u1: "u1:10", u2: "u2:20" });
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
test("excludeProp", () => {
|
|
209
|
+
const obj = { a: 1, b: 2, c: 3 };
|
|
210
|
+
|
|
211
|
+
// Without condition (default: excludes)
|
|
212
|
+
const withoutB = excludeProp(obj, "b");
|
|
213
|
+
assertEqual(withoutB, { a: 1, c: 3 });
|
|
214
|
+
|
|
215
|
+
// With condition = true (keeps all)
|
|
216
|
+
const keepAll = excludeProp(obj, "b", true);
|
|
217
|
+
assertEqual(keepAll, { a: 1, b: 2, c: 3 });
|
|
218
|
+
|
|
219
|
+
// With condition = false (excludes)
|
|
220
|
+
const excluded = excludeProp(obj, "a", false);
|
|
221
|
+
assertEqual(excluded, { b: 2, c: 3 });
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
test("createMutableRecord", () => {
|
|
225
|
+
const values = createMutableRecord<string, number>();
|
|
226
|
+
values.__proto__ = 123;
|
|
227
|
+
|
|
228
|
+
assertEqual(values.__proto__, 123);
|
|
229
|
+
|
|
230
|
+
// Ensure Object.prototype was not changed
|
|
231
|
+
const protoValue = Reflect.get(Object.prototype, "__proto__");
|
|
232
|
+
assertSame(Reflect.get(Object.prototype, "__proto__"), protoValue);
|
|
233
|
+
|
|
234
|
+
interface Source {
|
|
235
|
+
readonly name: string;
|
|
236
|
+
readonly age?: number;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const source: Source = { name: "Ada" };
|
|
240
|
+
const copy = createMutableRecord(source);
|
|
241
|
+
|
|
242
|
+
copy.name = "Grace";
|
|
243
|
+
|
|
244
|
+
assertType<typeof copy, { name: string; age?: number }>();
|
|
245
|
+
assertEqual(copy, { name: "Grace" });
|
|
246
|
+
assertEqual(source, { name: "Ada" });
|
|
247
|
+
assertSame(Object.getPrototypeOf(copy), null);
|
|
248
|
+
|
|
249
|
+
const compileTimeAssertions = () => {
|
|
250
|
+
// @ts-expect-error createMutableRecord source must be an object.
|
|
251
|
+
createMutableRecord("Ada");
|
|
252
|
+
};
|
|
253
|
+
assertType<
|
|
254
|
+
typeof compileTimeAssertions extends (...args: Array<never>) => unknown
|
|
255
|
+
? true
|
|
256
|
+
: false,
|
|
257
|
+
true
|
|
258
|
+
>();
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
test("emptyRecord", () => {
|
|
262
|
+
assertSame(Object.getPrototypeOf(emptyRecord), null);
|
|
263
|
+
assertTrue(Object.isFrozen(emptyRecord));
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
test("getOwnProp", () => {
|
|
267
|
+
const record = { a: 1, b: 2 };
|
|
268
|
+
const stringRecord: Readonly<Record<string, number>> = record;
|
|
269
|
+
const nullPrototypeRecord = createMutableRecord<string, number>();
|
|
270
|
+
const value = getOwnProp(record, "a");
|
|
271
|
+
|
|
272
|
+
assertType<typeof value, number | undefined>();
|
|
273
|
+
assertEqual(value, 1);
|
|
274
|
+
assertEqual(getOwnProp(record, "b"), 2);
|
|
275
|
+
// @ts-expect-error c does not exists
|
|
276
|
+
assertSame(getOwnProp(record, "c"), undefined);
|
|
277
|
+
assertSame(getOwnProp(stringRecord, "toString"), undefined);
|
|
278
|
+
assertSame(getOwnProp(nullPrototypeRecord, "toString"), undefined);
|
|
279
|
+
|
|
280
|
+
Reflect.set(nullPrototypeRecord, "toString", 1);
|
|
281
|
+
|
|
282
|
+
assertEqual(getOwnProp(nullPrototypeRecord, "toString"), 1);
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
test("createObjectURL", () => {
|
|
286
|
+
const blob = new Blob(["test"], { type: "text/plain" });
|
|
287
|
+
const objectUrl = createObjectURL(blob);
|
|
288
|
+
|
|
289
|
+
assertTrue(objectUrl.url.startsWith("blob:"));
|
|
290
|
+
|
|
291
|
+
// Dispose revokes the URL
|
|
292
|
+
objectUrl[Symbol.dispose]();
|
|
293
|
+
});
|
package/src/Object.ts
CHANGED
|
@@ -107,6 +107,7 @@ export const isPlainObject = (
|
|
|
107
107
|
* assertFalse(isFunction({}));
|
|
108
108
|
* ```
|
|
109
109
|
*/
|
|
110
|
+
// oxlint-disable-next-line evolu/no-unnecessary-global-this -- @evolu/common also exports an Evolu Function Type, while this predicate recognizes JavaScript function objects.
|
|
110
111
|
export const isFunction = (value: unknown): value is globalThis.Function =>
|
|
111
112
|
typeof value === "function";
|
|
112
113
|
|
|
@@ -147,7 +148,7 @@ export const isIterable = (value: unknown): value is Iterable<unknown> =>
|
|
|
147
148
|
* const users: ReadonlyRecord<UserId, string> = { [userId]: "Alice" };
|
|
148
149
|
* const entries = objectToEntries(users);
|
|
149
150
|
*
|
|
150
|
-
* assertType<ReadonlyArray<[UserId, string]
|
|
151
|
+
* assertType<typeof entries, ReadonlyArray<[UserId, string]>>();
|
|
151
152
|
* assertEqual(entries, [[userId, "Alice"]]);
|
|
152
153
|
* ```
|
|
153
154
|
*/
|
|
@@ -182,7 +183,7 @@ type StringKeyOf<T> = Extract<keyof T, string>;
|
|
|
182
183
|
* ];
|
|
183
184
|
* const users = objectFromEntries(entries);
|
|
184
185
|
*
|
|
185
|
-
* assertType<ReadonlyRecord<UserId, string
|
|
186
|
+
* assertType<typeof users, ReadonlyRecord<UserId, string>>();
|
|
186
187
|
* assertEqual(users, { u1: "Alice" });
|
|
187
188
|
* ```
|
|
188
189
|
*/
|
|
@@ -211,7 +212,7 @@ export const objectFromEntries = <K extends string, V>(
|
|
|
211
212
|
* (locale): string => `Hello in ${locale}`,
|
|
212
213
|
* );
|
|
213
214
|
*
|
|
214
|
-
* assertType<ReadonlyRecord<"en" | "fr", string
|
|
215
|
+
* assertType<typeof translations, ReadonlyRecord<"en" | "fr", string>>();
|
|
215
216
|
* assertEqual(translations, {
|
|
216
217
|
* en: "Hello in en",
|
|
217
218
|
* fr: "Hello in fr",
|
|
@@ -395,7 +396,7 @@ export const emptyRecord: Readonly<Record<string, never>> =
|
|
|
395
396
|
* };
|
|
396
397
|
* const user = getOwnProp(users, "bob");
|
|
397
398
|
*
|
|
398
|
-
* assertType<User | undefined
|
|
399
|
+
* assertType<typeof user, User | undefined>();
|
|
399
400
|
* assertEqual(user, undefined);
|
|
400
401
|
* assertEqual(getOwnProp(users, "toString"), undefined);
|
|
401
402
|
* ```
|