@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.
Files changed (237) hide show
  1. package/dist/src/Array.d.ts +279 -173
  2. package/dist/src/Array.d.ts.map +1 -1
  3. package/dist/src/Array.js +105 -101
  4. package/dist/src/Assert.d.ts +44 -44
  5. package/dist/src/Assert.d.ts.map +1 -1
  6. package/dist/src/Assert.js +44 -32
  7. package/dist/src/BigInt.d.ts +6 -3
  8. package/dist/src/BigInt.d.ts.map +1 -1
  9. package/dist/src/BigInt.js +5 -2
  10. package/dist/src/Brand.d.ts +27 -40
  11. package/dist/src/Brand.d.ts.map +1 -1
  12. package/dist/src/Buffer.d.ts +27 -11
  13. package/dist/src/Buffer.d.ts.map +1 -1
  14. package/dist/src/Cache.d.ts +9 -4
  15. package/dist/src/Cache.d.ts.map +1 -1
  16. package/dist/src/Cache.js +8 -3
  17. package/dist/src/Callbacks.d.ts +30 -19
  18. package/dist/src/Callbacks.d.ts.map +1 -1
  19. package/dist/src/Console.d.ts +145 -66
  20. package/dist/src/Console.d.ts.map +1 -1
  21. package/dist/src/Console.js +91 -43
  22. package/dist/src/Crypto.d.ts +85 -42
  23. package/dist/src/Crypto.d.ts.map +1 -1
  24. package/dist/src/Crypto.js +44 -20
  25. package/dist/src/Eq.d.ts +87 -21
  26. package/dist/src/Eq.d.ts.map +1 -1
  27. package/dist/src/Eq.js +67 -19
  28. package/dist/src/Error.d.ts +5 -3
  29. package/dist/src/Error.d.ts.map +1 -1
  30. package/dist/src/Error.js +16 -0
  31. package/dist/src/Function.d.ts +119 -58
  32. package/dist/src/Function.d.ts.map +1 -1
  33. package/dist/src/Function.js +86 -43
  34. package/dist/src/Http.d.ts +394 -0
  35. package/dist/src/Http.d.ts.map +1 -0
  36. package/dist/src/Http.js +102 -0
  37. package/dist/src/Identicon.d.ts +16 -7
  38. package/dist/src/Identicon.d.ts.map +1 -1
  39. package/dist/src/Identicon.js +14 -5
  40. package/dist/src/LeakDetector.d.ts +88 -0
  41. package/dist/src/LeakDetector.d.ts.map +1 -0
  42. package/dist/src/LeakDetector.js +86 -0
  43. package/dist/src/LockManager.d.ts +18 -9
  44. package/dist/src/LockManager.d.ts.map +1 -1
  45. package/dist/src/LockManager.js +7 -8
  46. package/dist/src/Lookup.d.ts +43 -13
  47. package/dist/src/Lookup.d.ts.map +1 -1
  48. package/dist/src/Lookup.js +28 -8
  49. package/dist/src/Microtask.d.ts +1 -1
  50. package/dist/src/Microtask.d.ts.map +1 -1
  51. package/dist/src/Microtask.js +3 -7
  52. package/dist/src/Number.d.ts +49 -13
  53. package/dist/src/Number.d.ts.map +1 -1
  54. package/dist/src/Number.js +34 -5
  55. package/dist/src/Object.d.ts +160 -40
  56. package/dist/src/Object.d.ts.map +1 -1
  57. package/dist/src/Object.js +92 -46
  58. package/dist/src/Option.d.ts +10 -7
  59. package/dist/src/Option.d.ts.map +1 -1
  60. package/dist/src/Option.js +9 -6
  61. package/dist/src/Order.d.ts +21 -21
  62. package/dist/src/Order.d.ts.map +1 -1
  63. package/dist/src/Order.js +18 -21
  64. package/dist/src/Platform.d.ts +31 -0
  65. package/dist/src/Platform.d.ts.map +1 -1
  66. package/dist/src/Platform.js +98 -0
  67. package/dist/src/Random.d.ts +32 -17
  68. package/dist/src/Random.d.ts.map +1 -1
  69. package/dist/src/Random.js +12 -8
  70. package/dist/src/Redacted.d.ts +43 -29
  71. package/dist/src/Redacted.d.ts.map +1 -1
  72. package/dist/src/Redacted.js +14 -3
  73. package/dist/src/Ref.d.ts +11 -3
  74. package/dist/src/Ref.d.ts.map +1 -1
  75. package/dist/src/Ref.js +2 -2
  76. package/dist/src/RefCount.d.ts +2 -6
  77. package/dist/src/RefCount.d.ts.map +1 -1
  78. package/dist/src/RefCount.js +33 -90
  79. package/dist/src/Relation.d.ts +70 -5
  80. package/dist/src/Relation.d.ts.map +1 -1
  81. package/dist/src/Relation.js +77 -2
  82. package/dist/src/Resource.d.ts +590 -155
  83. package/dist/src/Resource.d.ts.map +1 -1
  84. package/dist/src/Resource.js +592 -343
  85. package/dist/src/Result.d.ts +838 -191
  86. package/dist/src/Result.d.ts.map +1 -1
  87. package/dist/src/Result.js +163 -102
  88. package/dist/src/Schedule.d.ts +684 -231
  89. package/dist/src/Schedule.d.ts.map +1 -1
  90. package/dist/src/Schedule.js +766 -295
  91. package/dist/src/Set.d.ts +89 -40
  92. package/dist/src/Set.d.ts.map +1 -1
  93. package/dist/src/Set.js +50 -29
  94. package/dist/src/Sqlite.d.ts +29 -72
  95. package/dist/src/Sqlite.d.ts.map +1 -1
  96. package/dist/src/Sqlite.js +41 -46
  97. package/dist/src/StackTrace.d.ts +40 -0
  98. package/dist/src/StackTrace.d.ts.map +1 -0
  99. package/dist/src/StackTrace.js +140 -0
  100. package/dist/src/Store.d.ts +5 -4
  101. package/dist/src/Store.d.ts.map +1 -1
  102. package/dist/src/Store.js +5 -4
  103. package/dist/src/String.d.ts +7 -1
  104. package/dist/src/String.d.ts.map +1 -1
  105. package/dist/src/String.js +20 -7
  106. package/dist/src/Task.d.ts +3572 -1770
  107. package/dist/src/Task.d.ts.map +1 -1
  108. package/dist/src/Task.js +1879 -1274
  109. package/dist/src/Test.d.ts +17 -125
  110. package/dist/src/Test.d.ts.map +1 -1
  111. package/dist/src/Test.js +17 -68
  112. package/dist/src/Time.d.ts +108 -49
  113. package/dist/src/Time.d.ts.map +1 -1
  114. package/dist/src/Time.js +172 -73
  115. package/dist/src/Type.d.ts +3542 -1926
  116. package/dist/src/Type.d.ts.map +1 -1
  117. package/dist/src/Type.js +3070 -1784
  118. package/dist/src/Types.d.ts +200 -108
  119. package/dist/src/Types.d.ts.map +1 -1
  120. package/dist/src/Types.js +39 -8
  121. package/dist/src/WebSocket.d.ts +48 -23
  122. package/dist/src/WebSocket.d.ts.map +1 -1
  123. package/dist/src/WebSocket.js +15 -6
  124. package/dist/src/Worker.d.ts +94 -36
  125. package/dist/src/Worker.d.ts.map +1 -1
  126. package/dist/src/Worker.js +58 -24
  127. package/dist/src/index.d.ts +55 -52
  128. package/dist/src/index.d.ts.map +1 -1
  129. package/dist/src/index.js +3 -1
  130. package/dist/src/intl/cs.d.ts +94 -0
  131. package/dist/src/intl/cs.d.ts.map +1 -0
  132. package/dist/src/intl/cs.js +220 -0
  133. package/dist/src/intl/index.d.ts +7 -0
  134. package/dist/src/intl/index.d.ts.map +1 -0
  135. package/dist/src/intl/index.js +6 -0
  136. package/dist/src/local-first/Db.d.ts +12 -8
  137. package/dist/src/local-first/Db.d.ts.map +1 -1
  138. package/dist/src/local-first/Db.js +109 -132
  139. package/dist/src/local-first/Error.d.ts +4 -4
  140. package/dist/src/local-first/Evolu.d.ts +338 -92
  141. package/dist/src/local-first/Evolu.d.ts.map +1 -1
  142. package/dist/src/local-first/Evolu.js +47 -55
  143. package/dist/src/local-first/LocalAuth.d.ts +4 -4
  144. package/dist/src/local-first/LocalAuth.d.ts.map +1 -1
  145. package/dist/src/local-first/Owner.d.ts +61 -24
  146. package/dist/src/local-first/Owner.d.ts.map +1 -1
  147. package/dist/src/local-first/Owner.js +45 -8
  148. package/dist/src/local-first/Protocol.d.ts +44 -31
  149. package/dist/src/local-first/Protocol.d.ts.map +1 -1
  150. package/dist/src/local-first/Protocol.js +36 -13
  151. package/dist/src/local-first/Query.d.ts +147 -62
  152. package/dist/src/local-first/Query.d.ts.map +1 -1
  153. package/dist/src/local-first/Query.js +112 -58
  154. package/dist/src/local-first/Relay.d.ts +44 -14
  155. package/dist/src/local-first/Relay.d.ts.map +1 -1
  156. package/dist/src/local-first/Schema.d.ts +92 -41
  157. package/dist/src/local-first/Schema.d.ts.map +1 -1
  158. package/dist/src/local-first/Schema.js +25 -8
  159. package/dist/src/local-first/Shared.d.ts +23 -21
  160. package/dist/src/local-first/Shared.d.ts.map +1 -1
  161. package/dist/src/local-first/Shared.js +100 -57
  162. package/dist/src/local-first/Storage.d.ts +53 -32
  163. package/dist/src/local-first/Storage.d.ts.map +1 -1
  164. package/dist/src/local-first/Storage.js +194 -234
  165. package/dist/src/local-first/Timestamp.d.ts +21 -21
  166. package/dist/src/local-first/Timestamp.d.ts.map +1 -1
  167. package/dist/src/local-first/Timestamp.js +2 -2
  168. package/dist/src/local-first/index.d.ts +11 -11
  169. package/package.json +14 -13
  170. package/src/Array.ts +306 -205
  171. package/src/Assert.ts +54 -69
  172. package/src/BigInt.ts +6 -3
  173. package/src/Brand.ts +27 -40
  174. package/src/Buffer.ts +28 -12
  175. package/src/Cache.ts +9 -4
  176. package/src/Callbacks.ts +30 -19
  177. package/src/Console.ts +156 -84
  178. package/src/Crypto.ts +83 -41
  179. package/src/Eq.ts +95 -22
  180. package/src/Error.ts +8 -4
  181. package/src/Function.ts +167 -59
  182. package/src/Http.ts +549 -0
  183. package/src/Identicon.ts +16 -7
  184. package/src/LeakDetector.ts +182 -0
  185. package/src/LockManager.ts +43 -39
  186. package/src/Lookup.ts +62 -28
  187. package/src/Microtask.ts +19 -23
  188. package/src/Number.ts +64 -12
  189. package/src/Object.ts +173 -49
  190. package/src/Option.ts +10 -7
  191. package/src/Order.ts +23 -21
  192. package/src/Platform.ts +136 -0
  193. package/src/Polyfills.ts +6 -12
  194. package/src/Random.ts +32 -19
  195. package/src/Redacted.ts +44 -30
  196. package/src/Ref.ts +14 -6
  197. package/src/RefCount.ts +38 -80
  198. package/src/Relation.ts +202 -10
  199. package/src/Resource.ts +1199 -591
  200. package/src/Result.ts +930 -271
  201. package/src/Schedule.ts +859 -343
  202. package/src/Set.ts +89 -40
  203. package/src/Sqlite.ts +144 -138
  204. package/src/StackTrace.ts +230 -0
  205. package/src/Store.ts +8 -7
  206. package/src/String.ts +22 -5
  207. package/src/Task.ts +5101 -2832
  208. package/src/Test.ts +20 -168
  209. package/src/Time.ts +293 -90
  210. package/src/Type.ts +10034 -4117
  211. package/src/Types.ts +227 -110
  212. package/src/WebSocket.ts +64 -36
  213. package/src/Worker.ts +180 -72
  214. package/src/index.ts +55 -52
  215. package/src/intl/cs.ts +388 -0
  216. package/src/intl/index.ts +7 -0
  217. package/src/local-first/Db.ts +152 -179
  218. package/src/local-first/Error.ts +4 -4
  219. package/src/local-first/Evolu.ts +495 -250
  220. package/src/local-first/LocalAuth.ts +6 -12
  221. package/src/local-first/Owner.ts +58 -21
  222. package/src/local-first/Protocol.ts +49 -31
  223. package/src/local-first/Query.ts +146 -61
  224. package/src/local-first/Relay.ts +51 -21
  225. package/src/local-first/Schema.ts +109 -51
  226. package/src/local-first/Shared.ts +266 -226
  227. package/src/local-first/Storage.ts +266 -264
  228. package/src/local-first/Timestamp.ts +25 -18
  229. package/src/local-first/index.ts +11 -11
  230. package/dist/src/Skiplist.d.ts +0 -28
  231. package/dist/src/Skiplist.d.ts.map +0 -1
  232. package/dist/src/Skiplist.js +0 -64
  233. package/dist/src/Tracer.d.ts +0 -48
  234. package/dist/src/Tracer.d.ts.map +0 -1
  235. package/dist/src/Tracer.js +0 -6
  236. package/src/Skiplist.ts +0 -108
  237. package/src/Tracer.ts +0 -54
