@evolu/common 7.4.0 → 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.
Files changed (251) hide show
  1. package/README.md +4 -61
  2. package/dist/src/Array.d.ts +343 -102
  3. package/dist/src/Array.d.ts.map +1 -1
  4. package/dist/src/Array.js +181 -85
  5. package/dist/src/Assert.d.ts +57 -11
  6. package/dist/src/Assert.d.ts.map +1 -1
  7. package/dist/src/Assert.js +47 -11
  8. package/dist/src/BigInt.d.ts +6 -1
  9. package/dist/src/BigInt.d.ts.map +1 -1
  10. package/dist/src/BigInt.js +5 -0
  11. package/dist/src/Brand.d.ts +40 -12
  12. package/dist/src/Brand.d.ts.map +1 -1
  13. package/dist/src/Brand.js +5 -0
  14. package/dist/src/Buffer.d.ts +6 -1
  15. package/dist/src/Buffer.d.ts.map +1 -1
  16. package/dist/src/Buffer.js +7 -2
  17. package/dist/src/Cache.d.ts +10 -2
  18. package/dist/src/Cache.d.ts.map +1 -1
  19. package/dist/src/Cache.js +6 -0
  20. package/dist/src/Callbacks.d.ts +10 -7
  21. package/dist/src/Callbacks.d.ts.map +1 -1
  22. package/dist/src/Callbacks.js +23 -13
  23. package/dist/src/Console.d.ts +361 -69
  24. package/dist/src/Console.d.ts.map +1 -1
  25. package/dist/src/Console.js +217 -117
  26. package/dist/src/Crypto.d.ts +70 -21
  27. package/dist/src/Crypto.d.ts.map +1 -1
  28. package/dist/src/Crypto.js +72 -27
  29. package/dist/src/Eq.d.ts +22 -2
  30. package/dist/src/Eq.d.ts.map +1 -1
  31. package/dist/src/Eq.js +21 -1
  32. package/dist/src/Error.d.ts +23 -12
  33. package/dist/src/Error.d.ts.map +1 -1
  34. package/dist/src/Error.js +27 -11
  35. package/dist/src/Function.d.ts +128 -56
  36. package/dist/src/Function.d.ts.map +1 -1
  37. package/dist/src/Function.js +118 -14
  38. package/dist/src/Identicon.d.ts +5 -0
  39. package/dist/src/Identicon.d.ts.map +1 -1
  40. package/dist/src/Identicon.js +6 -1
  41. package/dist/src/Lookup.d.ts +160 -0
  42. package/dist/src/Lookup.d.ts.map +1 -0
  43. package/dist/src/Lookup.js +192 -0
  44. package/dist/src/Microtask.d.ts +21 -0
  45. package/dist/src/Microtask.d.ts.map +1 -0
  46. package/dist/src/Microtask.js +37 -0
  47. package/dist/src/Number.d.ts +18 -4
  48. package/dist/src/Number.d.ts.map +1 -1
  49. package/dist/src/Number.js +23 -1
  50. package/dist/src/Object.d.ts +110 -5
  51. package/dist/src/Object.d.ts.map +1 -1
  52. package/dist/src/Object.js +108 -5
  53. package/dist/src/Option.d.ts +58 -0
  54. package/dist/src/Option.d.ts.map +1 -0
  55. package/dist/src/Option.js +43 -0
  56. package/dist/src/Order.d.ts +5 -0
  57. package/dist/src/Order.d.ts.map +1 -1
  58. package/dist/src/Order.js +8 -3
  59. package/dist/src/Platform.d.ts +36 -2
  60. package/dist/src/Platform.d.ts.map +1 -1
  61. package/dist/src/Platform.js +10 -6
  62. package/dist/src/Polyfills.d.ts +27 -0
  63. package/dist/src/Polyfills.d.ts.map +1 -0
  64. package/dist/src/Polyfills.js +299 -0
  65. package/dist/src/Random.d.ts +18 -7
  66. package/dist/src/Random.d.ts.map +1 -1
  67. package/dist/src/Random.js +9 -7
  68. package/dist/src/Redacted.d.ts +7 -4
  69. package/dist/src/Redacted.d.ts.map +1 -1
  70. package/dist/src/Redacted.js +5 -0
  71. package/dist/src/Ref.d.ts +31 -16
  72. package/dist/src/Ref.d.ts.map +1 -1
  73. package/dist/src/Ref.js +35 -7
  74. package/dist/src/RefCount.d.ts +62 -0
  75. package/dist/src/RefCount.d.ts.map +1 -0
  76. package/dist/src/RefCount.js +83 -0
  77. package/dist/src/Relation.d.ts +84 -29
  78. package/dist/src/Relation.d.ts.map +1 -1
  79. package/dist/src/Relation.js +83 -105
  80. package/dist/src/Resource.d.ts +263 -0
  81. package/dist/src/Resource.d.ts.map +1 -0
  82. package/dist/src/Resource.js +389 -0
  83. package/dist/src/Result.d.ts +390 -374
  84. package/dist/src/Result.d.ts.map +1 -1
  85. package/dist/src/Result.js +142 -70
  86. package/dist/src/Schedule.d.ts +953 -0
  87. package/dist/src/Schedule.d.ts.map +1 -0
  88. package/dist/src/Schedule.js +1199 -0
  89. package/dist/src/Set.d.ts +181 -0
  90. package/dist/src/Set.d.ts.map +1 -0
  91. package/dist/src/Set.js +137 -0
  92. package/dist/src/Skiplist.d.ts +6 -1
  93. package/dist/src/Skiplist.d.ts.map +1 -1
  94. package/dist/src/Skiplist.js +5 -0
  95. package/dist/src/Sqlite.d.ts +160 -60
  96. package/dist/src/Sqlite.d.ts.map +1 -1
  97. package/dist/src/Sqlite.js +290 -190
  98. package/dist/src/Store.d.ts +36 -28
  99. package/dist/src/Store.d.ts.map +1 -1
  100. package/dist/src/Store.js +55 -15
  101. package/dist/src/String.d.ts +5 -0
  102. package/dist/src/String.d.ts.map +1 -1
  103. package/dist/src/String.js +5 -0
  104. package/dist/src/Task.d.ts +2263 -423
  105. package/dist/src/Task.d.ts.map +1 -1
  106. package/dist/src/Task.js +1488 -348
  107. package/dist/src/Test.d.ts +122 -0
  108. package/dist/src/Test.d.ts.map +1 -0
  109. package/dist/src/Test.js +66 -0
  110. package/dist/src/Time.d.ts +201 -48
  111. package/dist/src/Time.d.ts.map +1 -1
  112. package/dist/src/Time.js +190 -86
  113. package/dist/src/Tracer.d.ts +48 -0
  114. package/dist/src/Tracer.d.ts.map +1 -0
  115. package/dist/src/Tracer.js +6 -0
  116. package/dist/src/Type.d.ts +582 -209
  117. package/dist/src/Type.d.ts.map +1 -1
  118. package/dist/src/Type.js +362 -240
  119. package/dist/src/Types.d.ts +160 -6
  120. package/dist/src/Types.d.ts.map +1 -1
  121. package/dist/src/Types.js +22 -2
  122. package/dist/src/WebSocket.d.ts +113 -58
  123. package/dist/src/WebSocket.d.ts.map +1 -1
  124. package/dist/src/WebSocket.js +210 -118
  125. package/dist/src/Worker.d.ts +245 -60
  126. package/dist/src/Worker.d.ts.map +1 -1
  127. package/dist/src/Worker.js +220 -64
  128. package/dist/src/index.d.ts +25 -3
  129. package/dist/src/index.d.ts.map +1 -1
  130. package/dist/src/index.js +19 -3
  131. package/dist/src/local-first/Db.d.ts +24 -213
  132. package/dist/src/local-first/Db.d.ts.map +1 -1
  133. package/dist/src/local-first/Db.js +622 -293
  134. package/dist/src/local-first/Error.d.ts +12 -0
  135. package/dist/src/local-first/Error.d.ts.map +1 -0
  136. package/dist/src/local-first/Error.js +6 -0
  137. package/dist/src/local-first/Evolu.d.ts +330 -285
  138. package/dist/src/local-first/Evolu.d.ts.map +1 -1
  139. package/dist/src/local-first/Evolu.js +472 -402
  140. package/dist/src/local-first/LocalAuth.d.ts +10 -10
  141. package/dist/src/local-first/LocalAuth.d.ts.map +1 -1
  142. package/dist/src/local-first/LocalAuth.js +6 -1
  143. package/dist/src/local-first/Owner.d.ts +70 -27
  144. package/dist/src/local-first/Owner.d.ts.map +1 -1
  145. package/dist/src/local-first/Owner.js +27 -14
  146. package/dist/src/local-first/Protocol.d.ts +58 -50
  147. package/dist/src/local-first/Protocol.d.ts.map +1 -1
  148. package/dist/src/local-first/Protocol.js +253 -155
  149. package/dist/src/local-first/Query.d.ts +133 -43
  150. package/dist/src/local-first/Query.d.ts.map +1 -1
  151. package/dist/src/local-first/Query.js +139 -90
  152. package/dist/src/local-first/Relay.d.ts +18 -38
  153. package/dist/src/local-first/Relay.d.ts.map +1 -1
  154. package/dist/src/local-first/Relay.js +59 -171
  155. package/dist/src/local-first/Schema.d.ts +139 -144
  156. package/dist/src/local-first/Schema.d.ts.map +1 -1
  157. package/dist/src/local-first/Schema.js +78 -163
  158. package/dist/src/local-first/Shared.d.ts +125 -0
  159. package/dist/src/local-first/Shared.d.ts.map +1 -0
  160. package/dist/src/local-first/Shared.js +569 -0
  161. package/dist/src/local-first/Storage.d.ts +50 -53
  162. package/dist/src/local-first/Storage.d.ts.map +1 -1
  163. package/dist/src/local-first/Storage.js +121 -173
  164. package/dist/src/local-first/Timestamp.d.ts +28 -35
  165. package/dist/src/local-first/Timestamp.d.ts.map +1 -1
  166. package/dist/src/local-first/Timestamp.js +24 -25
  167. package/dist/src/local-first/index.d.ts +2 -15
  168. package/dist/src/local-first/index.d.ts.map +1 -1
  169. package/dist/src/local-first/index.js +2 -15
  170. package/package.json +36 -26
  171. package/src/Array.ts +467 -106
  172. package/src/Assert.ts +87 -12
  173. package/src/BigInt.ts +7 -1
  174. package/src/Brand.ts +41 -12
  175. package/src/Buffer.ts +10 -4
  176. package/src/Cache.ts +11 -2
  177. package/src/Callbacks.ts +33 -23
  178. package/src/Console.ts +567 -178
  179. package/src/Crypto.ts +111 -62
  180. package/src/Eq.ts +30 -3
  181. package/src/Error.ts +50 -34
  182. package/src/Function.ts +140 -68
  183. package/src/Identicon.ts +7 -1
  184. package/src/Lookup.ts +415 -0
  185. package/src/Microtask.ts +58 -0
  186. package/src/Number.ts +40 -7
  187. package/src/Object.ts +155 -16
  188. package/src/Option.ts +74 -0
  189. package/src/Order.ts +11 -3
  190. package/src/Platform.ts +43 -7
  191. package/src/Polyfills.ts +465 -0
  192. package/src/Random.ts +25 -11
  193. package/src/Redacted.ts +8 -4
  194. package/src/Ref.ts +72 -21
  195. package/src/RefCount.ts +170 -0
  196. package/src/Relation.ts +188 -136
  197. package/src/Resource.ts +864 -0
  198. package/src/Result.ts +548 -381
  199. package/src/Schedule.ts +1471 -0
  200. package/src/Set.ts +247 -0
  201. package/src/Skiplist.ts +7 -1
  202. package/src/Sqlite.ts +409 -304
  203. package/src/Store.ts +94 -48
  204. package/src/String.ts +6 -0
  205. package/src/Task.ts +3685 -740
  206. package/src/Test.ts +162 -0
  207. package/src/Time.ts +348 -145
  208. package/src/Tracer.ts +54 -0
  209. package/src/Type.ts +1064 -630
  210. package/src/Types.ts +202 -7
  211. package/src/WebSocket.ts +311 -178
  212. package/src/Worker.ts +527 -147
  213. package/src/index.ts +74 -3
  214. package/src/local-first/Db.ts +871 -597
  215. package/src/local-first/Error.ts +17 -0
  216. package/src/local-first/Evolu.ts +884 -823
  217. package/src/local-first/LocalAuth.ts +10 -10
  218. package/src/local-first/Owner.ts +94 -42
  219. package/src/local-first/Protocol.ts +440 -303
  220. package/src/local-first/Query.ts +243 -194
  221. package/src/local-first/Relay.ts +91 -252
  222. package/src/local-first/Schema.ts +284 -394
  223. package/src/local-first/Shared.ts +932 -0
  224. package/src/local-first/Storage.ts +209 -282
  225. package/src/local-first/Timestamp.ts +38 -45
  226. package/src/local-first/index.ts +2 -16
  227. package/LICENSE +0 -21
  228. package/dist/src/Instances.d.ts +0 -34
  229. package/dist/src/Instances.d.ts.map +0 -1
  230. package/dist/src/Instances.js +0 -44
  231. package/dist/src/Resources.d.ts +0 -118
  232. package/dist/src/Resources.d.ts.map +0 -1
  233. package/dist/src/Resources.js +0 -197
  234. package/dist/src/local-first/Platform.d.ts +0 -25
  235. package/dist/src/local-first/Platform.d.ts.map +0 -1
  236. package/dist/src/local-first/Platform.js +0 -1
  237. package/dist/src/local-first/Public.d.ts +0 -22
  238. package/dist/src/local-first/Public.d.ts.map +0 -1
  239. package/dist/src/local-first/Public.js +0 -15
  240. package/dist/src/local-first/PublicKysely.d.ts +0 -148
  241. package/dist/src/local-first/PublicKysely.d.ts.map +0 -1
  242. package/dist/src/local-first/PublicKysely.js +0 -184
  243. package/dist/src/local-first/Sync.d.ts +0 -112
  244. package/dist/src/local-first/Sync.d.ts.map +0 -1
  245. package/dist/src/local-first/Sync.js +0 -529
  246. package/src/Instances.ts +0 -90
  247. package/src/Resources.ts +0 -367
  248. package/src/local-first/Platform.ts +0 -27
  249. package/src/local-first/Public.ts +0 -42
  250. package/src/local-first/PublicKysely.ts +0 -239
  251. package/src/local-first/Sync.ts +0 -960
