@evolu/common 8.0.0-next.4 → 8.0.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 +279 -173
- package/dist/src/Array.d.ts.map +1 -1
- package/dist/src/Array.js +105 -101
- package/dist/src/Assert.d.ts +44 -44
- package/dist/src/Assert.d.ts.map +1 -1
- package/dist/src/Assert.js +44 -32
- package/dist/src/BigInt.d.ts +6 -3
- package/dist/src/BigInt.d.ts.map +1 -1
- package/dist/src/BigInt.js +5 -2
- package/dist/src/Brand.d.ts +27 -40
- package/dist/src/Brand.d.ts.map +1 -1
- package/dist/src/Buffer.d.ts +27 -11
- package/dist/src/Buffer.d.ts.map +1 -1
- package/dist/src/Cache.d.ts +9 -4
- package/dist/src/Cache.d.ts.map +1 -1
- package/dist/src/Cache.js +8 -3
- package/dist/src/Callbacks.d.ts +30 -19
- package/dist/src/Callbacks.d.ts.map +1 -1
- package/dist/src/Console.d.ts +145 -66
- package/dist/src/Console.d.ts.map +1 -1
- package/dist/src/Console.js +91 -43
- package/dist/src/Crypto.d.ts +85 -42
- package/dist/src/Crypto.d.ts.map +1 -1
- package/dist/src/Crypto.js +44 -20
- package/dist/src/Eq.d.ts +87 -21
- package/dist/src/Eq.d.ts.map +1 -1
- package/dist/src/Eq.js +67 -19
- package/dist/src/Error.d.ts +5 -3
- package/dist/src/Error.d.ts.map +1 -1
- package/dist/src/Error.js +16 -0
- package/dist/src/Function.d.ts +119 -58
- package/dist/src/Function.d.ts.map +1 -1
- package/dist/src/Function.js +86 -43
- package/dist/src/Http.d.ts +394 -0
- package/dist/src/Http.d.ts.map +1 -0
- package/dist/src/Http.js +102 -0
- package/dist/src/Identicon.d.ts +16 -7
- package/dist/src/Identicon.d.ts.map +1 -1
- package/dist/src/Identicon.js +14 -5
- package/dist/src/LeakDetector.d.ts +88 -0
- package/dist/src/LeakDetector.d.ts.map +1 -0
- package/dist/src/LeakDetector.js +86 -0
- package/dist/src/LockManager.d.ts +18 -9
- package/dist/src/LockManager.d.ts.map +1 -1
- package/dist/src/LockManager.js +7 -8
- package/dist/src/Lookup.d.ts +43 -13
- package/dist/src/Lookup.d.ts.map +1 -1
- package/dist/src/Lookup.js +28 -8
- package/dist/src/Microtask.d.ts +1 -1
- package/dist/src/Microtask.d.ts.map +1 -1
- package/dist/src/Microtask.js +3 -7
- package/dist/src/Number.d.ts +49 -13
- package/dist/src/Number.d.ts.map +1 -1
- package/dist/src/Number.js +34 -5
- package/dist/src/Object.d.ts +160 -40
- package/dist/src/Object.d.ts.map +1 -1
- package/dist/src/Object.js +92 -46
- package/dist/src/Option.d.ts +10 -7
- package/dist/src/Option.d.ts.map +1 -1
- package/dist/src/Option.js +9 -6
- package/dist/src/Order.d.ts +21 -21
- package/dist/src/Order.d.ts.map +1 -1
- package/dist/src/Order.js +18 -21
- package/dist/src/Platform.d.ts +31 -0
- package/dist/src/Platform.d.ts.map +1 -1
- package/dist/src/Platform.js +98 -0
- package/dist/src/Random.d.ts +32 -17
- package/dist/src/Random.d.ts.map +1 -1
- package/dist/src/Random.js +12 -8
- package/dist/src/Redacted.d.ts +43 -29
- package/dist/src/Redacted.d.ts.map +1 -1
- package/dist/src/Redacted.js +14 -3
- package/dist/src/Ref.d.ts +11 -3
- package/dist/src/Ref.d.ts.map +1 -1
- package/dist/src/Ref.js +2 -2
- package/dist/src/RefCount.d.ts +2 -6
- package/dist/src/RefCount.d.ts.map +1 -1
- package/dist/src/RefCount.js +33 -90
- package/dist/src/Relation.d.ts +70 -5
- package/dist/src/Relation.d.ts.map +1 -1
- package/dist/src/Relation.js +77 -2
- package/dist/src/Resource.d.ts +590 -155
- package/dist/src/Resource.d.ts.map +1 -1
- package/dist/src/Resource.js +592 -343
- package/dist/src/Result.d.ts +838 -191
- package/dist/src/Result.d.ts.map +1 -1
- package/dist/src/Result.js +163 -102
- package/dist/src/Schedule.d.ts +684 -231
- package/dist/src/Schedule.d.ts.map +1 -1
- package/dist/src/Schedule.js +766 -295
- package/dist/src/Set.d.ts +89 -40
- package/dist/src/Set.d.ts.map +1 -1
- package/dist/src/Set.js +50 -29
- package/dist/src/Sqlite.d.ts +29 -72
- package/dist/src/Sqlite.d.ts.map +1 -1
- package/dist/src/Sqlite.js +41 -46
- package/dist/src/StackTrace.d.ts +40 -0
- package/dist/src/StackTrace.d.ts.map +1 -0
- package/dist/src/StackTrace.js +140 -0
- package/dist/src/Store.d.ts +5 -4
- package/dist/src/Store.d.ts.map +1 -1
- package/dist/src/Store.js +5 -4
- package/dist/src/String.d.ts +7 -1
- package/dist/src/String.d.ts.map +1 -1
- package/dist/src/String.js +20 -7
- package/dist/src/Task.d.ts +3572 -1770
- package/dist/src/Task.d.ts.map +1 -1
- package/dist/src/Task.js +1879 -1274
- package/dist/src/Test.d.ts +17 -125
- package/dist/src/Test.d.ts.map +1 -1
- package/dist/src/Test.js +17 -68
- package/dist/src/Time.d.ts +108 -49
- package/dist/src/Time.d.ts.map +1 -1
- package/dist/src/Time.js +172 -73
- package/dist/src/Type.d.ts +3542 -1926
- package/dist/src/Type.d.ts.map +1 -1
- package/dist/src/Type.js +3070 -1784
- package/dist/src/Types.d.ts +200 -108
- package/dist/src/Types.d.ts.map +1 -1
- package/dist/src/Types.js +39 -8
- package/dist/src/WebSocket.d.ts +48 -23
- package/dist/src/WebSocket.d.ts.map +1 -1
- package/dist/src/WebSocket.js +15 -6
- package/dist/src/Worker.d.ts +94 -36
- package/dist/src/Worker.d.ts.map +1 -1
- package/dist/src/Worker.js +58 -24
- package/dist/src/index.d.ts +55 -52
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +3 -1
- package/dist/src/intl/cs.d.ts +94 -0
- package/dist/src/intl/cs.d.ts.map +1 -0
- package/dist/src/intl/cs.js +220 -0
- package/dist/src/intl/index.d.ts +7 -0
- package/dist/src/intl/index.d.ts.map +1 -0
- package/dist/src/intl/index.js +6 -0
- package/dist/src/local-first/Db.d.ts +12 -8
- package/dist/src/local-first/Db.d.ts.map +1 -1
- package/dist/src/local-first/Db.js +109 -132
- package/dist/src/local-first/Error.d.ts +4 -4
- package/dist/src/local-first/Evolu.d.ts +338 -92
- package/dist/src/local-first/Evolu.d.ts.map +1 -1
- package/dist/src/local-first/Evolu.js +47 -55
- package/dist/src/local-first/LocalAuth.d.ts +4 -4
- package/dist/src/local-first/LocalAuth.d.ts.map +1 -1
- package/dist/src/local-first/Owner.d.ts +61 -24
- package/dist/src/local-first/Owner.d.ts.map +1 -1
- package/dist/src/local-first/Owner.js +45 -8
- package/dist/src/local-first/Protocol.d.ts +44 -31
- package/dist/src/local-first/Protocol.d.ts.map +1 -1
- package/dist/src/local-first/Protocol.js +36 -13
- package/dist/src/local-first/Query.d.ts +147 -62
- package/dist/src/local-first/Query.d.ts.map +1 -1
- package/dist/src/local-first/Query.js +112 -58
- package/dist/src/local-first/Relay.d.ts +44 -14
- package/dist/src/local-first/Relay.d.ts.map +1 -1
- package/dist/src/local-first/Schema.d.ts +92 -41
- package/dist/src/local-first/Schema.d.ts.map +1 -1
- package/dist/src/local-first/Schema.js +25 -8
- package/dist/src/local-first/Shared.d.ts +23 -21
- package/dist/src/local-first/Shared.d.ts.map +1 -1
- package/dist/src/local-first/Shared.js +100 -57
- package/dist/src/local-first/Storage.d.ts +53 -32
- package/dist/src/local-first/Storage.d.ts.map +1 -1
- package/dist/src/local-first/Storage.js +194 -234
- package/dist/src/local-first/Timestamp.d.ts +21 -21
- package/dist/src/local-first/Timestamp.d.ts.map +1 -1
- package/dist/src/local-first/Timestamp.js +2 -2
- package/dist/src/local-first/index.d.ts +11 -11
- package/package.json +14 -13
- package/src/Array.ts +306 -205
- package/src/Assert.ts +54 -69
- package/src/BigInt.ts +6 -3
- package/src/Brand.ts +27 -40
- package/src/Buffer.ts +28 -12
- package/src/Cache.ts +9 -4
- package/src/Callbacks.ts +30 -19
- package/src/Console.ts +156 -84
- package/src/Crypto.ts +83 -41
- package/src/Eq.ts +95 -22
- package/src/Error.ts +8 -4
- package/src/Function.ts +167 -59
- package/src/Http.ts +549 -0
- package/src/Identicon.ts +16 -7
- package/src/LeakDetector.ts +182 -0
- package/src/LockManager.ts +43 -39
- package/src/Lookup.ts +62 -28
- package/src/Microtask.ts +19 -23
- package/src/Number.ts +64 -12
- package/src/Object.ts +173 -49
- package/src/Option.ts +10 -7
- package/src/Order.ts +23 -21
- package/src/Platform.ts +136 -0
- package/src/Polyfills.ts +6 -12
- package/src/Random.ts +32 -19
- package/src/Redacted.ts +44 -30
- package/src/Ref.ts +14 -6
- package/src/RefCount.ts +38 -80
- package/src/Relation.ts +202 -10
- package/src/Resource.ts +1199 -591
- package/src/Result.ts +930 -271
- package/src/Schedule.ts +859 -343
- package/src/Set.ts +89 -40
- package/src/Sqlite.ts +144 -138
- package/src/StackTrace.ts +230 -0
- package/src/Store.ts +8 -7
- package/src/String.ts +22 -5
- package/src/Task.ts +5101 -2832
- package/src/Test.ts +20 -168
- package/src/Time.ts +293 -90
- package/src/Type.ts +10034 -4117
- package/src/Types.ts +227 -110
- package/src/WebSocket.ts +64 -36
- package/src/Worker.ts +180 -72
- package/src/index.ts +55 -52
- package/src/intl/cs.ts +388 -0
- package/src/intl/index.ts +7 -0
- package/src/local-first/Db.ts +152 -179
- package/src/local-first/Error.ts +4 -4
- package/src/local-first/Evolu.ts +495 -250
- package/src/local-first/LocalAuth.ts +6 -12
- package/src/local-first/Owner.ts +58 -21
- package/src/local-first/Protocol.ts +49 -31
- package/src/local-first/Query.ts +146 -61
- package/src/local-first/Relay.ts +51 -21
- package/src/local-first/Schema.ts +109 -51
- package/src/local-first/Shared.ts +266 -226
- package/src/local-first/Storage.ts +266 -264
- package/src/local-first/Timestamp.ts +25 -18
- package/src/local-first/index.ts +11 -11
- package/dist/src/Skiplist.d.ts +0 -28
- package/dist/src/Skiplist.d.ts.map +0 -1
- package/dist/src/Skiplist.js +0 -64
- package/dist/src/Tracer.d.ts +0 -48
- package/dist/src/Tracer.d.ts.map +0 -1
- package/dist/src/Tracer.js +0 -6
- package/src/Skiplist.ts +0 -108
- package/src/Tracer.ts +0 -54
package/src/Time.ts
CHANGED
|
@@ -4,10 +4,17 @@
|
|
|
4
4
|
* @module
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import { assert } from "./Assert.
|
|
8
|
-
import type { Brand } from "./Brand.
|
|
9
|
-
import type { yieldNow } from "./Task.
|
|
10
|
-
import {
|
|
7
|
+
import { assert } from "./Assert.ts";
|
|
8
|
+
import type { Brand } from "./Brand.ts";
|
|
9
|
+
import type { yieldNow } from "./Task.ts";
|
|
10
|
+
import {
|
|
11
|
+
brand,
|
|
12
|
+
type DateIso,
|
|
13
|
+
lessThan,
|
|
14
|
+
type NonNaNNumber,
|
|
15
|
+
NonNegativeInt,
|
|
16
|
+
positive,
|
|
17
|
+
} from "./Type.ts";
|
|
11
18
|
import type {
|
|
12
19
|
Digit,
|
|
13
20
|
Digit1To23,
|
|
@@ -16,20 +23,43 @@ import type {
|
|
|
16
23
|
Digit1To6,
|
|
17
24
|
Digit1To9,
|
|
18
25
|
Digit1To99,
|
|
19
|
-
} from "./Types.
|
|
26
|
+
} from "./Types.ts";
|
|
20
27
|
|
|
21
28
|
/** Time and timer operations. */
|
|
22
29
|
export interface Time {
|
|
23
|
-
|
|
24
|
-
|
|
30
|
+
readonly now: {
|
|
31
|
+
/** Returns current time as Unix epoch milliseconds. */
|
|
32
|
+
(): Millis;
|
|
25
33
|
|
|
26
|
-
|
|
27
|
-
|
|
34
|
+
/** Returns current time as an ISO 8601 UTC string. */
|
|
35
|
+
(type: "DateIso"): DateIso;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
readonly performance: {
|
|
39
|
+
/** Unix epoch timestamp used as the origin for `performance.now()`. */
|
|
40
|
+
readonly timeOrigin: PerformanceTimeOrigin;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Returns a high-resolution timestamp in milliseconds relative to
|
|
44
|
+
* `performance.timeOrigin`.
|
|
45
|
+
*
|
|
46
|
+
* Unlike {@link Time.now}, this value is not Unix epoch time and is
|
|
47
|
+
* unaffected by system clock adjustments. Like Node.js
|
|
48
|
+
* `process.hrtime.bigint()`, it is suitable for measuring elapsed time, but
|
|
49
|
+
* it uses the Web Performance API's number representation rather than
|
|
50
|
+
* nanoseconds as a bigint.
|
|
51
|
+
*/
|
|
52
|
+
readonly now: () => PerformanceTime;
|
|
53
|
+
};
|
|
28
54
|
|
|
29
|
-
/** Schedules a callback after the specified delay. */
|
|
30
|
-
readonly setTimeout: (fn: () => void, delay:
|
|
55
|
+
/** Schedules a callback after the specified positive delay. */
|
|
56
|
+
readonly setTimeout: (fn: () => void, delay: PositiveDuration) => TimeoutId;
|
|
31
57
|
|
|
32
|
-
/**
|
|
58
|
+
/**
|
|
59
|
+
* Cancels a timeout scheduled with this instance's {@link Time.setTimeout}.
|
|
60
|
+
*
|
|
61
|
+
* Throws if the timeout was scheduled by another {@link Time} instance.
|
|
62
|
+
*/
|
|
33
63
|
readonly clearTimeout: (id: TimeoutId) => void;
|
|
34
64
|
}
|
|
35
65
|
|
|
@@ -44,39 +74,127 @@ export interface TimeDep {
|
|
|
44
74
|
*/
|
|
45
75
|
export type TimeoutId = Brand<"TimeoutId">;
|
|
46
76
|
|
|
77
|
+
interface TimeoutIdInternal {
|
|
78
|
+
readonly owner: symbol;
|
|
79
|
+
readonly clear: () => void;
|
|
80
|
+
}
|
|
81
|
+
|
|
47
82
|
/**
|
|
48
|
-
* Creates a {@link Time} using `Date.now()` and
|
|
83
|
+
* Creates a {@link Time} using `Date.now()`, `performance`, and
|
|
84
|
+
* `globalThis.setTimeout`.
|
|
85
|
+
*
|
|
86
|
+
* Long timeouts are split into native timer chunks and tracked against an
|
|
87
|
+
* absolute wall-clock deadline so late callback execution and system suspension
|
|
88
|
+
* do not extend the requested delay. System clock adjustments can therefore
|
|
89
|
+
* shorten or lengthen long timeouts.
|
|
49
90
|
*
|
|
50
91
|
* Throws if the system clock returns an out-of-range value. This is intentional
|
|
51
92
|
* — there's no reasonable fallback for a misconfigured clock.
|
|
52
93
|
*/
|
|
53
94
|
export const createTime = (): Time => {
|
|
54
|
-
const
|
|
95
|
+
const timeoutOwner = Symbol("Time");
|
|
96
|
+
function now(): Millis;
|
|
97
|
+
function now(type: "DateIso"): DateIso;
|
|
98
|
+
function now(type?: "DateIso"): Millis | DateIso {
|
|
99
|
+
const millis = getSystemNowMillis();
|
|
100
|
+
return type === "DateIso" ? millisToDateIso(millis) : millis;
|
|
101
|
+
}
|
|
55
102
|
|
|
56
103
|
return {
|
|
57
|
-
now
|
|
104
|
+
now,
|
|
58
105
|
|
|
59
|
-
|
|
106
|
+
performance: {
|
|
107
|
+
timeOrigin: globalThis.performance.timeOrigin as PerformanceTimeOrigin,
|
|
108
|
+
now: () => globalThis.performance.now() as PerformanceTime,
|
|
109
|
+
},
|
|
60
110
|
|
|
61
|
-
setTimeout: (callback,
|
|
62
|
-
|
|
63
|
-
callback,
|
|
64
|
-
durationToMillis(delay),
|
|
65
|
-
) as unknown as TimeoutId,
|
|
111
|
+
setTimeout: (callback, duration) =>
|
|
112
|
+
scheduleNativeTimeout(timeoutOwner, callback, duration),
|
|
66
113
|
|
|
67
114
|
clearTimeout: (id) => {
|
|
68
|
-
|
|
115
|
+
clearTimeoutId(timeoutOwner, id);
|
|
69
116
|
},
|
|
70
117
|
};
|
|
71
118
|
};
|
|
72
119
|
|
|
120
|
+
const scheduleNativeTimeout = (
|
|
121
|
+
owner: symbol,
|
|
122
|
+
callback: () => void,
|
|
123
|
+
duration: PositiveDuration,
|
|
124
|
+
): TimeoutId => {
|
|
125
|
+
const delay = durationToMillis(duration);
|
|
126
|
+
let cancelled = false;
|
|
127
|
+
let nativeId: ReturnType<typeof globalThis.setTimeout>;
|
|
128
|
+
|
|
129
|
+
if (delay <= maxNativeTimeoutMillis) {
|
|
130
|
+
nativeId = globalThis.setTimeout(() => {
|
|
131
|
+
if (cancelled) return;
|
|
132
|
+
cancelled = true;
|
|
133
|
+
callback();
|
|
134
|
+
}, delay);
|
|
135
|
+
} else {
|
|
136
|
+
// Recompute each chunk from one absolute deadline so time elapsed while the
|
|
137
|
+
// event loop is suspended, for example during system sleep, is not added again.
|
|
138
|
+
const deadline = getSystemNowMillis() + delay;
|
|
139
|
+
|
|
140
|
+
const onTimeout = (): void => {
|
|
141
|
+
if (cancelled) return;
|
|
142
|
+
|
|
143
|
+
const remaining = deadline - getSystemNowMillis();
|
|
144
|
+
if (remaining > 0) {
|
|
145
|
+
nativeId = globalThis.setTimeout(
|
|
146
|
+
onTimeout,
|
|
147
|
+
Math.min(remaining, maxNativeTimeoutMillis),
|
|
148
|
+
);
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
cancelled = true;
|
|
153
|
+
callback();
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
nativeId = globalThis.setTimeout(onTimeout, maxNativeTimeoutMillis);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return {
|
|
160
|
+
owner,
|
|
161
|
+
clear: () => {
|
|
162
|
+
cancelled = true;
|
|
163
|
+
globalThis.clearTimeout(nativeId);
|
|
164
|
+
},
|
|
165
|
+
} as unknown as TimeoutId;
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Maximum delay supported reliably by native timers: the largest positive
|
|
170
|
+
* signed 32-bit integer, approximately 24.9 days in milliseconds. Longer
|
|
171
|
+
* logical delays are scheduled in chunks to avoid native timer overflow.
|
|
172
|
+
*/
|
|
173
|
+
const maxNativeTimeoutMillis = 2 ** 31 - 1;
|
|
174
|
+
|
|
175
|
+
const getSystemNowMillis = (): Millis => Millis.orThrow(globalThis.Date.now());
|
|
176
|
+
|
|
177
|
+
const clearTimeoutId = (owner: symbol, id: TimeoutId): void => {
|
|
178
|
+
const internal = id as unknown as TimeoutIdInternal;
|
|
179
|
+
assert(
|
|
180
|
+
internal.owner === owner,
|
|
181
|
+
"TimeoutId was created by another Time instance",
|
|
182
|
+
);
|
|
183
|
+
internal.clear();
|
|
184
|
+
};
|
|
185
|
+
|
|
73
186
|
/**
|
|
74
187
|
* Test {@link Time} with controllable timers.
|
|
75
188
|
*
|
|
76
189
|
* Call `advance(ms)` to move time forward and trigger any pending timeouts.
|
|
77
190
|
*/
|
|
78
191
|
export interface TestTime extends Time {
|
|
79
|
-
/**
|
|
192
|
+
/**
|
|
193
|
+
* Advances time by the specified duration, triggering pending timeouts.
|
|
194
|
+
*
|
|
195
|
+
* Timeout callback errors propagate and leave time at the callback's
|
|
196
|
+
* deadline. Throws if called while another advance is in progress.
|
|
197
|
+
*/
|
|
80
198
|
readonly advance: (duration: Duration) => void;
|
|
81
199
|
}
|
|
82
200
|
|
|
@@ -91,20 +209,23 @@ export interface TestTimeDep {
|
|
|
91
209
|
* called. Timeouts scheduled via `setTimeout` fire when time is advanced past
|
|
92
210
|
* their deadline.
|
|
93
211
|
*
|
|
94
|
-
* Set `autoIncrement` to automatically increment time by 1ms after each
|
|
95
|
-
* call. `"microtask"` increments after the
|
|
96
|
-
* increments immediately after each read. Omit it
|
|
97
|
-
* `advance()` is called.
|
|
212
|
+
* Set `autoIncrement` to automatically increment time by 1ms after each
|
|
213
|
+
* wall-clock or performance `now()` call. `"microtask"` increments after the
|
|
214
|
+
* current turn, while `"sync"` increments immediately after each read. Omit it
|
|
215
|
+
* to keep time fixed until `advance()` is called.
|
|
98
216
|
*/
|
|
99
217
|
export const testCreateTime = (options?: {
|
|
100
218
|
readonly startAt?: Millis;
|
|
101
219
|
readonly autoIncrement?: "microtask" | "sync";
|
|
102
220
|
}): TestTime => {
|
|
103
|
-
|
|
221
|
+
const startAt = options?.startAt ?? minMillis;
|
|
104
222
|
const autoIncrement = options?.autoIncrement;
|
|
223
|
+
const timeoutOwner = Symbol("TestTime");
|
|
224
|
+
let now = startAt;
|
|
105
225
|
let nextId = 1;
|
|
226
|
+
let advancing = false;
|
|
106
227
|
|
|
107
|
-
const pending = new Map<number, { callback: () => void; runAt:
|
|
228
|
+
const pending = new Map<number, { callback: () => void; runAt: Millis }>();
|
|
108
229
|
const incrementNow = (): void => {
|
|
109
230
|
now = Millis.orThrow(now + 1);
|
|
110
231
|
};
|
|
@@ -121,30 +242,65 @@ export const testCreateTime = (options?: {
|
|
|
121
242
|
}
|
|
122
243
|
return result;
|
|
123
244
|
};
|
|
245
|
+
function getNow(): Millis;
|
|
246
|
+
function getNow(type: "DateIso"): DateIso;
|
|
247
|
+
function getNow(type?: "DateIso"): Millis | DateIso {
|
|
248
|
+
const millis = getNowMillis();
|
|
249
|
+
return type === "DateIso" ? millisToDateIso(millis) : millis;
|
|
250
|
+
}
|
|
124
251
|
|
|
125
252
|
return {
|
|
126
|
-
now:
|
|
253
|
+
now: getNow,
|
|
127
254
|
|
|
128
|
-
|
|
255
|
+
performance: {
|
|
256
|
+
timeOrigin: Number(startAt) as PerformanceTimeOrigin,
|
|
257
|
+
now: () => (getNowMillis() - startAt) as PerformanceTime,
|
|
258
|
+
},
|
|
129
259
|
|
|
130
260
|
setTimeout: (callback, delay) => {
|
|
261
|
+
const runAt = Millis.orThrow(now + durationToMillis(delay));
|
|
131
262
|
const id = nextId++;
|
|
132
|
-
pending.set(id, { callback, runAt
|
|
133
|
-
return
|
|
263
|
+
pending.set(id, { callback, runAt });
|
|
264
|
+
return {
|
|
265
|
+
owner: timeoutOwner,
|
|
266
|
+
clear: () => {
|
|
267
|
+
pending.delete(id);
|
|
268
|
+
},
|
|
269
|
+
} as unknown as TimeoutId;
|
|
134
270
|
},
|
|
135
271
|
|
|
136
272
|
clearTimeout: (id) => {
|
|
137
|
-
|
|
273
|
+
clearTimeoutId(timeoutOwner, id);
|
|
138
274
|
},
|
|
139
275
|
|
|
140
276
|
advance: (duration) => {
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
277
|
+
assert(!advancing, "TestTime.advance cannot be called while advancing");
|
|
278
|
+
const target = Millis.orThrow(now + durationToMillis(duration));
|
|
279
|
+
advancing = true;
|
|
280
|
+
|
|
281
|
+
try {
|
|
282
|
+
while (pending.size > 0) {
|
|
283
|
+
let earliestId: number | null = null;
|
|
284
|
+
let nextRunAt = Number.POSITIVE_INFINITY;
|
|
285
|
+
|
|
286
|
+
for (const [id, timeout] of pending) {
|
|
287
|
+
if (timeout.runAt <= target && timeout.runAt < nextRunAt) {
|
|
288
|
+
earliestId = id;
|
|
289
|
+
nextRunAt = timeout.runAt;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
if (earliestId === null) break;
|
|
294
|
+
|
|
295
|
+
const timeout = pending.get(earliestId)!;
|
|
296
|
+
pending.delete(earliestId);
|
|
297
|
+
now = Millis.orThrow(Math.max(now, nextRunAt));
|
|
146
298
|
timeout.callback();
|
|
147
299
|
}
|
|
300
|
+
|
|
301
|
+
now = Millis.orThrow(Math.max(now, target));
|
|
302
|
+
} finally {
|
|
303
|
+
advancing = false;
|
|
148
304
|
}
|
|
149
305
|
},
|
|
150
306
|
};
|
|
@@ -154,7 +310,7 @@ export const testCreateTime = (options?: {
|
|
|
154
310
|
const maxMillisWithInfinity = 281474976710655;
|
|
155
311
|
|
|
156
312
|
/**
|
|
157
|
-
*
|
|
313
|
+
* Non-negative integer milliseconds used for timestamps and durations.
|
|
158
314
|
*
|
|
159
315
|
* The maximum value is 281474976710654 (281474976710655 - 1, reserved for
|
|
160
316
|
* infinity). This enables efficient binary serialization, saving 2 bytes
|
|
@@ -169,7 +325,11 @@ export const Millis = /*#__PURE__*/ brand(
|
|
|
169
325
|
"Millis",
|
|
170
326
|
/*#__PURE__*/ lessThan(maxMillisWithInfinity)(NonNegativeInt),
|
|
171
327
|
);
|
|
172
|
-
export type Millis = typeof Millis.
|
|
328
|
+
export type Millis = typeof Millis.Output;
|
|
329
|
+
|
|
330
|
+
/** Positive {@link Millis} value. */
|
|
331
|
+
export const PositiveMillis = /*#__PURE__*/ positive(Millis);
|
|
332
|
+
export type PositiveMillis = typeof PositiveMillis.Output;
|
|
173
333
|
|
|
174
334
|
/** Minimum {@link Millis} value. */
|
|
175
335
|
export const minMillis = 0 as Millis;
|
|
@@ -181,7 +341,7 @@ export const maxMillis = (maxMillisWithInfinity - 1) as Millis;
|
|
|
181
341
|
* Converts a number to {@link Millis}, rounding to the nearest millisecond and
|
|
182
342
|
* saturating overflow at {@link maxMillis}.
|
|
183
343
|
*/
|
|
184
|
-
export const saturateMillis = (value:
|
|
344
|
+
export const saturateMillis = (value: NonNaNNumber): Millis =>
|
|
185
345
|
Millis.orNull(Math.max(0, Math.round(value))) ?? maxMillis;
|
|
186
346
|
|
|
187
347
|
/**
|
|
@@ -193,12 +353,37 @@ export const saturateMillis = (value: number): Millis =>
|
|
|
193
353
|
export const millisToDateIso = (value: Millis): DateIso =>
|
|
194
354
|
new globalThis.Date(value).toISOString() as DateIso;
|
|
195
355
|
|
|
356
|
+
/** Unix epoch milliseconds used as the origin for {@link PerformanceTime}. */
|
|
357
|
+
export type PerformanceTimeOrigin = number & Brand<"PerformanceTimeOrigin">;
|
|
358
|
+
|
|
359
|
+
/** High-resolution milliseconds elapsed since {@link PerformanceTimeOrigin}. */
|
|
360
|
+
export type PerformanceTime = number & Brand<"PerformanceTime">;
|
|
361
|
+
|
|
362
|
+
/** Elapsed fractional milliseconds measured using {@link PerformanceTime}. */
|
|
363
|
+
export type PerformanceDuration = number & Brand<"PerformanceDuration">;
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Returns the elapsed fractional milliseconds between two performance times.
|
|
367
|
+
*
|
|
368
|
+
* Throws if `end` precedes `start`.
|
|
369
|
+
*/
|
|
370
|
+
export const performanceDurationBetween = (
|
|
371
|
+
start: PerformanceTime,
|
|
372
|
+
end: PerformanceTime,
|
|
373
|
+
): PerformanceDuration => {
|
|
374
|
+
assert(end >= start, "Performance end time must not precede start time");
|
|
375
|
+
return (end - start) as PerformanceDuration;
|
|
376
|
+
};
|
|
377
|
+
|
|
196
378
|
/**
|
|
197
379
|
* Duration can be either a {@link DurationLiteral} or milliseconds as
|
|
198
380
|
* {@link Millis}.
|
|
199
381
|
*/
|
|
200
382
|
export type Duration = DurationLiteral | Millis;
|
|
201
383
|
|
|
384
|
+
/** Positive duration accepted by timer-based APIs. */
|
|
385
|
+
export type PositiveDuration = DurationLiteral | PositiveMillis;
|
|
386
|
+
|
|
202
387
|
/**
|
|
203
388
|
* Duration literal with compile-time validation.
|
|
204
389
|
*
|
|
@@ -213,9 +398,9 @@ export type Duration = DurationLiteral | Millis;
|
|
|
213
398
|
* - Months: not supported (variable length)
|
|
214
399
|
* - Years: `1y`, `99y`, `1.5y` (1-99, 1.1-99.9)
|
|
215
400
|
*
|
|
216
|
-
* Each unit
|
|
217
|
-
*
|
|
218
|
-
*
|
|
401
|
+
* Each unit uses a bounded range. Where units convert exactly, this avoids
|
|
402
|
+
* equivalent representations (e.g., 1000ms must be written as `"1s"`, not
|
|
403
|
+
* `"1000ms"`).
|
|
219
404
|
*
|
|
220
405
|
* Decimal values cover cases like 1.5s (1500ms) or 1.5h (90 minutes) without
|
|
221
406
|
* allowing redundant forms. For precise values that don't fit (e.g., 1050ms),
|
|
@@ -299,17 +484,20 @@ export type DurationLiteralYears =
|
|
|
299
484
|
* ### Example
|
|
300
485
|
*
|
|
301
486
|
* ```ts
|
|
302
|
-
* durationToMillis
|
|
303
|
-
*
|
|
304
|
-
* durationToMillis("1.5s");
|
|
305
|
-
* durationToMillis("
|
|
306
|
-
* durationToMillis("
|
|
307
|
-
* durationToMillis("
|
|
308
|
-
* durationToMillis(
|
|
309
|
-
* durationToMillis(Millis.orThrow(5000)); // 5000 (already Millis)
|
|
487
|
+
* import { durationToMillis, Millis } from "@evolu/common";
|
|
488
|
+
*
|
|
489
|
+
* expect(durationToMillis("1.5s")).toBe(1500);
|
|
490
|
+
* expect(durationToMillis("5m")).toBe(300000);
|
|
491
|
+
* expect(durationToMillis("12h")).toBe(43200000);
|
|
492
|
+
* expect(durationToMillis("1w")).toBe(604800000);
|
|
493
|
+
* expect(durationToMillis(Millis.orThrow(5000))).toBe(5000);
|
|
310
494
|
* ```
|
|
311
495
|
*/
|
|
312
|
-
export
|
|
496
|
+
export function durationToMillis(
|
|
497
|
+
duration: DurationLiteral | PositiveMillis,
|
|
498
|
+
): PositiveMillis;
|
|
499
|
+
export function durationToMillis(duration: Duration): Millis;
|
|
500
|
+
export function durationToMillis(duration: Duration): Millis {
|
|
313
501
|
if (typeof duration === "number") return duration;
|
|
314
502
|
|
|
315
503
|
const num = parseFloat(duration);
|
|
@@ -320,23 +508,7 @@ export const durationToMillis = (duration: Duration): Millis => {
|
|
|
320
508
|
return Millis.orThrow(
|
|
321
509
|
Math.round(num * durationUnits[unit as keyof typeof durationUnits]),
|
|
322
510
|
);
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
/**
|
|
326
|
-
* Returns a Promise that resolves after the specified duration.
|
|
327
|
-
*
|
|
328
|
-
* Uses {@link Duration} parsing and `globalThis.setTimeout`.
|
|
329
|
-
*
|
|
330
|
-
* ### Example
|
|
331
|
-
*
|
|
332
|
-
* ```ts
|
|
333
|
-
* await setTimeout("1ms");
|
|
334
|
-
* ```
|
|
335
|
-
*/
|
|
336
|
-
export const setTimeout = (duration: Duration): Promise<void> =>
|
|
337
|
-
new Promise((resolve) => {
|
|
338
|
-
globalThis.setTimeout(resolve, durationToMillis(duration));
|
|
339
|
-
});
|
|
511
|
+
}
|
|
340
512
|
|
|
341
513
|
const durationUnits = {
|
|
342
514
|
ms: 1,
|
|
@@ -377,39 +549,70 @@ export const msLongTask = 50 as Millis;
|
|
|
377
549
|
*
|
|
378
550
|
* - Under 1 minute: `1.234s`
|
|
379
551
|
* - Under 1 hour: `1m30.000s`
|
|
380
|
-
* - 1
|
|
552
|
+
* - Under 1 day: `1h30m45.000s`
|
|
553
|
+
* - Under 1 week: `1d2h30m45.000s`
|
|
554
|
+
* - Under 1 year: `1w2d3h30m45.000s`
|
|
555
|
+
* - 1 year or more: `1y2w3d4h30m45.000s`
|
|
556
|
+
*
|
|
557
|
+
* Weeks are 7 days and years are 365 days.
|
|
381
558
|
*
|
|
382
559
|
* ### Example
|
|
383
560
|
*
|
|
384
561
|
* ```ts
|
|
385
|
-
* formatMillisAsDuration
|
|
386
|
-
*
|
|
387
|
-
* formatMillisAsDuration(
|
|
562
|
+
* import { formatMillisAsDuration, Millis } from "@evolu/common";
|
|
563
|
+
*
|
|
564
|
+
* expect(formatMillisAsDuration(Millis.orThrow(1234))).toBe("1.234s");
|
|
565
|
+
* expect(formatMillisAsDuration(Millis.orThrow(90000))).toBe("1m30.000s");
|
|
566
|
+
* expect(formatMillisAsDuration(Millis.orThrow(90061000))).toBe(
|
|
567
|
+
* "1d1h1m1.000s",
|
|
568
|
+
* );
|
|
388
569
|
* ```
|
|
389
570
|
*/
|
|
390
571
|
export const formatMillisAsDuration = (millis: Millis): string => {
|
|
391
|
-
const
|
|
392
|
-
if (
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
572
|
+
const seconds = ((millis % durationUnits.m) / durationUnits.s).toFixed(3);
|
|
573
|
+
if (millis < durationUnits.m) return `${seconds}s`;
|
|
574
|
+
|
|
575
|
+
const minutes = Math.floor(millis / durationUnits.m) % 60;
|
|
576
|
+
if (millis < durationUnits.h) return `${minutes}m${seconds}s`;
|
|
577
|
+
|
|
578
|
+
const hours = Math.floor(millis / durationUnits.h) % 24;
|
|
579
|
+
if (millis < durationUnits.d) return `${hours}h${minutes}m${seconds}s`;
|
|
580
|
+
|
|
581
|
+
const daysAfterYears = Math.floor(
|
|
582
|
+
(millis % durationUnits.y) / durationUnits.d,
|
|
583
|
+
);
|
|
584
|
+
const days = daysAfterYears % 7;
|
|
585
|
+
if (millis < durationUnits.w)
|
|
586
|
+
return `${days}d${hours}h${minutes}m${seconds}s`;
|
|
587
|
+
|
|
588
|
+
const weeks = Math.floor(daysAfterYears / 7);
|
|
589
|
+
if (millis < durationUnits.y)
|
|
590
|
+
return `${weeks}w${days}d${hours}h${minutes}m${seconds}s`;
|
|
591
|
+
|
|
592
|
+
const years = Math.floor(millis / durationUnits.y);
|
|
593
|
+
return `${years}y${weeks}w${days}d${hours}h${minutes}m${seconds}s`;
|
|
404
594
|
};
|
|
405
595
|
|
|
406
596
|
/**
|
|
407
|
-
* Formats {@link Millis} as `HH:MM:SS.mmm
|
|
597
|
+
* Formats {@link Millis} as local time in `HH:MM:SS.mmm` format.
|
|
408
598
|
*
|
|
409
599
|
* ### Example
|
|
410
600
|
*
|
|
411
601
|
* ```ts
|
|
412
|
-
* formatMillisAsClockTime
|
|
602
|
+
* import { formatMillisAsClockTime, Millis } from "@evolu/common";
|
|
603
|
+
*
|
|
604
|
+
* const timestamp = new globalThis.Date(
|
|
605
|
+
* 2026,
|
|
606
|
+
* 0,
|
|
607
|
+
* 28,
|
|
608
|
+
* 14,
|
|
609
|
+
* 32,
|
|
610
|
+
* 15,
|
|
611
|
+
* 234,
|
|
612
|
+
* ).getTime();
|
|
613
|
+
* expect(formatMillisAsClockTime(Millis.orThrow(timestamp))).toBe(
|
|
614
|
+
* "14:32:15.234",
|
|
615
|
+
* );
|
|
413
616
|
* ```
|
|
414
617
|
*/
|
|
415
618
|
export const formatMillisAsClockTime = (millis: Millis): string => {
|