@evolu/common 7.4.1 → 8.0.0-next.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/README.md +4 -61
- package/dist/src/Array.d.ts +343 -102
- package/dist/src/Array.d.ts.map +1 -1
- package/dist/src/Array.js +181 -85
- package/dist/src/Assert.d.ts +57 -11
- package/dist/src/Assert.d.ts.map +1 -1
- package/dist/src/Assert.js +47 -11
- package/dist/src/BigInt.d.ts +6 -1
- package/dist/src/BigInt.d.ts.map +1 -1
- package/dist/src/BigInt.js +5 -0
- package/dist/src/Brand.d.ts +40 -12
- package/dist/src/Brand.d.ts.map +1 -1
- package/dist/src/Brand.js +5 -0
- package/dist/src/Buffer.d.ts +6 -1
- package/dist/src/Buffer.d.ts.map +1 -1
- package/dist/src/Buffer.js +7 -2
- package/dist/src/Cache.d.ts +10 -2
- package/dist/src/Cache.d.ts.map +1 -1
- package/dist/src/Cache.js +6 -0
- package/dist/src/Callbacks.d.ts +10 -7
- package/dist/src/Callbacks.d.ts.map +1 -1
- package/dist/src/Callbacks.js +23 -13
- package/dist/src/Console.d.ts +361 -69
- package/dist/src/Console.d.ts.map +1 -1
- package/dist/src/Console.js +217 -117
- package/dist/src/Crypto.d.ts +70 -21
- package/dist/src/Crypto.d.ts.map +1 -1
- package/dist/src/Crypto.js +72 -27
- package/dist/src/Eq.d.ts +22 -2
- package/dist/src/Eq.d.ts.map +1 -1
- package/dist/src/Eq.js +21 -1
- package/dist/src/Error.d.ts +23 -12
- package/dist/src/Error.d.ts.map +1 -1
- package/dist/src/Error.js +27 -11
- package/dist/src/Function.d.ts +128 -56
- package/dist/src/Function.d.ts.map +1 -1
- package/dist/src/Function.js +118 -14
- package/dist/src/Identicon.d.ts +5 -0
- package/dist/src/Identicon.d.ts.map +1 -1
- package/dist/src/Identicon.js +6 -1
- package/dist/src/Lookup.d.ts +160 -0
- package/dist/src/Lookup.d.ts.map +1 -0
- package/dist/src/Lookup.js +192 -0
- package/dist/src/Microtask.d.ts +21 -0
- package/dist/src/Microtask.d.ts.map +1 -0
- package/dist/src/Microtask.js +37 -0
- package/dist/src/Number.d.ts +18 -4
- package/dist/src/Number.d.ts.map +1 -1
- package/dist/src/Number.js +23 -1
- package/dist/src/Object.d.ts +110 -5
- package/dist/src/Object.d.ts.map +1 -1
- package/dist/src/Object.js +108 -5
- package/dist/src/Option.d.ts +58 -0
- package/dist/src/Option.d.ts.map +1 -0
- package/dist/src/Option.js +43 -0
- package/dist/src/Order.d.ts +5 -0
- package/dist/src/Order.d.ts.map +1 -1
- package/dist/src/Order.js +8 -3
- package/dist/src/Platform.d.ts +36 -2
- package/dist/src/Platform.d.ts.map +1 -1
- package/dist/src/Platform.js +10 -6
- package/dist/src/Polyfills.d.ts +27 -0
- package/dist/src/Polyfills.d.ts.map +1 -0
- package/dist/src/Polyfills.js +299 -0
- package/dist/src/Random.d.ts +18 -7
- package/dist/src/Random.d.ts.map +1 -1
- package/dist/src/Random.js +9 -7
- package/dist/src/Redacted.d.ts +7 -4
- package/dist/src/Redacted.d.ts.map +1 -1
- package/dist/src/Redacted.js +5 -0
- package/dist/src/Ref.d.ts +31 -16
- package/dist/src/Ref.d.ts.map +1 -1
- package/dist/src/Ref.js +35 -7
- package/dist/src/RefCount.d.ts +62 -0
- package/dist/src/RefCount.d.ts.map +1 -0
- package/dist/src/RefCount.js +83 -0
- package/dist/src/Relation.d.ts +84 -29
- package/dist/src/Relation.d.ts.map +1 -1
- package/dist/src/Relation.js +83 -105
- package/dist/src/Resource.d.ts +263 -0
- package/dist/src/Resource.d.ts.map +1 -0
- package/dist/src/Resource.js +389 -0
- package/dist/src/Result.d.ts +390 -374
- package/dist/src/Result.d.ts.map +1 -1
- package/dist/src/Result.js +142 -70
- package/dist/src/Schedule.d.ts +953 -0
- package/dist/src/Schedule.d.ts.map +1 -0
- package/dist/src/Schedule.js +1199 -0
- package/dist/src/Set.d.ts +181 -0
- package/dist/src/Set.d.ts.map +1 -0
- package/dist/src/Set.js +137 -0
- package/dist/src/Skiplist.d.ts +6 -1
- package/dist/src/Skiplist.d.ts.map +1 -1
- package/dist/src/Skiplist.js +5 -0
- package/dist/src/Sqlite.d.ts +160 -60
- package/dist/src/Sqlite.d.ts.map +1 -1
- package/dist/src/Sqlite.js +290 -190
- package/dist/src/Store.d.ts +36 -28
- package/dist/src/Store.d.ts.map +1 -1
- package/dist/src/Store.js +55 -15
- package/dist/src/String.d.ts +5 -0
- package/dist/src/String.d.ts.map +1 -1
- package/dist/src/String.js +5 -0
- package/dist/src/Task.d.ts +2263 -423
- package/dist/src/Task.d.ts.map +1 -1
- package/dist/src/Task.js +1488 -348
- package/dist/src/Test.d.ts +122 -0
- package/dist/src/Test.d.ts.map +1 -0
- package/dist/src/Test.js +66 -0
- package/dist/src/Time.d.ts +201 -48
- package/dist/src/Time.d.ts.map +1 -1
- package/dist/src/Time.js +190 -86
- package/dist/src/Tracer.d.ts +48 -0
- package/dist/src/Tracer.d.ts.map +1 -0
- package/dist/src/Tracer.js +6 -0
- package/dist/src/Type.d.ts +582 -209
- package/dist/src/Type.d.ts.map +1 -1
- package/dist/src/Type.js +362 -240
- package/dist/src/Types.d.ts +160 -6
- package/dist/src/Types.d.ts.map +1 -1
- package/dist/src/Types.js +22 -2
- package/dist/src/WebSocket.d.ts +113 -58
- package/dist/src/WebSocket.d.ts.map +1 -1
- package/dist/src/WebSocket.js +210 -118
- package/dist/src/Worker.d.ts +245 -60
- package/dist/src/Worker.d.ts.map +1 -1
- package/dist/src/Worker.js +220 -64
- package/dist/src/index.d.ts +25 -3
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +19 -3
- package/dist/src/local-first/Db.d.ts +24 -213
- package/dist/src/local-first/Db.d.ts.map +1 -1
- package/dist/src/local-first/Db.js +622 -293
- package/dist/src/local-first/Error.d.ts +12 -0
- package/dist/src/local-first/Error.d.ts.map +1 -0
- package/dist/src/local-first/Error.js +6 -0
- package/dist/src/local-first/Evolu.d.ts +330 -285
- package/dist/src/local-first/Evolu.d.ts.map +1 -1
- package/dist/src/local-first/Evolu.js +472 -402
- package/dist/src/local-first/LocalAuth.d.ts +10 -10
- package/dist/src/local-first/LocalAuth.d.ts.map +1 -1
- package/dist/src/local-first/LocalAuth.js +6 -1
- package/dist/src/local-first/Owner.d.ts +70 -27
- package/dist/src/local-first/Owner.d.ts.map +1 -1
- package/dist/src/local-first/Owner.js +27 -14
- package/dist/src/local-first/Protocol.d.ts +58 -50
- package/dist/src/local-first/Protocol.d.ts.map +1 -1
- package/dist/src/local-first/Protocol.js +253 -155
- package/dist/src/local-first/Query.d.ts +133 -43
- package/dist/src/local-first/Query.d.ts.map +1 -1
- package/dist/src/local-first/Query.js +139 -90
- package/dist/src/local-first/Relay.d.ts +18 -38
- package/dist/src/local-first/Relay.d.ts.map +1 -1
- package/dist/src/local-first/Relay.js +59 -171
- package/dist/src/local-first/Schema.d.ts +139 -144
- package/dist/src/local-first/Schema.d.ts.map +1 -1
- package/dist/src/local-first/Schema.js +78 -163
- package/dist/src/local-first/Shared.d.ts +125 -0
- package/dist/src/local-first/Shared.d.ts.map +1 -0
- package/dist/src/local-first/Shared.js +569 -0
- package/dist/src/local-first/Storage.d.ts +50 -53
- package/dist/src/local-first/Storage.d.ts.map +1 -1
- package/dist/src/local-first/Storage.js +121 -173
- package/dist/src/local-first/Timestamp.d.ts +28 -35
- package/dist/src/local-first/Timestamp.d.ts.map +1 -1
- package/dist/src/local-first/Timestamp.js +24 -25
- package/dist/src/local-first/index.d.ts +2 -15
- package/dist/src/local-first/index.d.ts.map +1 -1
- package/dist/src/local-first/index.js +2 -15
- package/package.json +36 -26
- package/src/Array.ts +467 -106
- package/src/Assert.ts +87 -12
- package/src/BigInt.ts +7 -1
- package/src/Brand.ts +41 -12
- package/src/Buffer.ts +10 -4
- package/src/Cache.ts +11 -2
- package/src/Callbacks.ts +33 -23
- package/src/Console.ts +567 -178
- package/src/Crypto.ts +111 -62
- package/src/Eq.ts +30 -3
- package/src/Error.ts +50 -34
- package/src/Function.ts +140 -68
- package/src/Identicon.ts +7 -1
- package/src/Lookup.ts +415 -0
- package/src/Microtask.ts +58 -0
- package/src/Number.ts +40 -7
- package/src/Object.ts +155 -16
- package/src/Option.ts +74 -0
- package/src/Order.ts +11 -3
- package/src/Platform.ts +43 -7
- package/src/Polyfills.ts +465 -0
- package/src/Random.ts +25 -11
- package/src/Redacted.ts +8 -4
- package/src/Ref.ts +72 -21
- package/src/RefCount.ts +170 -0
- package/src/Relation.ts +188 -136
- package/src/Resource.ts +864 -0
- package/src/Result.ts +548 -381
- package/src/Schedule.ts +1471 -0
- package/src/Set.ts +247 -0
- package/src/Skiplist.ts +7 -1
- package/src/Sqlite.ts +409 -304
- package/src/Store.ts +94 -48
- package/src/String.ts +6 -0
- package/src/Task.ts +3685 -740
- package/src/Test.ts +162 -0
- package/src/Time.ts +348 -145
- package/src/Tracer.ts +54 -0
- package/src/Type.ts +851 -430
- package/src/Types.ts +202 -7
- package/src/WebSocket.ts +311 -178
- package/src/Worker.ts +527 -147
- package/src/index.ts +74 -3
- package/src/local-first/Db.ts +871 -597
- package/src/local-first/Error.ts +17 -0
- package/src/local-first/Evolu.ts +884 -824
- package/src/local-first/LocalAuth.ts +10 -10
- package/src/local-first/Owner.ts +94 -42
- package/src/local-first/Protocol.ts +440 -303
- package/src/local-first/Query.ts +243 -194
- package/src/local-first/Relay.ts +91 -252
- package/src/local-first/Schema.ts +284 -394
- package/src/local-first/Shared.ts +932 -0
- package/src/local-first/Storage.ts +206 -277
- package/src/local-first/Timestamp.ts +38 -45
- package/src/local-first/index.ts +2 -16
- package/LICENSE +0 -21
- package/dist/src/Instances.d.ts +0 -34
- package/dist/src/Instances.d.ts.map +0 -1
- package/dist/src/Instances.js +0 -44
- package/dist/src/Resources.d.ts +0 -118
- package/dist/src/Resources.d.ts.map +0 -1
- package/dist/src/Resources.js +0 -197
- package/dist/src/local-first/Platform.d.ts +0 -25
- package/dist/src/local-first/Platform.d.ts.map +0 -1
- package/dist/src/local-first/Platform.js +0 -1
- package/dist/src/local-first/Public.d.ts +0 -22
- package/dist/src/local-first/Public.d.ts.map +0 -1
- package/dist/src/local-first/Public.js +0 -15
- package/dist/src/local-first/PublicKysely.d.ts +0 -148
- package/dist/src/local-first/PublicKysely.d.ts.map +0 -1
- package/dist/src/local-first/PublicKysely.js +0 -184
- package/dist/src/local-first/Sync.d.ts +0 -112
- package/dist/src/local-first/Sync.d.ts.map +0 -1
- package/dist/src/local-first/Sync.js +0 -526
- package/src/Instances.ts +0 -92
- package/src/Resources.ts +0 -367
- package/src/local-first/Platform.ts +0 -27
- package/src/local-first/Public.ts +0 -42
- package/src/local-first/PublicKysely.ts +0 -239
- package/src/local-first/Sync.ts +0 -949
package/src/Time.ts
CHANGED
|
@@ -1,10 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Time representations, durations, and scheduling utilities.
|
|
3
|
+
*
|
|
4
|
+
* @module
|
|
5
|
+
*/
|
|
6
|
+
|
|
1
7
|
import { assert } from "./Assert.js";
|
|
2
|
-
import {
|
|
8
|
+
import type { Brand } from "./Brand.js";
|
|
9
|
+
import type { yieldNow } from "./Task.js";
|
|
10
|
+
import { brand, type DateIso, lessThan, NonNegativeInt } from "./Type.js";
|
|
11
|
+
import type {
|
|
12
|
+
Digit,
|
|
13
|
+
Digit1To23,
|
|
14
|
+
Digit1To51,
|
|
15
|
+
Digit1To59,
|
|
16
|
+
Digit1To6,
|
|
17
|
+
Digit1To9,
|
|
18
|
+
Digit1To99,
|
|
19
|
+
} from "./Types.js";
|
|
3
20
|
|
|
4
|
-
/**
|
|
21
|
+
/** Time and timer operations. */
|
|
5
22
|
export interface Time {
|
|
6
|
-
|
|
7
|
-
readonly
|
|
23
|
+
/** Returns current time as Unix epoch milliseconds. */
|
|
24
|
+
readonly now: () => Millis;
|
|
25
|
+
|
|
26
|
+
/** Returns current time as an ISO 8601 UTC string. */
|
|
27
|
+
readonly nowDateIso: () => DateIso;
|
|
28
|
+
|
|
29
|
+
/** Schedules a callback after the specified delay. */
|
|
30
|
+
readonly setTimeout: (fn: () => void, delay: Duration) => TimeoutId;
|
|
31
|
+
|
|
32
|
+
/** Cancels a timeout scheduled with {@link Time.setTimeout}. */
|
|
33
|
+
readonly clearTimeout: (id: TimeoutId) => void;
|
|
8
34
|
}
|
|
9
35
|
|
|
10
36
|
export interface TimeDep {
|
|
@@ -12,194 +38,371 @@ export interface TimeDep {
|
|
|
12
38
|
}
|
|
13
39
|
|
|
14
40
|
/**
|
|
15
|
-
*
|
|
41
|
+
* Opaque type for timeout handles.
|
|
16
42
|
*
|
|
17
|
-
*
|
|
18
|
-
|
|
19
|
-
|
|
43
|
+
* Use with {@link Time.clearTimeout} to cancel a pending timeout.
|
|
44
|
+
*/
|
|
45
|
+
export type TimeoutId = Brand<"TimeoutId">;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Creates a {@link Time} using `Date.now()` and `globalThis.setTimeout`.
|
|
49
|
+
*
|
|
50
|
+
* Throws if the system clock returns an out-of-range value. This is intentional
|
|
51
|
+
* — there's no reasonable fallback for a misconfigured clock.
|
|
20
52
|
*/
|
|
21
53
|
export const createTime = (): Time => {
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
54
|
+
const getNowMillis = (): Millis => Millis.orThrow(globalThis.Date.now());
|
|
55
|
+
|
|
56
|
+
return {
|
|
57
|
+
now: () => getNowMillis(),
|
|
58
|
+
|
|
59
|
+
nowDateIso: () => millisToDateIso(getNowMillis()),
|
|
60
|
+
|
|
61
|
+
setTimeout: (callback, delay) =>
|
|
62
|
+
globalThis.setTimeout(
|
|
63
|
+
callback,
|
|
64
|
+
durationToMillis(delay),
|
|
65
|
+
) as unknown as TimeoutId,
|
|
66
|
+
|
|
67
|
+
clearTimeout: (id) => {
|
|
68
|
+
globalThis.clearTimeout(id as unknown as number);
|
|
31
69
|
},
|
|
32
70
|
};
|
|
33
|
-
return time;
|
|
34
71
|
};
|
|
35
72
|
|
|
36
73
|
/**
|
|
37
|
-
*
|
|
38
|
-
*
|
|
74
|
+
* Test {@link Time} with controllable timers.
|
|
75
|
+
*
|
|
76
|
+
* Call `advance(ms)` to move time forward and trigger any pending timeouts.
|
|
77
|
+
*/
|
|
78
|
+
export interface TestTime extends Time {
|
|
79
|
+
/** Advances time by the specified duration, triggering pending timeouts. */
|
|
80
|
+
readonly advance: (duration: Duration) => void;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export interface TestTimeDep {
|
|
84
|
+
readonly time: TestTime;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Creates a {@link TestTime} with controllable timers for testing.
|
|
89
|
+
*
|
|
90
|
+
* Time starts at `startAt` (default 0) and only advances when `advance()` is
|
|
91
|
+
* called. Timeouts scheduled via `setTimeout` fire when time is advanced past
|
|
92
|
+
* their deadline.
|
|
93
|
+
*
|
|
94
|
+
* Set `autoIncrement` to automatically increment time by 1ms after each `now()`
|
|
95
|
+
* call via microtask (useful for tests that need monotonically increasing
|
|
96
|
+
* values without explicit `advance()` calls).
|
|
39
97
|
*/
|
|
40
|
-
export const
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
98
|
+
export const testCreateTime = (options?: {
|
|
99
|
+
readonly startAt?: Millis;
|
|
100
|
+
readonly autoIncrement?: boolean;
|
|
101
|
+
}): TestTime => {
|
|
102
|
+
let now = options?.startAt ?? minMillis;
|
|
103
|
+
const autoIncrement = options?.autoIncrement ?? false;
|
|
104
|
+
let nextId = 1;
|
|
105
|
+
|
|
106
|
+
const pending = new Map<number, { callback: () => void; runAt: number }>();
|
|
107
|
+
|
|
108
|
+
const getNowMillis = (): Millis => {
|
|
109
|
+
const result = now;
|
|
110
|
+
if (autoIncrement) {
|
|
45
111
|
queueMicrotask(() => {
|
|
46
|
-
now
|
|
112
|
+
now = Millis.orThrow(now + 1);
|
|
47
113
|
});
|
|
48
|
-
|
|
114
|
+
}
|
|
115
|
+
return result;
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
return {
|
|
119
|
+
now: () => getNowMillis(),
|
|
120
|
+
|
|
121
|
+
nowDateIso: () => millisToDateIso(getNowMillis()),
|
|
122
|
+
|
|
123
|
+
setTimeout: (callback, delay) => {
|
|
124
|
+
const id = nextId++;
|
|
125
|
+
pending.set(id, { callback, runAt: now + durationToMillis(delay) });
|
|
126
|
+
return id as unknown as TimeoutId;
|
|
127
|
+
},
|
|
128
|
+
|
|
129
|
+
clearTimeout: (id) => {
|
|
130
|
+
pending.delete(id as unknown as number);
|
|
131
|
+
},
|
|
132
|
+
|
|
133
|
+
advance: (duration) => {
|
|
134
|
+
now = Millis.orThrow(now + durationToMillis(duration));
|
|
135
|
+
|
|
136
|
+
for (const [id, timeout] of pending) {
|
|
137
|
+
if (timeout.runAt <= now) {
|
|
138
|
+
pending.delete(id);
|
|
139
|
+
timeout.callback();
|
|
140
|
+
}
|
|
141
|
+
}
|
|
49
142
|
},
|
|
50
|
-
nowIso: () =>
|
|
51
|
-
DateIso.orThrow(new globalThis.Date(time.now()).toISOString()),
|
|
52
143
|
};
|
|
53
|
-
return time;
|
|
54
144
|
};
|
|
55
145
|
|
|
56
|
-
|
|
57
|
-
|
|
146
|
+
// Literal (not expression like 1 + 2) to preserve type for Brand<"LessThan...">
|
|
147
|
+
const maxMillisWithInfinity = 281474976710655;
|
|
58
148
|
|
|
59
149
|
/**
|
|
60
|
-
*
|
|
61
|
-
*
|
|
150
|
+
* Milliseconds timestamp, like `Date.now()`.
|
|
151
|
+
*
|
|
152
|
+
* The maximum value is 281474976710654 (281474976710655 - 1, reserved for
|
|
153
|
+
* infinity). This enables efficient binary serialization, saving 2 bytes
|
|
154
|
+
* compared to typical 8-byte (64-bit) timestamps.
|
|
155
|
+
*
|
|
156
|
+
* `new Date(281474976710654).toString()` = Tue Aug 02 10889 07:31:49
|
|
157
|
+
*
|
|
158
|
+
* If a system clock exceeds this range, operations will throw. This is
|
|
159
|
+
* intentional — there's no reasonable fallback for a misconfigured clock.
|
|
62
160
|
*/
|
|
63
|
-
export
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
| `4${D}` // 40..49
|
|
69
|
-
| `5${D}`; // 50..59
|
|
161
|
+
export const Millis = /*#__PURE__*/ brand(
|
|
162
|
+
"Millis",
|
|
163
|
+
/*#__PURE__*/ lessThan(maxMillisWithInfinity)(NonNegativeInt),
|
|
164
|
+
);
|
|
165
|
+
export type Millis = typeof Millis.Type;
|
|
70
166
|
|
|
71
|
-
/**
|
|
72
|
-
export
|
|
73
|
-
| Exclude<D, "0"> // 1-9
|
|
74
|
-
| `1${D}` // 10-19
|
|
75
|
-
| `2${"0" | "1" | "2" | "3"}`; // 20-23
|
|
167
|
+
/** Minimum {@link Millis} value. */
|
|
168
|
+
export const minMillis = 0 as Millis;
|
|
76
169
|
|
|
77
|
-
/**
|
|
78
|
-
export
|
|
170
|
+
/** Maximum {@link Millis} value. */
|
|
171
|
+
export const maxMillis = (maxMillisWithInfinity - 1) as Millis;
|
|
79
172
|
|
|
80
173
|
/**
|
|
81
|
-
*
|
|
174
|
+
* Converts {@link Millis} to {@link DateIso}.
|
|
82
175
|
*
|
|
83
|
-
*
|
|
84
|
-
*
|
|
176
|
+
* This is a safe cast because {@link Millis} guarantees a valid timestamp range
|
|
177
|
+
* that always produces a valid ISO string.
|
|
178
|
+
*/
|
|
179
|
+
export const millisToDateIso = (value: Millis): DateIso =>
|
|
180
|
+
new globalThis.Date(value).toISOString() as DateIso;
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Duration can be either a {@link DurationLiteral} or milliseconds as
|
|
184
|
+
* {@link Millis}.
|
|
185
|
+
*/
|
|
186
|
+
export type Duration = DurationLiteral | Millis;
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Duration literal with compile-time validation.
|
|
85
190
|
*
|
|
86
191
|
* Supported formats:
|
|
87
192
|
*
|
|
88
|
-
* - Milliseconds: `
|
|
89
|
-
* - Seconds: `
|
|
90
|
-
* - Minutes: `
|
|
91
|
-
* - Hours: `1h`, `
|
|
92
|
-
* - Days: `1d`, `
|
|
93
|
-
* -
|
|
193
|
+
* - Milliseconds: `1ms`, `500ms`, `999ms` (1-999)
|
|
194
|
+
* - Seconds: `1s`, `59s`, `12.5s` (1-59, 1.1-59.9)
|
|
195
|
+
* - Minutes: `1m`, `59m`, `12.5m` (1-59, 1.1-59.9)
|
|
196
|
+
* - Hours: `1h`, `23h`, `12.5h` (1-23, 1.1-23.9)
|
|
197
|
+
* - Days: `1d`, `6d`, `1.5d` (1-6, 1.1-6.9)
|
|
198
|
+
* - Weeks: `1w`, `51w`, `1.5w` (1-51, 1.1-51.9)
|
|
199
|
+
* - Months: not supported (variable length)
|
|
200
|
+
* - Years: `1y`, `99y`, `1.5y` (1-99, 1.1-99.9)
|
|
201
|
+
*
|
|
202
|
+
* Each unit is limited to values that can't be expressed in the next larger
|
|
203
|
+
* unit, ensuring every duration has exactly one canonical representation (e.g.,
|
|
204
|
+
* 1000ms must be written as `"1s"`, not `"1000ms"`).
|
|
205
|
+
*
|
|
206
|
+
* Decimal values cover cases like 1.5s (1500ms) or 1.5h (90 minutes) without
|
|
207
|
+
* allowing redundant forms. For precise values that don't fit (e.g., 1050ms),
|
|
208
|
+
* use {@link Millis} directly.
|
|
94
209
|
*
|
|
95
|
-
*
|
|
96
|
-
*
|
|
97
|
-
*
|
|
98
|
-
*
|
|
210
|
+
* Zero duration (0ms) is not supported. For yielding without delay, use `await
|
|
211
|
+
* Promise.resolve()` for microtasks or the {@link yieldNow} for macrotasks.
|
|
212
|
+
*
|
|
213
|
+
* See {@link Duration} for a type that also accepts {@link Millis}. Use
|
|
214
|
+
* {@link durationToMillis} to convert to milliseconds.
|
|
215
|
+
*/
|
|
216
|
+
export type DurationLiteral =
|
|
217
|
+
| DurationLiteralMilliseconds
|
|
218
|
+
| DurationLiteralSeconds
|
|
219
|
+
| DurationLiteralMinutes
|
|
220
|
+
| DurationLiteralHours
|
|
221
|
+
| DurationLiteralDays
|
|
222
|
+
| DurationLiteralWeeks
|
|
223
|
+
| DurationLiteralYears;
|
|
224
|
+
|
|
225
|
+
/** Milliseconds duration: `"1ms"` to `"999ms"`. See {@link DurationLiteral}. */
|
|
226
|
+
export type DurationLiteralMilliseconds =
|
|
227
|
+
| `${Digit1To9}ms` // 1-9
|
|
228
|
+
| `${Digit1To9}${Digit}ms` // 10-99
|
|
229
|
+
| `${Digit1To9}${Digit}${Digit}ms`; // 100-999
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Seconds duration: `"1s"` to `"59s"` or `"1.1s"` to `"59.9s"`. See
|
|
233
|
+
* {@link DurationLiteral}.
|
|
99
234
|
*/
|
|
100
|
-
export type
|
|
101
|
-
| `${
|
|
102
|
-
| `${
|
|
103
|
-
| `${D}${D}${D}ms`
|
|
104
|
-
| `${MmSs}s`
|
|
105
|
-
| `${MmSs}m`
|
|
106
|
-
| `${Hours}h`
|
|
107
|
-
| `${Days}d`
|
|
108
|
-
| `${MmSs}s ${D}ms`
|
|
109
|
-
| `${MmSs}s ${D}${D}ms`
|
|
110
|
-
| `${MmSs}s ${D}${D}${D}ms`
|
|
111
|
-
| `${MmSs}m ${MmSs}s`
|
|
112
|
-
| `${Hours}h ${MmSs}m`
|
|
113
|
-
| `${Days}d ${Hours}h`;
|
|
235
|
+
export type DurationLiteralSeconds =
|
|
236
|
+
| `${Digit1To59}s` // 1-59
|
|
237
|
+
| `${Digit1To59}.${Digit1To9}s`; // 1.1-59.9
|
|
114
238
|
|
|
115
239
|
/**
|
|
116
|
-
*
|
|
117
|
-
* {@link
|
|
240
|
+
* Minutes duration: `"1m"` to `"59m"` or `"1.1m"` to `"59.9m"`. See
|
|
241
|
+
* {@link DurationLiteral}.
|
|
118
242
|
*/
|
|
119
|
-
export type
|
|
243
|
+
export type DurationLiteralMinutes =
|
|
244
|
+
| `${Digit1To59}m` // 1-59
|
|
245
|
+
| `${Digit1To59}.${Digit1To9}m`; // 1.1-59.9
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Hours duration: `"1h"` to `"23h"` or `"1.1h"` to `"23.9h"`. See
|
|
249
|
+
* {@link DurationLiteral}.
|
|
250
|
+
*/
|
|
251
|
+
export type DurationLiteralHours =
|
|
252
|
+
| `${Digit1To23}h` // 1-23
|
|
253
|
+
| `${Digit1To23}.${Digit1To9}h`; // 1.1-23.9
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Days duration: `"1d"` to `"6d"` or `"1.1d"` to `"6.9d"`. See
|
|
257
|
+
* {@link DurationLiteral}.
|
|
258
|
+
*/
|
|
259
|
+
export type DurationLiteralDays =
|
|
260
|
+
| `${Digit1To6}d` // 1-6
|
|
261
|
+
| `${Digit1To6}.${Digit1To9}d`; // 1.1-6.9
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Weeks duration: `"1w"` to `"51w"` or `"1.1w"` to `"51.9w"`. See
|
|
265
|
+
* {@link DurationLiteral}.
|
|
266
|
+
*/
|
|
267
|
+
export type DurationLiteralWeeks =
|
|
268
|
+
| `${Digit1To51}w` // 1-51
|
|
269
|
+
| `${Digit1To51}.${Digit1To9}w`; // 1.1-51.9
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Years duration: `"1y"` to `"99y"` or `"1.1y"` to `"99.9y"`. See
|
|
273
|
+
* {@link DurationLiteral}.
|
|
274
|
+
*/
|
|
275
|
+
export type DurationLiteralYears =
|
|
276
|
+
| `${Digit1To99}y` // 1-99
|
|
277
|
+
| `${Digit1To99}.${Digit1To9}y`; // 1.1-99.9
|
|
120
278
|
|
|
121
279
|
/**
|
|
122
280
|
* Converts a duration to milliseconds.
|
|
123
281
|
*
|
|
124
|
-
* Accepts either a {@link
|
|
125
|
-
*
|
|
282
|
+
* Accepts either a {@link DurationLiteral} (e.g., "5m", "1.5s") or
|
|
283
|
+
* {@link Millis}.
|
|
126
284
|
*
|
|
127
285
|
* ### Example
|
|
128
286
|
*
|
|
129
287
|
* ```ts
|
|
130
|
-
*
|
|
131
|
-
*
|
|
132
|
-
*
|
|
133
|
-
*
|
|
134
|
-
*
|
|
135
|
-
*
|
|
136
|
-
*
|
|
137
|
-
*
|
|
288
|
+
* durationToMillis("1ms"); // 1
|
|
289
|
+
* durationToMillis("500ms"); // 500
|
|
290
|
+
* durationToMillis("1.5s"); // 1500
|
|
291
|
+
* durationToMillis("30s"); // 30000
|
|
292
|
+
* durationToMillis("5m"); // 300000
|
|
293
|
+
* durationToMillis("12h"); // 43200000
|
|
294
|
+
* durationToMillis("7d"); // 604800000
|
|
295
|
+
* durationToMillis(Millis.orThrow(5000)); // 5000 (already Millis)
|
|
138
296
|
* ```
|
|
139
297
|
*/
|
|
140
|
-
export const
|
|
141
|
-
duration
|
|
142
|
-
): NonNegativeInt => {
|
|
143
|
-
// If it's already a NonNegativeInt (milliseconds), return as-is
|
|
144
|
-
if (typeof duration === "number") {
|
|
145
|
-
return duration;
|
|
146
|
-
}
|
|
298
|
+
export const durationToMillis = (duration: Duration): Millis => {
|
|
299
|
+
if (typeof duration === "number") return duration;
|
|
147
300
|
|
|
148
|
-
|
|
149
|
-
const
|
|
150
|
-
ms: 1,
|
|
151
|
-
s: 1000,
|
|
152
|
-
m: 60000,
|
|
153
|
-
h: 3600000,
|
|
154
|
-
d: 86400000, // 24 * 60 * 60 * 1000
|
|
155
|
-
} as const;
|
|
156
|
-
|
|
157
|
-
let total = 0;
|
|
158
|
-
let i = 0;
|
|
159
|
-
|
|
160
|
-
while (i < duration.length) {
|
|
161
|
-
// Skip whitespace
|
|
162
|
-
while (i < duration.length && duration[i] === " ") {
|
|
163
|
-
i++;
|
|
164
|
-
}
|
|
301
|
+
const num = parseFloat(duration);
|
|
302
|
+
const unit = duration.endsWith("ms") ? "ms" : duration.at(-1)!;
|
|
165
303
|
|
|
166
|
-
|
|
304
|
+
assert(unit in durationUnits, `Unknown duration unit: ${unit}`);
|
|
167
305
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
i++;
|
|
173
|
-
}
|
|
306
|
+
return Millis.orThrow(
|
|
307
|
+
Math.round(num * durationUnits[unit as keyof typeof durationUnits]),
|
|
308
|
+
);
|
|
309
|
+
};
|
|
174
310
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
311
|
+
/**
|
|
312
|
+
* Returns a Promise that resolves after the specified duration.
|
|
313
|
+
*
|
|
314
|
+
* Uses {@link Duration} parsing and `globalThis.setTimeout`.
|
|
315
|
+
*
|
|
316
|
+
* ### Example
|
|
317
|
+
*
|
|
318
|
+
* ```ts
|
|
319
|
+
* await setTimeout("1ms");
|
|
320
|
+
* ```
|
|
321
|
+
*/
|
|
322
|
+
export const setTimeout = (duration: Duration): Promise<void> =>
|
|
323
|
+
new Promise((resolve) => {
|
|
324
|
+
globalThis.setTimeout(resolve, durationToMillis(duration));
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
const durationUnits = {
|
|
328
|
+
ms: 1,
|
|
329
|
+
s: 1000,
|
|
330
|
+
m: 60000,
|
|
331
|
+
h: 3600000,
|
|
332
|
+
d: 86400000,
|
|
333
|
+
w: 604800000, // 7 days
|
|
334
|
+
y: 31536000000, // 365 days
|
|
335
|
+
} as const;
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Frame budget at 60fps (16ms).
|
|
339
|
+
*
|
|
340
|
+
* Work exceeding this blocks a frame, causing visible jank in animations.
|
|
341
|
+
*/
|
|
342
|
+
export const ms60fps = 16 as Millis;
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Frame budget at 120fps (8ms).
|
|
346
|
+
*
|
|
347
|
+
* For high refresh rate displays. Work exceeding this blocks a frame.
|
|
348
|
+
*/
|
|
349
|
+
export const ms120fps = 8 as Millis;
|
|
197
350
|
|
|
198
|
-
|
|
351
|
+
/**
|
|
352
|
+
* Long task threshold (50ms).
|
|
353
|
+
*
|
|
354
|
+
* Tasks exceeding this are "long tasks" per web standards. Use with
|
|
355
|
+
* {@link yieldNow} to yield periodically and keep UI responsive.
|
|
356
|
+
*
|
|
357
|
+
* @see https://web.dev/articles/optimize-long-tasks
|
|
358
|
+
*/
|
|
359
|
+
export const msLongTask = 50 as Millis;
|
|
199
360
|
|
|
200
|
-
|
|
201
|
-
|
|
361
|
+
/**
|
|
362
|
+
* Formats {@link Millis} as a human-readable duration string.
|
|
363
|
+
*
|
|
364
|
+
* - Under 1 minute: `1.234s`
|
|
365
|
+
* - Under 1 hour: `1m30.000s`
|
|
366
|
+
* - 1 hour or more: `1h30m45.000s`
|
|
367
|
+
*
|
|
368
|
+
* ### Example
|
|
369
|
+
*
|
|
370
|
+
* ```ts
|
|
371
|
+
* formatMillisAsDuration(1234 as Millis); // "1.234s"
|
|
372
|
+
* formatMillisAsDuration(90000 as Millis); // "1m30.000s"
|
|
373
|
+
* formatMillisAsDuration(3661000 as Millis); // "1h1m1.000s"
|
|
374
|
+
* ```
|
|
375
|
+
*/
|
|
376
|
+
export const formatMillisAsDuration = (millis: Millis): string => {
|
|
377
|
+
const elapsed = millis / 1000;
|
|
378
|
+
if (elapsed < 60) {
|
|
379
|
+
return `${elapsed.toFixed(3)}s`;
|
|
380
|
+
} else if (elapsed < 3600) {
|
|
381
|
+
const minutes = Math.floor(elapsed / 60);
|
|
382
|
+
const seconds = (elapsed % 60).toFixed(3);
|
|
383
|
+
return `${minutes}m${seconds}s`;
|
|
384
|
+
} else {
|
|
385
|
+
const hours = Math.floor(elapsed / 3600);
|
|
386
|
+
const minutes = Math.floor((elapsed % 3600) / 60);
|
|
387
|
+
const seconds = ((elapsed % 3600) % 60).toFixed(3);
|
|
388
|
+
return `${hours}h${minutes}m${seconds}s`;
|
|
202
389
|
}
|
|
390
|
+
};
|
|
203
391
|
|
|
204
|
-
|
|
392
|
+
/**
|
|
393
|
+
* Formats {@link Millis} as `HH:MM:SS.mmm`.
|
|
394
|
+
*
|
|
395
|
+
* ### Example
|
|
396
|
+
*
|
|
397
|
+
* ```ts
|
|
398
|
+
* formatMillisAsClockTime(Millis.orThrow(Date.now())); // "14:32:15.234"
|
|
399
|
+
* ```
|
|
400
|
+
*/
|
|
401
|
+
export const formatMillisAsClockTime = (millis: Millis): string => {
|
|
402
|
+
const date = new globalThis.Date(millis);
|
|
403
|
+
const hours = date.getHours().toString().padStart(2, "0");
|
|
404
|
+
const minutes = date.getMinutes().toString().padStart(2, "0");
|
|
405
|
+
const seconds = date.getSeconds().toString().padStart(2, "0");
|
|
406
|
+
const ms = date.getMilliseconds().toString().padStart(3, "0");
|
|
407
|
+
return `${hours}:${minutes}:${seconds}.${ms}`;
|
|
205
408
|
};
|
package/src/Tracer.ts
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal tracer interface for observability.
|
|
3
|
+
*
|
|
4
|
+
* @module
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { Ref } from "./Ref.js";
|
|
8
|
+
import type { Run } from "./Task.js";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Minimal tracer interface for observability.
|
|
12
|
+
*
|
|
13
|
+
* Tracer provides unified observability for both sync and async code. Use it
|
|
14
|
+
* directly for sync operations, or let {@link Run} use it automatically for
|
|
15
|
+
* async tasks.
|
|
16
|
+
*
|
|
17
|
+
* ### Example
|
|
18
|
+
*
|
|
19
|
+
* ```ts
|
|
20
|
+
* // Sync code — use tracer directly
|
|
21
|
+
* const result = deps.tracer?.span("parseData", () => parseData(input));
|
|
22
|
+
*
|
|
23
|
+
* // Async code — Run traces automatically
|
|
24
|
+
* await using run = createRun(deps);
|
|
25
|
+
* const result = await run(fetchUser(id)); // Traced if tracer provided
|
|
26
|
+
* ```
|
|
27
|
+
*
|
|
28
|
+
* TODO: Complete implementation with OpenTelemetry adapter.
|
|
29
|
+
*/
|
|
30
|
+
export interface Tracer {
|
|
31
|
+
/**
|
|
32
|
+
* Wraps a sync function in a traced span.
|
|
33
|
+
*
|
|
34
|
+
* Records start time, end time, and success/failure status.
|
|
35
|
+
*/
|
|
36
|
+
readonly span: <T>(name: string, fn: () => T) => T;
|
|
37
|
+
|
|
38
|
+
/** Records an event/fact (e.g., "user.purchased", "sync.completed"). */
|
|
39
|
+
readonly event: (name: string, data?: unknown) => void;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface TracerDep {
|
|
43
|
+
readonly tracer: Tracer;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Configuration for tracing. */
|
|
47
|
+
export interface TracerConfig {
|
|
48
|
+
/** When `true`, enables trace data collection. Can be toggled at runtime. */
|
|
49
|
+
readonly tracing?: Ref<boolean>;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface TracerConfigDep {
|
|
53
|
+
readonly tracerConfig: TracerConfig;
|
|
54
|
+
}
|