package/dist/src/Time.js CHANGED
@@ -4,24 +4,86 @@
4
4
  * @module
5
5
  */
6
6
  import { assert } from "./Assert.js";
7
- import { brand, lessThan, NonNegativeInt } from "./Type.js";
7
+ import { brand, lessThan, NonNegativeInt, positive, } from "./Type.js";
8
8
  /**
9
- * Creates a {@link Time} using `Date.now()` and `globalThis.setTimeout`.
9
+ * Creates a {@link Time} using `Date.now()`, `performance`, and
10
+ * `globalThis.setTimeout`.
11
+ *
12
+ * Long timeouts are split into native timer chunks and tracked against an
13
+ * absolute wall-clock deadline so late callback execution and system suspension
14
+ * do not extend the requested delay. System clock adjustments can therefore
15
+ * shorten or lengthen long timeouts.
10
16
  *
11
17
  * Throws if the system clock returns an out-of-range value. This is intentional
12
18
  * — there's no reasonable fallback for a misconfigured clock.
13
19
  */
14
20
  export const createTime = () => {
15
- const getNowMillis = () => Millis.orThrow(globalThis.Date.now());
21
+ const timeoutOwner = Symbol("Time");
22
+ function now(type) {
23
+ const millis = getSystemNowMillis();
24
+ return type === "DateIso" ? millisToDateIso(millis) : millis;
25
+ }
16
26
  return {
17
- now: () => getNowMillis(),
18
- nowDateIso: () => millisToDateIso(getNowMillis()),
19
- setTimeout: (callback, delay) => globalThis.setTimeout(callback, durationToMillis(delay)),
27
+ now,
28
+ performance: {
29
+ timeOrigin: globalThis.performance.timeOrigin,
30
+ now: () => globalThis.performance.now(),
31
+ },
32
+ setTimeout: (callback, duration) => scheduleNativeTimeout(timeoutOwner, callback, duration),
20
33
  clearTimeout: (id) => {
21
- globalThis.clearTimeout(id);
34
+ clearTimeoutId(timeoutOwner, id);
35
+ },
36
+ };
37
+ };
38
+ const scheduleNativeTimeout = (owner, callback, duration) => {
39
+ const delay = durationToMillis(duration);
40
+ let cancelled = false;
41
+ let nativeId;
42
+ if (delay <= maxNativeTimeoutMillis) {
43
+ nativeId = globalThis.setTimeout(() => {
44
+ if (cancelled)
45
+ return;
46
+ cancelled = true;
47
+ callback();
48
+ }, delay);
49
+ }
50
+ else {
51
+ // Recompute each chunk from one absolute deadline so time elapsed while the
52
+ // event loop is suspended, for example during system sleep, is not added again.
53
+ const deadline = getSystemNowMillis() + delay;
54
+ const onTimeout = () => {
55
+ if (cancelled)
56
+ return;
57
+ const remaining = deadline - getSystemNowMillis();
58
+ if (remaining > 0) {
59
+ nativeId = globalThis.setTimeout(onTimeout, Math.min(remaining, maxNativeTimeoutMillis));
60
+ return;
61
+ }
62
+ cancelled = true;
63
+ callback();
64
+ };
65
+ nativeId = globalThis.setTimeout(onTimeout, maxNativeTimeoutMillis);
66
+ }
67
+ return {
68
+ owner,
69
+ clear: () => {
70
+ cancelled = true;
71
+ globalThis.clearTimeout(nativeId);
22
72
  },
23
73
  };
24
74
  };