package/dist/src/Time.js CHANGED
@@ -1,114 +1,218 @@
1
+ /**
2
+ * Time representations, durations, and scheduling utilities.
3
+ *
4
+ * @module
5
+ */
1
6
  import { assert } from "./Assert.js";
2
- import { DateIso, NonNegativeInt } from "./Type.js";
7
+ import { brand, lessThan, NonNegativeInt } from "./Type.js";
3
8
  /**
4
- * Creates a {@link Time} using Date.now().
9
+ * Creates a {@link Time} using `Date.now()` and `globalThis.setTimeout`.
5
10
  *
6
- * If the system clock is misconfigured (out of allowed range), the application
7
- * will fail with an assertion error. This is intentional - there's no
8
- * reasonable fallback when the system clock is fundamentally wrong.
11
+ * Throws if the system clock returns an out-of-range value. This is intentional
12
+ * there's no reasonable fallback for a misconfigured clock.
9
13
  */
10
14
  export const createTime = () => {
11
- const time = {
12
- now: () => {
13
- const iso = time.nowIso();
14
- return new globalThis.Date(iso).getTime();
15
- },
16
- nowIso: () => {
17
- const iso = new globalThis.Date().toISOString();
18
- assert(DateIso.is(iso), "System clock returned invalid ISO date");
19
- return iso;
15
+ const getNowMillis = () => Millis.orThrow(globalThis.Date.now());
16
+ return {
17
+ now: () => getNowMillis(),
18
+ nowDateIso: () => millisToDateIso(getNowMillis()),
19
+ setTimeout: (callback, delay) => globalThis.setTimeout(callback, durationToMillis(delay)),
20
+ clearTimeout: (id) => {
21
+ globalThis.clearTimeout(id);
20
22
  },
21
23
  };
22
- return time;
23
24
  };
24
25
  /**
25
- * Creates a {@link Time} that returns a monotonically increasing number based on
26
- * a queueMicrotask.
26
+ * Creates a {@link TestTime} with controllable timers for testing.
27
+ *
28
+ * Time starts at `startAt` (default 0) and only advances when `advance()` is
29
+ * called. Timeouts scheduled via `setTimeout` fire when time is advanced past
30
+ * their deadline.
31
+ *
32
+ * Set `autoIncrement` to automatically increment time by 1ms after each `now()`
33
+ * call via microtask (useful for tests that need monotonically increasing
34
+ * values without explicit `advance()` calls).
27
35
  */
28
- export const createTestTime = () => {
29
- let now = 0;
30
- const time = {
31
- now: () => {
32
- const current = now;
36
+ export const testCreateTime = (options) => {
37
+ let now = options?.startAt ?? minMillis;
38
+ const autoIncrement = options?.autoIncrement ?? false;
39
+ let nextId = 1;
40
+ const pending = new Map();
41
+ const getNowMillis = () => {
42
+ const result = now;
43
+ if (autoIncrement) {
33
44
  queueMicrotask(() => {
34
- now++;
45
+ now = Millis.orThrow(now + 1);
35
46
  });
36
- return current;
47
+ }
48
+ return result;
49
+ };
50
+ return {
51
+ now: () => getNowMillis(),
52
+ nowDateIso: () => millisToDateIso(getNowMillis()),
53
+ setTimeout: (callback, delay) => {
54
+ const id = nextId++;
55
+ pending.set(id, { callback, runAt: now + durationToMillis(delay) });
56
+ return id;
57
+ },
58
+ clearTimeout: (id) => {
59
+ pending.delete(id);
60
+ },
61
+ advance: (duration) => {
62
+ now = Millis.orThrow(now + durationToMillis(duration));
63
+ for (const [id, timeout] of pending) {
64
+ if (timeout.runAt <= now) {
65
+ pending.delete(id);
66
+ timeout.callback();
67
+ }
68
+ }
37
69
  },
38
- nowIso: () => DateIso.orThrow(new globalThis.Date(time.now()).toISOString()),
39
70
  };
40
- return time;
41
71
  };
72
+ // Literal (not expression like 1 + 2) to preserve type for Brand<"LessThan...">
73
+ const maxMillisWithInfinity = 281474976710655;
74
+ /**
75
+ * Milliseconds timestamp, like `Date.now()`.
76
+ *
77
+ * The maximum value is 281474976710654 (281474976710655 - 1, reserved for
78
+ * infinity). This enables efficient binary serialization, saving 2 bytes
79
+ * compared to typical 8-byte (64-bit) timestamps.
80
+ *
81
+ * `new Date(281474976710654).toString()` = Tue Aug 02 10889 07:31:49
82
+ *
83
+ * If a system clock exceeds this range, operations will throw. This is
84
+ * intentional — there's no reasonable fallback for a misconfigured clock.
85
+ */
86
+ export const Millis = /*#__PURE__*/ brand("Millis",
87
+ /*#__PURE__*/ lessThan(maxMillisWithInfinity)(NonNegativeInt));
88
+ /** Minimum {@link Millis} value. */
89
+ export const minMillis = 0;
90
+ /** Maximum {@link Millis} value. */
91
+ export const maxMillis = (maxMillisWithInfinity - 1);
92
+ /**
93
+ * Converts {@link Millis} to {@link DateIso}.
94
+ *
95
+ * This is a safe cast because {@link Millis} guarantees a valid timestamp range
96
+ * that always produces a valid ISO string.
97
+ */
98
+ export const millisToDateIso = (value) => new globalThis.Date(value).toISOString();
42
99
  /**
43
100
  * Converts a duration to milliseconds.
44
101
  *
45
- * Accepts either a {@link DurationString} (e.g., "5m", "1h 30m") or milliseconds
46
- * as {@link NonNegativeInt}.
102
+ * Accepts either a {@link DurationLiteral} (e.g., "5m", "1.5s") or
103
+ * {@link Millis}.
47
104
  *
48
105
  * ### Example
49
106
  *
50
107
  * ```ts
51
- * durationToNonNegativeInt("0ms"); // 0 ✅
52
- * durationToNonNegativeInt("500ms"); // 500
53
- * durationToNonNegativeInt("30s"); // 30000 ✅
54
- * durationToNonNegativeInt("5m"); // 300000 ✅
55
- * durationToNonNegativeInt("12h"); // 43200000 ✅
56
- * durationToNonNegativeInt("7d"); // 604800000 ✅
57
- * durationToNonNegativeInt("2h 45m"); // 9900000 ✅
58
- * durationToNonNegativeInt(5000); // 5000 (already milliseconds)
108
+ * durationToMillis("1ms"); // 1
109
+ * durationToMillis("500ms"); // 500
110
+ * durationToMillis("1.5s"); // 1500
111
+ * durationToMillis("30s"); // 30000
112
+ * durationToMillis("5m"); // 300000
113
+ * durationToMillis("12h"); // 43200000
114
+ * durationToMillis("7d"); // 604800000
115
+ * durationToMillis(Millis.orThrow(5000)); // 5000 (already Millis)
59
116
  * ```
60
117
  */
61
- export const durationToNonNegativeInt = (duration) => {
62
- // If it's already a NonNegativeInt (milliseconds), return as-is
63
- if (typeof duration === "number") {
118
+ export const durationToMillis = (duration) => {
119
+ if (typeof duration === "number")
64
120
  return duration;
121
+ const num = parseFloat(duration);
122
+ const unit = duration.endsWith("ms") ? "ms" : duration.at(-1);
123
+ assert(unit in durationUnits, `Unknown duration unit: ${unit}`);
124
+ return Millis.orThrow(Math.round(num * durationUnits[unit]));
125
+ };
126
+ /**
127
+ * Returns a Promise that resolves after the specified duration.
128
+ *
129
+ * Uses {@link Duration} parsing and `globalThis.setTimeout`.
130
+ *
131
+ * ### Example
132
+ *
133
+ * ```ts
134
+ * await setTimeout("1ms");
135
+ * ```
136
+ */
137
+ export const setTimeout = (duration) => new Promise((resolve) => {
138
+ globalThis.setTimeout(resolve, durationToMillis(duration));
139
+ });
140
+ const durationUnits = {
141
+ ms: 1,
142
+ s: 1000,
143
+ m: 60000,
144
+ h: 3600000,
145
+ d: 86400000,
146
+ w: 604800000, // 7 days
147
+ y: 31536000000, // 365 days
148
+ };
149
+ /**
150
+ * Frame budget at 60fps (16ms).
151
+ *
152
+ * Work exceeding this blocks a frame, causing visible jank in animations.
153
+ */
154
+ export const ms60fps = 16;
155
+ /**
156
+ * Frame budget at 120fps (8ms).
157
+ *
158
+ * For high refresh rate displays. Work exceeding this blocks a frame.
159
+ */
160
+ export const ms120fps = 8;
161
+ /**
162
+ * Long task threshold (50ms).
163
+ *
164
+ * Tasks exceeding this are "long tasks" per web standards. Use with
165
+ * {@link yieldNow} to yield periodically and keep UI responsive.
166
+ *
167
+ * @see https://web.dev/articles/optimize-long-tasks
168
+ */
169
+ export const msLongTask = 50;
170
+ /**
171
+ * Formats {@link Millis} as a human-readable duration string.
172
+ *
173
+ * - Under 1 minute: `1.234s`
174
+ * - Under 1 hour: `1m30.000s`
175
+ * - 1 hour or more: `1h30m45.000s`
176
+ *
177
+ * ### Example
178
+ *
179
+ * ```ts
180
+ * formatMillisAsDuration(1234 as Millis); // "1.234s"
181
+ * formatMillisAsDuration(90000 as Millis); // "1m30.000s"
182
+ * formatMillisAsDuration(3661000 as Millis); // "1h1m1.000s"
183
+ * ```
184
+ */
185
+ export const formatMillisAsDuration = (millis) => {
186
+ const elapsed = millis / 1000;
187
+ if (elapsed < 60) {
188
+ return `${elapsed.toFixed(3)}s`;
65
189
  }
66
- // Parse duration string without regex to avoid ReDoS vulnerabilities
67
- const units = {
68
- ms: 1,
69
- s: 1000,
70
- m: 60000,
71
- h: 3600000,
72
- d: 86400000, // 24 * 60 * 60 * 1000
73
- };
74
- let total = 0;
75
- let i = 0;
76
- while (i < duration.length) {
77
- // Skip whitespace
78
- while (i < duration.length && duration[i] === " ") {
79
- i++;
80
- }
81
- if (i >= duration.length)
82
- break;
83
- // Parse number
84
- let numStr = "";
85
- while (i < duration.length && duration[i] >= "0" && duration[i] <= "9") {
86
- numStr += duration[i];
87
- i++;
88
- }
89
- if (numStr === "")
90
- break;
91
- // Parse unit (ms or single char s/m/h/d)
92
- let unit = "";
93
- if (i < duration.length) {
94
- if (duration[i] === "m" &&
95
- i + 1 < duration.length &&
96
- duration[i + 1] === "s") {
97
- unit = "ms";
98
- i += 2;
99
- }
100
- else if (duration[i] === "s" ||
101
- duration[i] === "m" ||
102
- duration[i] === "h" ||
103
- duration[i] === "d") {
104
- unit = duration[i];
105
- i++;
106
- }
107
- }
108
- if (unit === "")
109
- break;
110
- const value = parseInt(numStr, 10);
111
- total += value * units[unit];
190
+ else if (elapsed < 3600) {
191
+ const minutes = Math.floor(elapsed / 60);
192
+ const seconds = (elapsed % 60).toFixed(3);
193
+ return `${minutes}m${seconds}s`;
112
194
  }
113
- return NonNegativeInt.orThrow(total);
195
+ else {
196
+ const hours = Math.floor(elapsed / 3600);
197
+ const minutes = Math.floor((elapsed % 3600) / 60);
198
+ const seconds = ((elapsed % 3600) % 60).toFixed(3);
199
+ return `${hours}h${minutes}m${seconds}s`;
200
+ }
201
+ };
202
+ /**
203
+ * Formats {@link Millis} as `HH:MM:SS.mmm`.
204
+ *
205
+ * ### Example
206
+ *
207
+ * ```ts
208
+ * formatMillisAsClockTime(Millis.orThrow(Date.now())); // "14:32:15.234"
209
+ * ```
210
+ */
211
+ export const formatMillisAsClockTime = (millis) => {
212
+ const date = new globalThis.Date(millis);
213
+ const hours = date.getHours().toString().padStart(2, "0");
214
+ const minutes = date.getMinutes().toString().padStart(2, "0");
215
+ const seconds = date.getSeconds().toString().padStart(2, "0");
216
+ const ms = date.getMilliseconds().toString().padStart(3, "0");
217
+ return `${hours}:${minutes}:${seconds}.${ms}`;
114
218
  };
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Minimal tracer interface for observability.
3
+ *
4
+ * @module
5
+ */
6
+ import type { Ref } from "./Ref.js";
7
+ /**
8
+ * Minimal tracer interface for observability.
9
+ *
10
+ * Tracer provides unified observability for both sync and async code. Use it
11
+ * directly for sync operations, or let {@link Run} use it automatically for
12
+ * async tasks.
13
+ *
14
+ * ### Example
15
+ *
16
+ * ```ts
17
+ * // Sync code — use tracer directly
18
+ * const result = deps.tracer?.span("parseData", () => parseData(input));
19
+ *
20
+ * // Async code — Run traces automatically
21
+ * await using run = createRun(deps);
22
+ * const result = await run(fetchUser(id)); // Traced if tracer provided
23
+ * ```
24
+ *
25
+ * TODO: Complete implementation with OpenTelemetry adapter.
26
+ */
27
+ export interface Tracer {
28
+ /**
29
+ * Wraps a sync function in a traced span.
30
+ *
31
+ * Records start time, end time, and success/failure status.
32
+ */
33
+ readonly span: <T>(name: string, fn: () => T) => T;
34
+ /** Records an event/fact (e.g., "user.purchased", "sync.completed"). */
35
+ readonly event: (name: string, data?: unknown) => void;
36
+ }
37
+ export interface TracerDep {
38
+ readonly tracer: Tracer;
39
+ }
40
+ /** Configuration for tracing. */
41
+ export interface TracerConfig {
42
+ /** When `true`, enables trace data collection. Can be toggled at runtime. */
43
+ readonly tracing?: Ref<boolean>;
44
+ }
45
+ export interface TracerConfigDep {
46
+ readonly tracerConfig: TracerConfig;
47
+ }
48
+ //# sourceMappingURL=Tracer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Tracer.d.ts","sourceRoot":"","sources":["../../src/Tracer.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AAGpC;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,WAAW,MAAM;IACrB;;;;OAIG;IACH,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC;IAEnD,wEAAwE;IACxE,QAAQ,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,KAAK,IAAI,CAAC;CACxD;AAED,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED,iCAAiC;AACjC,MAAM,WAAW,YAAY;IAC3B,6EAA6E;IAC7E,QAAQ,CAAC,OAAO,CAAC,EAAE,GAAG,CAAC,OAAO,CAAC,CAAC;CACjC;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,YAAY,EAAE,YAAY,CAAC;CACrC"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Minimal tracer interface for observability.
3
+ *
4
+ * @module
5
+ */
6
+ export {};