@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
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Development-time detection of leaked handles via garbage collection.
3
+ *
4
+ * @module
5
+ */
6
+ import {} from "./Console.js";
7
+ import { constVoid } from "./Function.js";
8
+ /**
9
+ * Creates {@link LeakDetector} backed by `FinalizationRegistry`.
10
+ *
11
+ * Capturing a stack per track call is too expensive for production; use
12
+ * {@link noopLeakDetector} there.
13
+ */
14
+ export const createLeakDetector = (deps) => {
15
+ if (typeof globalThis.FinalizationRegistry !== "function")
16
+ return noopLeakDetector;
17
+ const registry = new globalThis.FinalizationRegistry(reportLeak(deps));
18
+ return {
19
+ track: (target, leak, unregisterToken) => {
20
+ registry.register(target, { ...leak, stack: new Error().stack }, unregisterToken);
21
+ },
22
+ untrack: (unregisterToken) => {
23
+ registry.unregister(unregisterToken);
24
+ },
25
+ };
26
+ };
27
+ /** No-op {@link LeakDetector} for production. */
28
+ export const noopLeakDetector = {
29
+ track: constVoid,
30
+ untrack: constVoid,
31
+ };
32
+ /**
33
+ * Reports a collected handle that was still held.
34
+ *
35
+ * Shared by {@link createLeakDetector} as the `FinalizationRegistry` callback
36
+ * and by {@link testCreateLeakDetector.collect | collect}, so tests cover the
37
+ * same reporting path without forcing garbage collection. Returns whether the
38
+ * leak was reported.
39
+ */
40
+ const reportLeak = (deps) => (leak) => {
41
+ if (!leak.isLeaked())
42
+ return false;
43
+ deps.console.warn(`${leak.name} was garbage-collected without cleanup. Tracked at:`, leak.stack);
44
+ return true;
45
+ };
46
+ /** Creates {@link TestLeakDetector}. */
47
+ export const testCreateLeakDetector = (deps) => {
48
+ const trackedLeaksByToken = new Map();
49
+ const report = reportLeak(deps);
50
+ return {
51
+ track: (_target, leak, unregisterToken) => {
52
+ const trackedLeaks = trackedLeaksByToken.get(unregisterToken) ?? [];
53
+ trackedLeaksByToken.set(unregisterToken, [
54
+ ...trackedLeaks,
55
+ {
56
+ ...leak,
57
+ stack: new Error().stack,
58
+ },
59
+ ]);
60
+ },
61
+ untrack: (unregisterToken) => {
62
+ trackedLeaksByToken.delete(unregisterToken);
63
+ },
64
+ collect: () => {
65
+ let leakCount = 0;
66
+ for (const trackedLeaks of trackedLeaksByToken.values()) {
67
+ for (const leak of trackedLeaks) {
68
+ if (report(leak))
69
+ leakCount++;
70
+ }
71
+ }
72
+ trackedLeaksByToken.clear();
73
+ return leakCount;
74
+ },
75
+ getTrackedCount: ({ name } = {}) => {
76
+ let count = 0;
77
+ for (const trackedLeaks of trackedLeaksByToken.values()) {
78
+ for (const leak of trackedLeaks) {
79
+ if (name === undefined || leak.name === name)
80
+ count++;
81
+ }
82
+ }
83
+ return count;
84
+ },
85
+ };
86
+ };
@@ -3,32 +3,41 @@
3
3
  *
4
4
  * @module
5
5
  */
6
- import { AbortError, type Task } from "./Task.js";
7
- import type { Callback } from "./Types.js";
6
+ import type { Task } from "./Task.ts";
7
+ import type { Callback } from "./Types.ts";
8
8
  /**
9
9
  * Dependency wrapper for {@link LockManager}.
10
10
  *
11
11
  * For React Native, use the `lockManager` ponyfill from `@evolu/react-native`.
12
12
  * For tests, use {@link testCreateLockManager}.
13
13
  *
14
- * ### Example
14
+ * ### Web and Node.js 24+
15
15
  *
16
16
  * ```ts
17
- * // Web or Node.js 24+
18
- * import { testCreateLockManager } from "@evolu/common";
17
+ * import type { LockManagerDep } from "@evolu/common";
19
18
  *
20
19
  * const deps: LockManagerDep = {
21
- * lockManager: testCreateLockManager(),
20
+ * lockManager: globalThis.navigator.locks,
22
21
  * };
22
+ *
23
+ * const result = await deps.lockManager.request("example", (lock) => {
24
+ * return lock?.name;
25
+ * });
26
+ * expect(result).toBe("example");
23
27
  * ```
24
28
  *
25
- * ### Example
29
+ * ### React Native
26
30
  *
27
31
  * ```ts
28
- * // React Native
32
+ * import type { LockManagerDep } from "@evolu/common";
29
33
  * import { lockManager } from "@evolu/react-native";
30
34
  *
31
35
  * const deps: LockManagerDep = { lockManager };
36
+ *
37
+ * const result = await deps.lockManager.request("example", (lock) => {
38
+ * return lock?.name;
39
+ * });
40
+ * expect(result).toBe("example");
32
41
  * ```
33
42
  */