75
+ /**
76
+ * Maximum delay supported reliably by native timers: the largest positive
77
+ * signed 32-bit integer, approximately 24.9 days in milliseconds. Longer
78
+ * logical delays are scheduled in chunks to avoid native timer overflow.
79
+ */
80
+ const maxNativeTimeoutMillis = 2 ** 31 - 1;
81
+ const getSystemNowMillis = () => Millis.orThrow(globalThis.Date.now());
82
+ const clearTimeoutId = (owner, id) => {
83
+ const internal = id;
84
+ assert(internal.owner === owner, "TimeoutId was created by another Time instance");
85
+ internal.clear();
86
+ };
25
87
  /**
26
88
  * Creates a {@link TestTime} with controllable timers for testing.
27
89
  *
@@ -29,15 +91,18 @@ export const createTime = () => {
29
91
  * called. Timeouts scheduled via `setTimeout` fire when time is advanced past
30
92
  * their deadline.
31
93
  *
32
- * Set `autoIncrement` to automatically increment time by 1ms after each `now()`
33
- * call. `"microtask"` increments after the current turn, while `"sync"`
34
- * increments immediately after each read. Omit it to keep time fixed until
35
- * `advance()` is called.
94
+ * Set `autoIncrement` to automatically increment time by 1ms after each
95
+ * wall-clock or performance `now()` call. `"microtask"` increments after the
96
+ * current turn, while `"sync"` increments immediately after each read. Omit it
97
+ * to keep time fixed until `advance()` is called.
36
98
  */
37
99
  export const testCreateTime = (options) => {
38
- let now = options?.startAt ?? minMillis;
100
+ const startAt = options?.startAt ?? minMillis;
39
101
  const autoIncrement = options?.autoIncrement;
102
+ const timeoutOwner = Symbol("TestTime");
103
+ let now = startAt;
40
104
  let nextId = 1;
105
+ let advancing = false;
41
106
  const pending = new Map();
42
107
  const incrementNow = () => {
43
108
  now = Millis.orThrow(now + 1);
@@ -54,24 +119,55 @@ export const testCreateTime = (options) => {
54
119
  }
55
120
  return result;
56
121
  };
122
+ function getNow(type) {
123
+ const millis = getNowMillis();
124
+ return type === "DateIso" ? millisToDateIso(millis) : millis;
125
+ }
57
126
  return {
58
- now: () => getNowMillis(),
59
- nowDateIso: () => millisToDateIso(getNowMillis()),
127
+ now: getNow,
128
+ performance: {
129
+ timeOrigin: Number(startAt),
130
+ now: () => (getNowMillis() - startAt),
131
+ },
60
132
  setTimeout: (callback, delay) => {
133
+ const runAt = Millis.orThrow(now + durationToMillis(delay));
61
134
  const id = nextId++;
62
- pending.set(id, { callback, runAt: now + durationToMillis(delay) });
63
- return id;
135
+ pending.set(id, { callback, runAt });
136
+ return {
137
+ owner: timeoutOwner,
138
+ clear: () => {
139
+ pending.delete(id);
140
+ },
141
+ };
64
142
  },
65
143
  clearTimeout: (id) => {
66
- pending.delete(id);
144
+ clearTimeoutId(timeoutOwner, id);
67
145
  },
68
146
  advance: (duration) => {
69
- now = Millis.orThrow(now + durationToMillis(duration));
70
- for (const [id, timeout] of pending) {
71
- if (timeout.runAt <= now) {
72
- pending.delete(id);
147
+ assert(!advancing, "TestTime.advance cannot be called while advancing");
148
+ const target = Millis.orThrow(now + durationToMillis(duration));
149
+ advancing = true;
150
+ try {
151
+ while (pending.size > 0) {
152
+ let earliestId = null;
153
+ let nextRunAt = Number.POSITIVE_INFINITY;
154
+ for (const [id, timeout] of pending) {
155
+ if (timeout.runAt <= target && timeout.runAt < nextRunAt) {
156
+ earliestId = id;
157
+ nextRunAt = timeout.runAt;
158
+ }
159
+ }
160
+ if (earliestId === null)
161
+ break;
162
+ const timeout = pending.get(earliestId);
163
+ pending.delete(earliestId);
164
+ now = Millis.orThrow(Math.max(now, nextRunAt));
73
165
  timeout.callback();
74
166
  }
167
+ now = Millis.orThrow(Math.max(now, target));
168
+ }
169
+ finally {
170
+ advancing = false;
75
171
  }
76
172
  },
77
173
  };
@@ -79,7 +175,7 @@ export const testCreateTime = (options) => {
79
175
  // Literal (not expression like 1 + 2) to preserve type for Brand<"LessThan...">
80
176
  const maxMillisWithInfinity = 281474976710655;
81
177
  /**
82
- * Milliseconds timestamp, like `Date.now()`.
178
+ * Non-negative integer milliseconds used for timestamps and durations.
83
179
  *
84
180
  * The maximum value is 281474976710654 (281474976710655 - 1, reserved for
85
181
  * infinity). This enables efficient binary serialization, saving 2 bytes
@@ -92,6 +188,8 @@ const maxMillisWithInfinity = 281474976710655;
92
188
  */
93
189
  export const Millis = /*#__PURE__*/ brand("Millis",
94
190
  /*#__PURE__*/ lessThan(maxMillisWithInfinity)(NonNegativeInt));
191
+ /** Positive {@link Millis} value. */
192
+ export const PositiveMillis = /*#__PURE__*/ positive(Millis);
95
193
  /** Minimum {@link Millis} value. */
96
194
  export const minMillis = 0;
97
195
  /** Maximum {@link Millis} value. */
@@ -109,46 +207,22 @@ export const saturateMillis = (value) => Millis.orNull(Math.max(0, Math.round(va
109
207
  */
110
208
  export const millisToDateIso = (value) => new globalThis.Date(value).toISOString();
111
209
  /**
112
- * Converts a duration to milliseconds.
210
+ * Returns the elapsed fractional milliseconds between two performance times.
113
211
  *
114
- * Accepts either a {@link DurationLiteral} (e.g., "5m", "1.5s") or
115
- * {@link Millis}.
116
- *
117
- * ### Example
118
- *
119
- * ```ts
120
- * durationToMillis("1ms"); // 1
121
- * durationToMillis("500ms"); // 500
122
- * durationToMillis("1.5s"); // 1500
123
- * durationToMillis("30s"); // 30000
124
- * durationToMillis("5m"); // 300000
125
- * durationToMillis("12h"); // 43200000
126
- * durationToMillis("7d"); // 604800000
127
- * durationToMillis(Millis.orThrow(5000)); // 5000 (already Millis)
128
- * ```
212
+ * Throws if `end` precedes `start`.
129
213
  */
130
- export const durationToMillis = (duration) => {
214
+ export const performanceDurationBetween = (start, end) => {
215
+ assert(end >= start, "Performance end time must not precede start time");
216
+ return (end - start);
217
+ };
218
+ export function durationToMillis(duration) {
131
219
  if (typeof duration === "number")
132
220
  return duration;
133
221
  const num = parseFloat(duration);
134
222
  const unit = duration.endsWith("ms") ? "ms" : duration.at(-1);
135
223
  assert(unit in durationUnits, `Unknown duration unit: ${unit}`);
136
224
  return Millis.orThrow(Math.round(num * durationUnits[unit]));
137
- };
138
- /**
139
- * Returns a Promise that resolves after the specified duration.
140
- *
141
- * Uses {@link Duration} parsing and `globalThis.setTimeout`.
142
- *
143
- * ### Example
144
- *
145
- * ```ts
146
- * await setTimeout("1ms");
147
- * ```
148
- */
149
- export const setTimeout = (duration) => new Promise((resolve) => {
150
- globalThis.setTimeout(resolve, durationToMillis(duration));
151
- });
225
+ }
152
226
  const durationUnits = {
153
227
  ms: 1,
154
228
  s: 1000,
@@ -184,40 +258,65 @@ export const msLongTask = 50;
184
258
  *
185
259
  * - Under 1 minute: `1.234s`
186
260
  * - Under 1 hour: `1m30.000s`
187
- * - 1 hour or more: `1h30m45.000s`
261
+ * - Under 1 day: `1h30m45.000s`
262
+ * - Under 1 week: `1d2h30m45.000s`
263
+ * - Under 1 year: `1w2d3h30m45.000s`
264
+ * - 1 year or more: `1y2w3d4h30m45.000s`
265
+ *
266
+ * Weeks are 7 days and years are 365 days.
188
267
  *
189
268
  * ### Example
190
269
  *
191
270
  * ```ts
192
- * formatMillisAsDuration(1234 as Millis); // "1.234s"
193
- * formatMillisAsDuration(90000 as Millis); // "1m30.000s"
194
- * formatMillisAsDuration(3661000 as Millis); // "1h1m1.000s"
271
+ * import { formatMillisAsDuration, Millis } from "@evolu/common";
272
+ *
273
+ * expect(formatMillisAsDuration(Millis.orThrow(1234))).toBe("1.234s");
274
+ * expect(formatMillisAsDuration(Millis.orThrow(90000))).toBe("1m30.000s");
275
+ * expect(formatMillisAsDuration(Millis.orThrow(90061000))).toBe(
276
+ * "1d1h1m1.000s",
277
+ * );
195
278
  * ```
196
279
  */
197
280
  export const formatMillisAsDuration = (millis) => {
198
- const elapsed = millis / 1000;
199
- if (elapsed < 60) {
200
- return `${elapsed.toFixed(3)}s`;
201
- }
202
- else if (elapsed < 3600) {
203
- const minutes = Math.floor(elapsed / 60);
204
- const seconds = (elapsed % 60).toFixed(3);
281
+ const seconds = ((millis % durationUnits.m) / durationUnits.s).toFixed(3);
282
+ if (millis < durationUnits.m)
283
+ return `${seconds}s`;
284
+ const minutes = Math.floor(millis / durationUnits.m) % 60;
285
+ if (millis < durationUnits.h)
205
286
  return `${minutes}m${seconds}s`;
206
- }
207
- else {
208
- const hours = Math.floor(elapsed / 3600);
209
- const minutes = Math.floor((elapsed % 3600) / 60);
210
- const seconds = ((elapsed % 3600) % 60).toFixed(3);
287
+ const hours = Math.floor(millis / durationUnits.h) % 24;
288
+ if (millis < durationUnits.d)
211
289
  return `${hours}h${minutes}m${seconds}s`;
212
- }
290
+ const daysAfterYears = Math.floor((millis % durationUnits.y) / durationUnits.d);
291
+ const days = daysAfterYears % 7;
292
+ if (millis < durationUnits.w)
293
+ return `${days}d${hours}h${minutes}m${seconds}s`;
294
+ const weeks = Math.floor(daysAfterYears / 7);
295
+ if (millis < durationUnits.y)
296
+ return `${weeks}w${days}d${hours}h${minutes}m${seconds}s`;
297
+ const years = Math.floor(millis / durationUnits.y);
298
+ return `${years}y${weeks}w${days}d${hours}h${minutes}m${seconds}s`;
213
299
  };
214
300
  /**
215
- * Formats {@link Millis} as `HH:MM:SS.mmm`.
301
+ * Formats {@link Millis} as local time in `HH:MM:SS.mmm` format.
216
302
  *
217
303
  * ### Example
218
304
  *
219
305
  * ```ts
220
- * formatMillisAsClockTime(Millis.orThrow(Date.now())); // "14:32:15.234"
306
+ * import { formatMillisAsClockTime, Millis } from "@evolu/common";
307
+ *
308
+ * const timestamp = new globalThis.Date(
309
+ * 2026,
310
+ * 0,
311
+ * 28,
312
+ * 14,
313
+ * 32,
314
+ * 15,
315
+ * 234,
316
+ * ).getTime();
317
+ * expect(formatMillisAsClockTime(Millis.orThrow(timestamp))).toBe(
318
+ * "14:32:15.234",
319
+ * );
221
320
  * ```
222
321
  */
223
322
  export const formatMillisAsClockTime = (millis) => {