34
43
  export interface LockManagerDep {
@@ -58,7 +67,7 @@ export declare const testCreateLockManager: (nativeLockManager?: LockManager) =>
58
67
  * another waiting caller may become the next leader. Waiting for leadership is
59
68
  * abortable via the calling {@link Task}'s signal.
60
69
  */
61
- export declare const acquireLeaderLock: (name: string) => Task<AsyncDisposable, AbortError, LockManagerDep>;
70
+ export declare const acquireLeaderLock: (name: string) => Task<AsyncDisposable, never, LockManagerDep>;
62
71
  /**
63
72
  * Competes to become the current leader for `name` using {@link LockManager}.
64
73
  *
@@ -1 +1 @@
1
- {"version":3,"file":"LockManager.d.ts","sourceRoot":"","sources":["../../src/LockManager.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAKH,OAAO,EAAE,UAAU,EAAE,KAAK,IAAI,EAAE,MAAM,WAAW,CAAC;AAElD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAE3C;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAC;CACnC;AAED;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,qBAAqB,GAChC,oBAAmB,WAAwC,KAC1D,WAwCF,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,iBAAiB,GAC3B,MAAM,MAAM,KAAG,IAAI,CAAC,eAAe,EAAE,UAAU,EAAE,cAAc,CA4B7D,CAAC;AAEN;;;;;;;;;;GAUG;AACH,eAAO,MAAM,yBAAyB,GACnC,MAAM,cAAc,MACpB,MAAM,MAAM,EAAE,UAAU,QAAQ,CAAC,IAAI,CAAC,KAAG,UAsBzC,CAAC"}
1
+ {"version":3,"file":"LockManager.d.ts","sourceRoot":"","sources":["../../src/LockManager.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAKH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEtC,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAE3C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAEH,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAC;CACnC;AAED;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,qBAAqB,uBACb,WAAW,KAC7B,WAwCF,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,iBAAiB,SACrB,MAAM,KAAG,IAAI,CAAC,eAAe,EAAE,KAAK,EAAE,cAAc,CAuB1D,CAAC;AAEJ;;;;;;;;;;GAUG;AACH,eAAO,MAAM,yBAAyB,SAC7B,cAAc,YACd,MAAM,YAAY,QAAQ,CAAC,IAAI,CAAC,KAAG,UAsBzC,CAAC"}
@@ -4,9 +4,8 @@
4
4
  * @module
5
5
  */
6
6
  import { createRandomBytes } from "./Crypto.js";
7
- import { lazyVoid } from "./Function.js";
8
- import { tryAsync } from "./Result.js";
9
- import { AbortError } from "./Task.js";
7
+ import { constVoid } from "./Function.js";
8
+ import { ok } from "./Result.js";
10
9
  import { createId } from "./Type.js";
11
10
  /**
12
11
  * Creates a test {@link LockManager} backed by the native platform
@@ -56,7 +55,7 @@ export const testCreateLockManager = (nativeLockManager = globalThis.navigator.l
56
55
  * another waiting caller may become the next leader. Waiting for leadership is
57
56
  * abortable via the calling {@link Task}'s signal.
58
57
  */
59
- export const acquireLeaderLock = (name) => (run) => tryAsync(async () => {
58
+ export const acquireLeaderLock = (name) => async (run) => {
60
59
  const acquisition = Promise.withResolvers();
61
60
  const released = Promise.withResolvers();
62
61
  const request = run.deps.lockManager.request(createLeaderLockName(name), { mode: "exclusive", signal: run.signal }, async () => {
@@ -65,13 +64,13 @@ export const acquireLeaderLock = (name) => (run) => tryAsync(async () => {
65
64
  });
66
65
  void request.catch(acquisition.reject);
67
66
  await acquisition.promise;
68
- return {
67
+ return ok({
69
68
  [Symbol.asyncDispose]: async () => {
70
69
  released.resolve();
71
70
  await request;
72
71
  },
73
- };
74
- }, (error) => AbortError.is(error) ? error : { type: "AbortError", reason: error });
72
+ });
73
+ };
75
74
  /**
76
75
  * Competes to become the current leader for `name` using {@link LockManager}.
77
76
  *
@@ -92,7 +91,7 @@ export const acquireLeaderLockCallback = (deps) => (name, callback) => {
92
91
  callback();
93
92
  await released.promise;
94
93
  });
95
- void request.catch(lazyVoid);
94
+ void request.catch(constVoid);
96
95
  return {
97
96
  [Symbol.dispose]: () => {
98
97
  abortController.abort();
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * @module
5
5
  */
6
- import type { Brand } from "./Brand.js";
6
+ import type { Brand } from "./Brand.ts";
7
7
  /**
8
8
  * Derives the native lookup key used by lookup-based collections.
9
9
  *
@@ -18,6 +18,16 @@ import type { Brand } from "./Brand.js";
18
18
  * Use this when callers provide keys in one form but logical identity should be
19
19
  * based on another stable key, such as a natural id or {@link StructuralKey}.
20
20
  *
21
+ * Lookup functions must be pure and stable. The same logical key must derive
22
+ * the same native `Map` key while an entry is stored, so use lookup-based
23
+ * collections with immutable key data, preferably readonly values. Mutating key
24
+ * data after insertion can make later `get`, `has`, and `delete` calls disagree
25
+ * with the inserted entry.
26
+ *
27
+ * The returned lookup key uses native `Map` equality. Return primitives or
28
+ * stable object references; returning a new object for each call prevents equal
29
+ * logical keys from matching.
30
+ *
21
31
  * @see {@link createLookupMap}
22
32
  * @see {@link createLookupSet}
23
33
  */
@@ -113,17 +123,19 @@ export type StructuralKey = StructuralScalar | ReadonlyArray<StructuralKey> | {
113
123
  *
114
124
  * `Structural<T>` checks a concrete type recursively at compile time instead:
115
125
  * scalars pass through, arrays recurse, object properties recurse, and
116
- * function-valued properties are rejected. This keeps public structural APIs
117
- * ergonomic for interface-based inputs while preserving the same runtime
126
+ * function-valued and symbol-keyed properties are rejected. Symbol-keyed
127
+ * properties are rejected because runtime serialization reads only enumerable
128
+ * string keys, so they would be silently ignored. This keeps public structural
129
+ * APIs ergonomic for interface-based inputs while preserving the same runtime
118
130
  * constraints as structural lookup serialization.
119
131
  *
120
132
  * @see {@link StructuralKey}
121
133
  * @see {@link structuralLookup}
122
134
  */
123
135
  export type Structural<T> = T extends StructuralScalar ? T : T extends ReadonlyArray<infer Item> ? ReadonlyArray<Structural<Item>> : T extends StructuralFunction ? never : T extends object ? {
124
- readonly [K in keyof T as Extract<T[K], StructuralFunction> extends never ? K : never]: Structural<Exclude<T[K], StructuralFunction>>;
136
+ readonly [K in keyof T as K extends symbol ? never : Extract<T[K], StructuralFunction> extends never ? K : never]: Structural<Exclude<T[K], StructuralFunction>>;
125
137
  } & {
126
- readonly [K in keyof T as Extract<T[K], StructuralFunction> extends never ? never : K]?: never;
138
+ readonly [K in keyof T as K extends symbol ? K : Extract<T[K], StructuralFunction> extends never ? never : K]?: never;
127
139
  } : never;
128
140
  /** Function branch excluded by {@link Structural}. */
129
141
  export type StructuralFunction = (...args: ReadonlyArray<unknown>) => unknown;
@@ -132,7 +144,8 @@ export type StructuralFunction = (...args: ReadonlyArray<unknown>) => unknown;
132
144
  *
133
145
  * Structural lookup keys are derived from JSON-like values plus `Uint8Array`.
134
146
  * Equal structures produce the same lookup key even when they are different
135
- * JavaScript instances.
147
+ * JavaScript instances. `undefined` values and sparse array holes are rejected
148
+ * rather than skipped.
136
149
  *
137
150
  * The derived key is memoized by non-null object identity in a module-scoped
138
151
  * `WeakMap` shared by all callers, so keys must be immutable.
@@ -143,16 +156,33 @@ export type StructuralFunction = (...args: ReadonlyArray<unknown>) => unknown;
143
156
  * ### Example
144
157
  *
145
158
  * ```ts
146
- * const byFilter = createLookupMap<
147
- * { readonly table: string; readonly where: readonly [string, string] },
148
- * string,
149
- * StructuralLookupKey
150
- * >({
159
+ * import {
160
+ * createLookupMap,
161
+ * structuralLookup,
162
+ * type StructuralLookupKey,
163
+ * } from "@evolu/common";
164
+ *
165
+ * interface Filter {
166
+ * readonly table: string;
167
+ * readonly where: readonly [string, string];
168
+ * }
169
+ *
170
+ * const byFilter = createLookupMap<Filter, string, StructuralLookupKey>({
151
171
  * lookup: structuralLookup,
152
172
  * });
153
173
  *
154
- * byFilter.set({ table: "todo", where: ["owner", "ada"] }, "cached");
155
- * byFilter.get({ table: "todo", where: ["owner", "ada"] }); // "cached"
174
+ * const cachedFilter: Filter = {
175
+ * table: "todo",
176
+ * where: ["owner", "ada"],
177
+ * };
178
+ * const equivalentFilter: Filter = {
179
+ * where: ["owner", "ada"],
180
+ * table: "todo",
181
+ * };
182
+ * byFilter.set(cachedFilter, "cached");
183
+ *
184
+ * expect(byFilter.get(equivalentFilter)).toBe("cached");
185
+ * expect(byFilter.getKey(equivalentFilter)).toBe(cachedFilter);
156
186
  * ```
157
187
  *
158
188
  * @see {@link StructuralKey}
@@ -1 +1 @@
1
- {"version":3,"file":"Lookup.d.ts","sourceRoot":"","sources":["../../src/Lookup.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"Lookup.d.ts","sourceRoot":"","sources":["../../src/Lookup.ts"],"names":[],"mappings":"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"}
@@ -113,7 +113,8 @@ export const createLookupSet = ({ lookup, values, }) => {
113
113
  *
114
114
  * Structural lookup keys are derived from JSON-like values plus `Uint8Array`.
115
115
  * Equal structures produce the same lookup key even when they are different
116
- * JavaScript instances.
116
+ * JavaScript instances. `undefined` values and sparse array holes are rejected
117
+ * rather than skipped.
117
118
  *
118
119
  * The derived key is memoized by non-null object identity in a module-scoped
119
120
  * `WeakMap` shared by all callers, so keys must be immutable.
@@ -124,16 +125,33 @@ export const createLookupSet = ({ lookup, values, }) => {
124
125
  * ### Example
125
126
  *
126
127
  * ```ts
127
- * const byFilter = createLookupMap<
128
- * { readonly table: string; readonly where: readonly [string, string] },
129
- * string,
130
- * StructuralLookupKey
131
- * >({
128
+ * import {
129
+ * createLookupMap,
130
+ * structuralLookup,
131
+ * type StructuralLookupKey,
132
+ * } from "@evolu/common";
133
+ *
134
+ * interface Filter {
135
+ * readonly table: string;
136
+ * readonly where: readonly [string, string];
137
+ * }
138
+ *
139
+ * const byFilter = createLookupMap<Filter, string, StructuralLookupKey>({
132
140
  * lookup: structuralLookup,
133
141
  * });
134
142
  *
135
- * byFilter.set({ table: "todo", where: ["owner", "ada"] }, "cached");
136
- * byFilter.get({ table: "todo", where: ["owner", "ada"] }); // "cached"
143
+ * const cachedFilter: Filter = {
144
+ * table: "todo",
145
+ * where: ["owner", "ada"],
146
+ * };
147
+ * const equivalentFilter: Filter = {
148
+ * where: ["owner", "ada"],
149
+ * table: "todo",
150
+ * };
151
+ * byFilter.set(cachedFilter, "cached");
152
+ *
153
+ * expect(byFilter.get(equivalentFilter)).toBe("cached");
154
+ * expect(byFilter.getKey(equivalentFilter)).toBe(cachedFilter);
137
155
  * ```
138
156
  *
139
157
  * @see {@link StructuralKey}
@@ -180,6 +198,8 @@ const structuralLookupInternal = (value, path = new Set()) => {
180
198
  else {
181
199
  assert(false, "Structural lookup keys must be JSON-like values or Uint8Array.");
182
200
  }
201
+ // Cache only after traversal succeeds so cyclic values never leave a
202
+ // partial lookup key in the memo.
183
203
  structuralLookupKeyByValue.set(value, lookupKey);
184
204
  return lookupKey;
185
205
  }
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * @module
5
5
  */
6
- import { type NonEmptyReadonlyArray } from "./Array.js";
6
+ import { type NonEmptyReadonlyArray } from "./Array.ts";
7
7
  /**
8
8
  * Batches values and flushes them in a single microtask.
9
9
  *
@@ -1 +1 @@
1
- {"version":3,"file":"Microtask.d.ts","sourceRoot":"","sources":["../../src/Microtask.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAiB,KAAK,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAGvE;;;;;GAKG;AACH,MAAM,WAAW,cAAc,CAAC,CAAC,CAAE,SAAQ,UAAU;IACnD,mEAAmE;IACnE,QAAQ,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,IAAI,CAAC;IAEjC,wCAAwC;IACxC,QAAQ,CAAC,QAAQ,EAAE,MAAM,IAAI,CAAC;CAC/B;AAED,sCAAsC;AACtC,eAAO,MAAM,oBAAoB,GAAI,CAAC,EACpC,SAAS,CAAC,KAAK,EAAE,qBAAqB,CAAC,CAAC,CAAC,KAAK,IAAI,KACjD,cAAc,CAAC,CAAC,CAqClB,CAAC"}
1
+ {"version":3,"file":"Microtask.d.ts","sourceRoot":"","sources":["../../src/Microtask.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAiB,KAAK,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAGvE;;;;;GAKG;AACH,MAAM,WAAW,cAAc,CAAC,CAAC,CAAE,SAAQ,UAAU;IACnD,mEAAmE;IACnE,QAAQ,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,IAAI,CAAC;IAEjC,wCAAwC;IACxC,QAAQ,CAAC,QAAQ,EAAE,MAAM,IAAI,CAAC;CAC/B;AAED,sCAAsC;AACtC,eAAO,MAAM,oBAAoB,GAAI,CAAC,WAC3B,CAAC,KAAK,EAAE,qBAAqB,CAAC,CAAC,CAAC,KAAK,IAAI,KACjD,cAAc,CAAC,CAAC,CAiClB,CAAC"}
@@ -56,7 +56,7 @@ var __disposeResources = (this && this.__disposeResources) || (function (Suppres
56
56
  return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
57
57
  });
58
58
  import { appendToArray } from "./Array.js";
59
- import { assertNotDisposed } from "./Assert.js";
59
+ import { disposable } from "./Function.js";
60
60
  /** Creates {@link MicrotaskBatch}. */
61
61
  export const createMicrotaskBatch = (onFlush) => {
62
62
  const env_1 = { stack: [], error: void 0, hasError: false };
@@ -66,7 +66,6 @@ export const createMicrotaskBatch = (onFlush) => {
66
66
  disposer.defer(() => {
67
67
  queue = null;
68
68
  });
69
- const disposables = disposer.move();
70
69
  const flushQueuedItems = () => {
71
70
  if (queue == null)
72
71
  return;
@@ -74,9 +73,8 @@ export const createMicrotaskBatch = (onFlush) => {
74
73
  queue = null;
75
74
  onFlush(queuedItems);
76
75
  };
77
- return {
76
+ return disposable({
78
77
  push: (item) => {
79
- assertNotDisposed(disposables);
80
78
  if (queue == null) {
81
79
  queue = [item];
82
80
  queueMicrotask(flushQueuedItems);
@@ -85,11 +83,9 @@ export const createMicrotaskBatch = (onFlush) => {
85
83
  queue = appendToArray(queue, item);
86
84
  },
87
85
  flushNow: () => {
88
- assertNotDisposed(disposables);
89
86
  flushQueuedItems();
90
87
  },
91
- [Symbol.dispose]: () => disposables.dispose(),
92
- };
88
+ }, disposer);
93
89
  }
94
90
  catch (e_1) {
95
91
  env_1.error = e_1;
@@ -3,12 +3,25 @@
3
3
  *
4
4
  * @module
5
5
  */
6
- import type { NonEmptyReadonlyArray } from "./Array.js";
7
- import type { IsBranded } from "./Brand.js";
8
- import type { Result } from "./Result.js";
9
- import { NonNegativeInt, PositiveInt } from "./Type.js";
10
- import type { Predicate, WidenLiteral } from "./Types.js";
6
+ import type { NonEmptyReadonlyArray } from "./Array.ts";
7
+ import type { IsBranded } from "./Brand.ts";
8
+ import type { Result } from "./Result.ts";
9
+ import { NonNegativeInt, PositiveInt, Ratio } from "./Type.ts";
10
+ import type { Digit, Digit1To9, Digit1To99, Predicate, WidenLiteral } from "./Types.ts";
11
+ /** Percentage represented as a readable literal or a validated ratio. */
12
+ export type Percentage = PercentageLiteral | Ratio;
13
+ /**
14
+ * Percentage literal from `"0%"` to `"100%"`.
15
+ *
16
+ * Decimal literals support one decimal place. Use {@link Ratio} for computed
17
+ * values or greater precision.
18
+ */
19
+ export type PercentageLiteral = "0%" | "100%" | `${Digit1To99}%` | `${Digit | Digit1To99}.${Digit1To9}%`;
20
+ /** Converts a {@link Percentage} to its numeric {@link Ratio}. */
21
+ export declare const percentageToRatio: (percentage: Percentage) => Ratio;
11
22
  export declare const increment: (n: number) => number;
23
+ /** Increments a positive integer, saturating at the maximum safe integer. */
24
+ export declare const incrementPositiveInt: (n: PositiveInt) => PositiveInt;
12
25
  export declare const decrement: (n: number) => number;
13
26
  /** Clamps a number within a given range. */
14
27
  export declare const clamp: (min: number, max: number) => (n: number) => number;
@@ -18,9 +31,12 @@ export declare const clamp: (min: number, max: number) => (n: number) => number;
18
31
  * ### Example
19
32
  *
20
33
  * ```ts
34
+ * import { isBetween } from "@evolu/common";
35
+ *
21
36
  * const isBetween10And20 = isBetween(10, 20);
22
- * console.log(isBetween10And20(15)); // true
23
- * console.log(isBetween10And20(25)); // false
37
+ *
38
+ * expect(isBetween10And20(20)).toBe(true);
39
+ * expect(isBetween10And20(25)).toBe(false);
24
40
  * ```
25
41
  */
26
42
  export declare const isBetween: (min: number, max: number) => Predicate<number>;
@@ -36,22 +52,42 @@ export declare const max: <T extends number>(...values: [T, ...ReadonlyArray<T>]
36
52
  * ### Example
37
53
  *
38
54
  * ```ts
39
- * computeBalancedBuckets(10, 3, 2); // Returns ok([4, 7, 10])
40
- * computeBalancedBuckets(5, 3, 2); // Returns err(6)
55
+ * import {
56
+ * computeBalancedBuckets,
57
+ * NonNegativeInt,
58
+ * PositiveInt,
59
+ * } from "@evolu/common";
60
+ *
61
+ * expectOk(
62
+ * computeBalancedBuckets(
63
+ * NonNegativeInt.orThrow(10),
64
+ * PositiveInt.orThrow(3),
65
+ * PositiveInt.orThrow(2),
66
+ * ),
67
+ * [4, 7, 10],
68
+ * );
69
+ * expectErr(
70
+ * computeBalancedBuckets(
71
+ * NonNegativeInt.orThrow(5),
72
+ * PositiveInt.orThrow(3),
73
+ * PositiveInt.orThrow(2),
74
+ * ),
75
+ * 6,
76
+ * );
41
77
  * ```
42
78
  */
43
79
  export declare const computeBalancedBuckets: (numberOfItems: NonNegativeInt,
44
80
  /** Default: 16 */
45
- numberOfBuckets?: number & import("./Brand.js").Brand<"Int"> & import("./Brand.js").Brand<"NonNegative"> & import("./Brand.js").Brand<"Positive">,
81
+ numberOfBuckets?: number & import("./Brand.ts").Brand<"NonNaN"> & import("./Brand.ts").Brand<"Finite"> & import("./Brand.ts").Brand<"Int"> & import("./Brand.ts").Brand<"NonNegative"> & import("./Brand.ts").Brand<"Positive">,
46
82
  /** Default: 2 */
47
- minNumberOfItemsPerBucket?: number & import("./Brand.js").Brand<"Int"> & import("./Brand.js").Brand<"NonNegative"> & import("./Brand.js").Brand<"Positive">) => Result<NonEmptyReadonlyArray<PositiveInt>, PositiveInt>;
83
+ minNumberOfItemsPerBucket?: number & import("./Brand.ts").Brand<"NonNaN"> & import("./Brand.ts").Brand<"Finite"> & import("./Brand.ts").Brand<"Int"> & import("./Brand.ts").Brand<"NonNegative"> & import("./Brand.ts").Brand<"Positive">) => Result<NonEmptyReadonlyArray<PositiveInt>, PositiveInt>;
48
84
  /**
49
85
  * Valid index for {@link fibonacciAt}, constrained to 1-78.
50
86
  *
51
87
  * Limited to 78 because F(79) exceeds JavaScript's `MAX_SAFE_INTEGER`.
52
88
  */
53
- export declare const FibonacciIndex: import("./Type.js").BrandType<import("./Type.js").BrandType<import("./Type.js").Type<"Brand", number & import("./Brand.js").Brand<"Int"> & import("./Brand.js").Brand<"NonNegative"> & import("./Brand.js").Brand<"Positive">, number, import("./Type.js").PositiveError, number & import("./Brand.js").Brand<"Int"> & import("./Brand.js").Brand<"NonNegative">, import("./Type.js").NumberError | import("./Type.js").NonNegativeError | import("./Type.js").IntError>, "LessThanOrEqualTo78", import("./Type.js").LessThanOrEqualToError<78>, import("./Type.js").NumberError | import("./Type.js").NonNegativeError | import("./Type.js").IntError | import("./Type.js").PositiveError>, "FibonacciIndex", import("./Type.js").BrandWithoutRefineError<"FibonacciIndex", import("./Type.js").NumberError | import("./Type.js").NonNegativeError | import("./Type.js").IntError | import("./Type.js").PositiveError | import("./Type.js").LessThanOrEqualToError<78>>, never>;
54
- export type FibonacciIndex = typeof FibonacciIndex.Type;
89
+ export declare const FibonacciIndex: import("./Type.ts").BrandType<import("./Type.ts").BrandType<import("./Type.ts").BrandType<import("./Type.ts").BrandType<import("./Type.ts").BrandType<import("./Type.ts").BrandType<import("./Type.ts").BrandType<import("./Type.ts").Type<"Number", number, number, import("./Type.ts").TypeOfError<"Number">, null, import("./Type.ts").TypeOfError<"Number">, never, number, true>, "NonNaN", import("./Type.ts").NonNaNError>, "Finite", import("./Type.ts").FiniteError>, "Int", import("./Type.ts").IntError>, "NonNegative", import("./Type.ts").NonNegativeError>, "Positive", import("./Type.ts").PositiveError>, "LessThanOrEqualTo78", import("./Type.ts").LessThanOrEqualToError<78>>, "FibonacciIndex", never>;
90
+ export type FibonacciIndex = typeof FibonacciIndex.Output;
55
91
  /** Returns the Fibonacci number at the given index (1-indexed: 1,1,2,3,5,8,...). */
56
92
  export declare const fibonacciAt: (index: FibonacciIndex) => PositiveInt;
57
93
  //# sourceMappingURL=Number.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"Number.d.ts","sourceRoot":"","sources":["../../src/Number.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAExD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAC5C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAE1C,OAAO,EAIL,cAAc,EACd,WAAW,EACZ,MAAM,WAAW,CAAC;AACnB,OAAO,KAAK,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE1D,eAAO,MAAM,SAAS,GAAI,GAAG,MAAM,KAAG,MAAe,CAAC;AAEtD,eAAO,MAAM,SAAS,GAAI,GAAG,MAAM,KAAG,MAAe,CAAC;AAEtD,4CAA4C;AAC5C,eAAO,MAAM,KAAK,GACf,KAAK,MAAM,EAAE,KAAK,MAAM,MACxB,GAAG,MAAM,KAAG,MACoB,CAAC;AAEpC;;;;;;;;;;GAUG;AACH,eAAO,MAAM,SAAS,GACnB,KAAK,MAAM,EAAE,KAAK,MAAM,KAAG,SAAS,CAAC,MAAM,CAEd,CAAC;AAEjC,wEAAwE;AACxE,eAAO,MAAM,GAAG,GAAI,CAAC,SAAS,MAAM,EAClC,GAAG,QAAQ,CAAC,CAAC,EAAE,GAAG,aAAa,CAAC,CAAC,CAAC,CAAC,KAClC,SAAS,CAAC,CAAC,CAAC,SAAS,IAAI,GAAG,CAAC,GAAG,YAAY,CAAC,CAAC,CACE,CAAC;AAEpD,wEAAwE;AACxE,eAAO,MAAM,GAAG,GAAI,CAAC,SAAS,MAAM,EAClC,GAAG,QAAQ,CAAC,CAAC,EAAE,GAAG,aAAa,CAAC,CAAC,CAAC,CAAC,KAClC,SAAS,CAAC,CAAC,CAAC,SAAS,IAAI,GAAG,CAAC,GAAG,YAAY,CAAC,CAAC,CACE,CAAC;AAEpD;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,sBAAsB,GACjC,eAAe,cAAc;AAE7B,kBAAkB;AAClB,iJAAyC;AAEzC,iBAAiB;AACjB,2JAAkD,KACjD,MAAM,CAAC,qBAAqB,CAAC,WAAW,CAAC,EAAE,WAAW,CAoBxD,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,cAAc,k7BAG1B,CAAC;AACF,MAAM,MAAM,cAAc,GAAG,OAAO,cAAc,CAAC,IAAI,CAAC;AAExD,oFAAoF;AACpF,eAAO,MAAM,WAAW,GAAI,OAAO,cAAc,KAAG,WAMnD,CAAC"}
1
+ {"version":3,"file":"Number.d.ts","sourceRoot":"","sources":["../../src/Number.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAExD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAC5C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAE1C,OAAO,EAIL,cAAc,EACd,WAAW,EACX,KAAK,EACN,MAAM,WAAW,CAAC;AACnB,OAAO,KAAK,EACV,KAAK,EACL,SAAS,EACT,UAAU,EACV,SAAS,EACT,YAAY,EACb,MAAM,YAAY,CAAC;AAEpB,yEAAyE;AACzE,MAAM,MAAM,UAAU,GAAG,iBAAiB,GAAG,KAAK,CAAC;AAEnD;;;;;GAKG;AACH,MAAM,MAAM,iBAAiB,GAC3B,IAAI,GAAG,MAAM,GAAG,GAAG,UAAU,GAAG,GAAG,GAAG,KAAK,GAAG,UAAU,IAAI,SAAS,GAAG,CAAC;AAE3E,kEAAkE;AAClE,eAAO,MAAM,iBAAiB,eAAgB,UAAU,KAAG,KAGQ,CAAC;AAEpE,eAAO,MAAM,SAAS,MAAO,MAAM,KAAG,MAAe,CAAC;AAEtD,6EAA6E;AAC7E,eAAO,MAAM,oBAAoB,MAAO,WAAW,KAAG,WACf,CAAC;AAExC,eAAO,MAAM,SAAS,MAAO,MAAM,KAAG,MAAe,CAAC;AAEtD,4CAA4C;AAC5C,eAAO,MAAM,KAAK,QACV,MAAM,OAAO,MAAM,SACrB,MAAM,KAAG,MACoB,CAAC;AAEpC;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,SAAS,QACd,MAAM,OAAO,MAAM,KAAG,SAAS,CAAC,MAAM,CAEd,CAAC;AAEjC,wEAAwE;AACxE,eAAO,MAAM,GAAG,GAAI,CAAC,SAAS,MAAM,aACvB,CAAC,CAAC,EAAE,GAAG,aAAa,CAAC,CAAC,CAAC,CAAC,KAClC,SAAS,CAAC,CAAC,CAAC,SAAS,IAAI,GAAG,CAAC,GAAG,YAAY,CAAC,CAAC,CACE,CAAC;AAEpD,wEAAwE;AACxE,eAAO,MAAM,GAAG,GAAI,CAAC,SAAS,MAAM,aACvB,CAAC,CAAC,EAAE,GAAG,aAAa,CAAC,CAAC,CAAC,CAAC,KAClC,SAAS,CAAC,CAAC,CAAC,SAAS,IAAI,GAAG,CAAC,GAAG,YAAY,CAAC,CAAC,CACE,CAAC;AAEpD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,eAAO,MAAM,sBAAsB,kBAClB,cAAc;AAE7B,kBAAkB;;AAGlB,iBAAiB;8OAEhB,MAAM,CAAC,qBAAqB,CAAC,WAAW,CAAC,EAAE,WAAW,CAoBxD,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,cAAc,6rBAG1B,CAAC;AACF,MAAM,MAAM,cAAc,GAAG,OAAO,cAAc,CAAC,MAAM,CAAC;AAE1D,oFAAoF;AACpF,eAAO,MAAM,WAAW,UAAW,cAAc,KAAG,WAMnD,CAAC"}
@@ -5,8 +5,14 @@
5
5
  */
6
6
  import { assertNonEmptyReadonlyArray } from "./Assert.js";
7
7
  import { err, ok } from "./Result.js";
8
- import { brand, lessThanOrEqualTo, onePositiveInt, NonNegativeInt, PositiveInt, } from "./Type.js";
8
+ import { brand, lessThanOrEqualTo, onePositiveInt, NonNegativeInt, PositiveInt, Ratio, } from "./Type.js";
9
+ /** Converts a {@link Percentage} to its numeric {@link Ratio}. */
10
+ export const percentageToRatio = (percentage) => typeof percentage === "number"
11
+ ? percentage
12
+ : Ratio.orThrow(globalThis.Number.parseFloat(percentage) / 100);
9
13
  export const increment = (n) => n + 1;
14
+ /** Increments a positive integer, saturating at the maximum safe integer. */
15
+ export const incrementPositiveInt = (n) => PositiveInt.orNull(increment(n)) ?? n;
10
16
  export const decrement = (n) => n - 1;
11
17
  /** Clamps a number within a given range. */
12
18
  export const clamp = (min, max) => (n) => Math.min(Math.max(n, min), max);
@@ -16,9 +22,12 @@ export const clamp = (min, max) => (n) => Math.min(Math.max(n, min), max);
16
22
  * ### Example
17
23
  *
18
24
  * ```ts
25
+ * import { isBetween } from "@evolu/common";
26
+ *
19
27
  * const isBetween10And20 = isBetween(10, 20);
20
- * console.log(isBetween10And20(15)); // true
21
- * console.log(isBetween10And20(25)); // false
28
+ *
29
+ * expect(isBetween10And20(20)).toBe(true);
30
+ * expect(isBetween10And20(25)).toBe(false);
22
31
  * ```
23
32
  */
24
33
  export const isBetween = (min, max) => (value) => value >= min && value <= max;
@@ -34,8 +43,28 @@ export const max = (...values) => values.reduce((a, b) => (a > b ? a : b));
34
43
  * ### Example
35
44
  *
36
45
  * ```ts
37
- * computeBalancedBuckets(10, 3, 2); // Returns ok([4, 7, 10])
38
- * computeBalancedBuckets(5, 3, 2); // Returns err(6)
46
+ * import {
47
+ * computeBalancedBuckets,
48
+ * NonNegativeInt,
49
+ * PositiveInt,
50
+ * } from "@evolu/common";
51
+ *
52
+ * expectOk(
53
+ * computeBalancedBuckets(
54
+ * NonNegativeInt.orThrow(10),
55
+ * PositiveInt.orThrow(3),
56
+ * PositiveInt.orThrow(2),
57
+ * ),
58
+ * [4, 7, 10],
59
+ * );
60
+ * expectErr(
61
+ * computeBalancedBuckets(
62
+ * NonNegativeInt.orThrow(5),
63
+ * PositiveInt.orThrow(3),
64
+ * PositiveInt.orThrow(2),
65
+ * ),
66
+ * 6,
67
+ * );
39
68
  * ```
40
69
  */
41
70
  export const computeBalancedBuckets = (numberOfItems,