@evolu/common 5.4.8 → 6.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 (242) hide show
  1. package/README.md +30 -34
  2. package/dist/src/Array.d.ts +17 -0
  3. package/dist/src/Array.d.ts.map +1 -0
  4. package/dist/src/Array.js +12 -0
  5. package/dist/src/Assert.d.ts +68 -0
  6. package/dist/src/Assert.d.ts.map +1 -0
  7. package/dist/src/Assert.js +77 -0
  8. package/dist/src/BigInt.d.ts +20 -0
  9. package/dist/src/BigInt.d.ts.map +1 -0
  10. package/dist/src/BigInt.js +18 -0
  11. package/dist/src/Buffer.d.ts +92 -0
  12. package/dist/src/Buffer.d.ts.map +1 -0
  13. package/dist/src/Buffer.js +62 -0
  14. package/dist/src/Callbacks.d.ts +20 -0
  15. package/dist/src/Callbacks.d.ts.map +1 -0
  16. package/dist/src/Callbacks.js +18 -0
  17. package/dist/src/Console.d.ts +78 -0
  18. package/dist/src/Console.d.ts.map +1 -0
  19. package/dist/src/Console.js +103 -0
  20. package/dist/src/Crypto.d.ts +72 -39
  21. package/dist/src/Crypto.d.ts.map +1 -1
  22. package/dist/src/Crypto.js +89 -54
  23. package/dist/src/Eq.d.ts +97 -0
  24. package/dist/src/Eq.d.ts.map +1 -0
  25. package/dist/src/Eq.js +167 -0
  26. package/dist/src/Error.d.ts +14 -10
  27. package/dist/src/Error.d.ts.map +1 -1
  28. package/dist/src/Error.js +43 -11
  29. package/dist/src/Evolu/Config.d.ts +69 -0
  30. package/dist/src/Evolu/Config.d.ts.map +1 -0
  31. package/dist/src/Evolu/Config.js +9 -0
  32. package/dist/src/Evolu/Db.d.ts +126 -0
  33. package/dist/src/Evolu/Db.d.ts.map +1 -0
  34. package/dist/src/Evolu/Db.js +774 -0
  35. package/dist/src/Evolu/Diff.d.ts +43 -0
  36. package/dist/src/Evolu/Diff.d.ts.map +1 -0
  37. package/dist/src/Evolu/Diff.js +95 -0
  38. package/dist/src/Evolu/Evolu.d.ts +334 -0
  39. package/dist/src/Evolu/Evolu.d.ts.map +1 -0
  40. package/dist/src/Evolu/Evolu.js +434 -0
  41. package/dist/src/Evolu/Internal.d.ts +26 -0
  42. package/dist/src/Evolu/Internal.d.ts.map +1 -0
  43. package/dist/src/Evolu/Internal.js +25 -0
  44. package/dist/src/Evolu/Kysely.d.ts +6 -0
  45. package/dist/src/Evolu/Kysely.d.ts.map +1 -0
  46. package/dist/src/Evolu/Kysely.js +21 -0
  47. package/dist/src/Evolu/Owner.d.ts +155 -0
  48. package/dist/src/Evolu/Owner.d.ts.map +1 -0
  49. package/dist/src/Evolu/Owner.js +126 -0
  50. package/dist/src/Evolu/Platform.d.ts +23 -0
  51. package/dist/src/Evolu/Platform.d.ts.map +1 -0
  52. package/dist/src/Evolu/Platform.js +1 -0
  53. package/dist/src/Evolu/Protocol.d.ts +401 -0
  54. package/dist/src/Evolu/Protocol.d.ts.map +1 -0
  55. package/dist/src/Evolu/Protocol.js +1151 -0
  56. package/dist/src/Evolu/Public.d.ts +18 -0
  57. package/dist/src/Evolu/Public.d.ts.map +1 -0
  58. package/dist/src/Evolu/Public.js +11 -0
  59. package/dist/src/Evolu/PublicKysely.d.ts +148 -0
  60. package/dist/src/Evolu/PublicKysely.d.ts.map +1 -0
  61. package/dist/src/Evolu/PublicKysely.js +185 -0
  62. package/dist/src/Evolu/Query.d.ts +63 -0
  63. package/dist/src/Evolu/Query.d.ts.map +1 -0
  64. package/dist/src/Evolu/Query.js +61 -0
  65. package/dist/src/Evolu/Relay.d.ts +13 -0
  66. package/dist/src/Evolu/Relay.d.ts.map +1 -0
  67. package/dist/src/Evolu/Relay.js +109 -0
  68. package/dist/src/Evolu/Schema.d.ts +201 -0
  69. package/dist/src/Evolu/Schema.d.ts.map +1 -0
  70. package/dist/src/Evolu/Schema.js +150 -0
  71. package/dist/src/Evolu/Storage.d.ts +49 -0
  72. package/dist/src/Evolu/Storage.d.ts.map +1 -0
  73. package/dist/src/Evolu/Storage.js +1111 -0
  74. package/dist/src/Evolu/Sync.d.ts +59 -0
  75. package/dist/src/Evolu/Sync.d.ts.map +1 -0
  76. package/dist/src/Evolu/Sync.js +29 -0
  77. package/dist/src/Evolu/Timestamp.d.ts +106 -0
  78. package/dist/src/Evolu/Timestamp.d.ts.map +1 -0
  79. package/dist/src/Evolu/Timestamp.js +179 -0
  80. package/dist/src/Function.d.ts +54 -0
  81. package/dist/src/Function.d.ts.map +1 -0
  82. package/dist/src/Function.js +38 -0
  83. package/dist/src/ManyToManyMap.d.ts +26 -0
  84. package/dist/src/ManyToManyMap.d.ts.map +1 -0
  85. package/dist/src/ManyToManyMap.js +92 -0
  86. package/dist/src/NanoId.d.ts +27 -0
  87. package/dist/src/NanoId.d.ts.map +1 -0
  88. package/dist/src/NanoId.js +6 -0
  89. package/dist/src/Number.d.ts +42 -0
  90. package/dist/src/Number.d.ts.map +1 -0
  91. package/dist/src/Number.js +55 -0
  92. package/dist/src/Object.d.ts +35 -0
  93. package/dist/src/Object.d.ts.map +1 -0
  94. package/dist/src/Object.js +36 -0
  95. package/dist/src/Order.d.ts +90 -0
  96. package/dist/src/Order.d.ts.map +1 -0
  97. package/dist/src/Order.js +85 -0
  98. package/dist/src/Promise.d.ts +180 -0
  99. package/dist/src/Promise.d.ts.map +1 -0
  100. package/dist/src/Promise.js +176 -0
  101. package/dist/src/Random.d.ts +52 -0
  102. package/dist/src/Random.d.ts.map +1 -0
  103. package/dist/src/Random.js +29 -0
  104. package/dist/src/Ref.d.ts +40 -0
  105. package/dist/src/Ref.d.ts.map +1 -0
  106. package/dist/src/Ref.js +13 -0
  107. package/dist/src/Result.d.ts +421 -0
  108. package/dist/src/Result.d.ts.map +1 -0
  109. package/dist/src/Result.js +357 -0
  110. package/dist/src/Skiplist.d.ts +23 -0
  111. package/dist/src/Skiplist.d.ts.map +1 -0
  112. package/dist/src/Skiplist.js +58 -0
  113. package/dist/src/Sqlite.d.ts +116 -52
  114. package/dist/src/Sqlite.d.ts.map +1 -1
  115. package/dist/src/Sqlite.js +183 -67
  116. package/dist/src/Store.d.ts +45 -8
  117. package/dist/src/Store.d.ts.map +1 -1
  118. package/dist/src/Store.js +33 -17
  119. package/dist/src/String.d.ts +2 -0
  120. package/dist/src/String.d.ts.map +1 -0
  121. package/dist/src/String.js +14 -0
  122. package/dist/src/Time.d.ts +20 -0
  123. package/dist/src/Time.d.ts.map +1 -0
  124. package/dist/src/Time.js +25 -0
  125. package/dist/src/Type.d.ts +1937 -0
  126. package/dist/src/Type.d.ts.map +1 -0
  127. package/dist/src/Type.js +2002 -0
  128. package/dist/src/Types.d.ts +188 -0
  129. package/dist/src/Types.d.ts.map +1 -0
  130. package/dist/src/Types.js +6 -0
  131. package/dist/src/WebSocket.d.ts +112 -0
  132. package/dist/src/WebSocket.d.ts.map +1 -0
  133. package/dist/src/WebSocket.js +139 -0
  134. package/dist/src/Worker.d.ts +44 -0
  135. package/dist/src/Worker.d.ts.map +1 -0
  136. package/dist/src/Worker.js +66 -0
  137. package/dist/src/index.d.ts +24 -11
  138. package/dist/src/index.d.ts.map +1 -1
  139. package/dist/src/index.js +24 -11
  140. package/package.json +29 -38
  141. package/src/Array.ts +39 -0
  142. package/src/Assert.ts +116 -0
  143. package/src/BigInt.ts +29 -0
  144. package/src/Buffer.ts +175 -0
  145. package/src/Callbacks.ts +43 -0
  146. package/src/Console.ts +159 -0
  147. package/src/Crypto.ts +169 -115
  148. package/src/Eq.ts +204 -0
  149. package/src/Error.ts +57 -20
  150. package/src/Evolu/Config.ts +83 -0
  151. package/src/Evolu/Db.ts +1275 -0
  152. package/src/Evolu/Diff.ts +142 -0
  153. package/src/Evolu/Evolu.ts +947 -0
  154. package/src/Evolu/Internal.ts +26 -0
  155. package/src/Evolu/Kysely.ts +38 -0
  156. package/src/Evolu/Owner.ts +296 -0
  157. package/src/Evolu/Platform.ts +27 -0
  158. package/src/Evolu/Protocol.ts +1857 -0
  159. package/src/Evolu/Public.ts +43 -0
  160. package/src/Evolu/PublicKysely.ts +240 -0
  161. package/src/Evolu/Query.ts +167 -0
  162. package/src/Evolu/Relay.ts +142 -0
  163. package/src/Evolu/Schema.ts +417 -0
  164. package/src/Evolu/Storage.ts +1281 -0
  165. package/src/Evolu/Sync.ts +105 -0
  166. package/src/Evolu/Timestamp.ts +311 -0
  167. package/src/Function.ts +58 -0
  168. package/src/ManyToManyMap.ts +140 -0
  169. package/src/NanoId.ts +39 -0
  170. package/src/Number.ts +90 -0
  171. package/src/Object.ts +64 -0
  172. package/src/Order.ts +113 -0
  173. package/src/Promise.ts +295 -0
  174. package/src/Random.ts +68 -0
  175. package/src/Ref.ts +63 -0
  176. package/src/Result.ts +453 -0
  177. package/src/Skiplist.ts +102 -0
  178. package/src/Sqlite.ts +366 -153
  179. package/src/Store.ts +79 -36
  180. package/src/String.ts +10 -0
  181. package/src/Time.ts +36 -0
  182. package/src/Type.ts +3978 -0
  183. package/src/Types.ts +209 -0
  184. package/src/WebSocket.ts +273 -0
  185. package/src/Worker.ts +129 -0
  186. package/src/index.ts +24 -11
  187. package/dist/src/Config.d.ts +0 -56
  188. package/dist/src/Config.d.ts.map +0 -1
  189. package/dist/src/Config.js +0 -39
  190. package/dist/src/Crdt.d.ts +0 -89
  191. package/dist/src/Crdt.d.ts.map +0 -1
  192. package/dist/src/Crdt.js +0 -181
  193. package/dist/src/Db.d.ts +0 -107
  194. package/dist/src/Db.d.ts.map +0 -1
  195. package/dist/src/Db.js +0 -443
  196. package/dist/src/Diff.d.ts +0 -27
  197. package/dist/src/Diff.d.ts.map +0 -1
  198. package/dist/src/Diff.js +0 -84
  199. package/dist/src/Evolu.d.ts +0 -426
  200. package/dist/src/Evolu.d.ts.map +0 -1
  201. package/dist/src/Evolu.js +0 -333
  202. package/dist/src/Model.d.ts +0 -141
  203. package/dist/src/Model.d.ts.map +0 -1
  204. package/dist/src/Model.js +0 -125
  205. package/dist/src/Murmurhash.d.ts +0 -2
  206. package/dist/src/Murmurhash.d.ts.map +0 -1
  207. package/dist/src/Murmurhash.js +0 -60
  208. package/dist/src/Owner.d.ts +0 -33
  209. package/dist/src/Owner.d.ts.map +0 -1
  210. package/dist/src/Owner.js +0 -26
  211. package/dist/src/Platform.d.ts +0 -37
  212. package/dist/src/Platform.d.ts.map +0 -1
  213. package/dist/src/Platform.js +0 -11
  214. package/dist/src/Protobuf.d.ts +0 -81
  215. package/dist/src/Protobuf.d.ts.map +0 -1
  216. package/dist/src/Protobuf.js +0 -92
  217. package/dist/src/Public.d.ts +0 -13
  218. package/dist/src/Public.d.ts.map +0 -1
  219. package/dist/src/Public.js +0 -6
  220. package/dist/src/Socket.d.ts +0 -8
  221. package/dist/src/Socket.d.ts.map +0 -1
  222. package/dist/src/Socket.js +0 -51
  223. package/dist/src/Sql.d.ts +0 -12
  224. package/dist/src/Sql.d.ts.map +0 -1
  225. package/dist/src/Sql.js +0 -30
  226. package/dist/src/Sync.d.ts +0 -70
  227. package/dist/src/Sync.d.ts.map +0 -1
  228. package/dist/src/Sync.js +0 -127
  229. package/src/Config.ts +0 -119
  230. package/src/Crdt.ts +0 -361
  231. package/src/Db.ts +0 -955
  232. package/src/Diff.ts +0 -114
  233. package/src/Evolu.ts +0 -1016
  234. package/src/Model.ts +0 -233
  235. package/src/Murmurhash.ts +0 -70
  236. package/src/Owner.ts +0 -69
  237. package/src/Platform.ts +0 -47
  238. package/src/Protobuf.ts +0 -155
  239. package/src/Public.ts +0 -12
  240. package/src/Socket.ts +0 -83
  241. package/src/Sql.ts +0 -41
  242. package/src/Sync.ts +0 -315
package/src/Number.ts ADDED
@@ -0,0 +1,90 @@
1
+ import { NonEmptyReadonlyArray } from "./Array.js";
2
+ import { assertNonEmptyReadonlyArray } from "./Assert.js";
3
+ import { err, ok, Result } from "./Result.js";
4
+ import { NonNegativeInt, PositiveInt } from "./Type.js";
5
+ import {
6
+ IntentionalNever,
7
+ IsBranded,
8
+ Predicate,
9
+ WidenLiteral,
10
+ } from "./Types.js";
11
+
12
+ export const increment = (n: number): number => n + 1;
13
+
14
+ export const decrement = (n: number): number => n - 1;
15
+
16
+ /** Clamps a number within a given range. */
17
+ export const clamp =
18
+ (min: number, max: number) =>
19
+ (n: number): number =>
20
+ Math.min(Math.max(n, min), max);
21
+
22
+ /**
23
+ * Creates a predicate that checks if a number is within a range, inclusive.
24
+ *
25
+ * ### Example
26
+ *
27
+ * ```ts
28
+ * const isBetween10And20 = isBetween(10, 20);
29
+ * console.log(isBetween10And20(15)); // true
30
+ * console.log(isBetween10And20(25)); // false
31
+ * ```
32
+ */
33
+ export const isBetween =
34
+ (min: number, max: number): Predicate<number> =>
35
+ (value) =>
36
+ value >= min && value <= max;
37
+
38
+ /** Returns the minimum value, preserving branded type if applicable. */
39
+ export const min = <T extends number>(
40
+ ...values: [T, ...ReadonlyArray<T>]
41
+ ): IsBranded<T> extends true ? T : WidenLiteral<T> =>
42
+ values.reduce((a, b) => (a < b ? a : b)) as IntentionalNever;
43
+
44
+ /** Returns the maximum value, preserving branded type if applicable. */
45
+ export const max = <T extends number>(
46
+ ...values: [T, ...ReadonlyArray<T>]
47
+ ): IsBranded<T> extends true ? T : WidenLiteral<T> =>
48
+ values.reduce((a, b) => (a > b ? a : b)) as IntentionalNever;
49
+
50
+ /**
51
+ * Divides items into buckets as evenly as possible, ensuring each bucket has at
52
+ * least the minimum number of items. Returns a success result if the minimum is
53
+ * met, or an error result with the required number of items if not.
54
+ *
55
+ * ### Example
56
+ *
57
+ * ```ts
58
+ * computeBalancedBuckets(10, 3, 2); // Returns ok([4, 7, 10])
59
+ * computeBalancedBuckets(5, 3, 2); // Returns err(6)
60
+ * ```
61
+ */
62
+ export const computeBalancedBuckets = (
63
+ numberOfItems: NonNegativeInt,
64
+
65
+ /** Default: 16 */
66
+ numberOfBuckets = 16 as PositiveInt,
67
+
68
+ /** Default: 2 */
69
+ minNumberOfItemsPerBucket = 2 as PositiveInt,
70
+ ): Result<NonEmptyReadonlyArray<PositiveInt>, PositiveInt> => {
71
+ const minRequiredItems = numberOfBuckets * minNumberOfItemsPerBucket;
72
+
73
+ if (numberOfItems < minRequiredItems)
74
+ return err(minRequiredItems as PositiveInt);
75
+
76
+ const indexes: Array<PositiveInt> = [];
77
+ const itemsPerBucket = Math.floor(numberOfItems / numberOfBuckets);
78
+ const extraItems = numberOfItems % numberOfBuckets;
79
+
80
+ let bucketBoundary = 0;
81
+ for (let i = 0; i < numberOfBuckets; i++) {
82
+ const hasExtraItem = i < extraItems;
83
+ const itemsInThisBucket = itemsPerBucket + (hasExtraItem ? 1 : 0);
84
+ bucketBoundary += itemsInThisBucket;
85
+ indexes.push(bucketBoundary as PositiveInt);
86
+ }
87
+
88
+ assertNonEmptyReadonlyArray(indexes);
89
+ return ok(indexes);
90
+ };
package/src/Object.ts ADDED
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Checks if a value is a plain object (e.g., created with `{}` or `Object`).
3
+ *
4
+ * ### Example
5
+ *
6
+ * ```ts
7
+ * isPlainObject({}); // true
8
+ * isPlainObject(new Date()); // false
9
+ * isPlainObject([]); // false
10
+ * isPlainObject(null); // false
11
+ * ```
12
+ */
13
+ export const isPlainObject = (
14
+ value: unknown,
15
+ ): value is Record<string, unknown> =>
16
+ Object.prototype.toString.call(value) === "[object Object]";
17
+
18
+ /**
19
+ * A read-only `Record<K, V>` with `K extends keyof any` to preserve branded key
20
+ * types (e.g., in {@link mapObject}).
21
+ */
22
+ export type ReadonlyRecord<K extends keyof any, V> = Readonly<Record<K, V>>;
23
+
24
+ // A helper type to remove symbol keys (e.g for branded objects).
25
+ type StringKeyOf<T> = Extract<keyof T, string>;
26
+
27
+ /**
28
+ * Converts a record to entries, preserving branded string key types (e.g.,
29
+ * `type Id = 'id' & string`) via `StringKeyOf<T>`, unlike `Object.entries`
30
+ * which widens keys to `string`.
31
+ */
32
+ export const objectToEntries = <T extends Record<string, any>>(
33
+ record: T,
34
+ ): Array<[StringKeyOf<T>, T[StringKeyOf<T>]]> =>
35
+ Object.entries(record) as Array<[StringKeyOf<T>, T[StringKeyOf<T>]]>;
36
+
37
+ /**
38
+ * Maps a `ReadonlyRecord<K, V>` to a new `ReadonlyRecord<K, U>`, preserving
39
+ * branded key types (e.g., `type Id = 'id' & string`) lost by `Object.entries`.
40
+ * Uses `K extends string` for precision.
41
+ */
42
+ export const mapObject = <K extends string, V, U>(
43
+ record: ReadonlyRecord<K, V>,
44
+ fn: (value: V, key: K) => U,
45
+ ): ReadonlyRecord<K, U> =>
46
+ Object.fromEntries(
47
+ Object.entries(record).map(([key, value]) => [
48
+ key,
49
+ fn(value as V, key as K),
50
+ ]),
51
+ ) as ReadonlyRecord<K, U>;
52
+
53
+ /** Conditionally excludes a property from an object. */
54
+ export const excludeProp = <T extends object, K extends keyof T>(
55
+ obj: T,
56
+ prop: K,
57
+ condition?: boolean,
58
+ ): typeof condition extends true ? T : Omit<T, K> => {
59
+ if (condition) {
60
+ return { ...obj };
61
+ }
62
+ const { [prop]: _, ...rest } = obj;
63
+ return rest;
64
+ };
package/src/Order.ts ADDED
@@ -0,0 +1,113 @@
1
+ /**
2
+ * 🔢
3
+ *
4
+ * @module
5
+ */
6
+
7
+ /**
8
+ * Compares two values of type `A` and returns their ordering.
9
+ *
10
+ * Ordering functions start with an 'order' prefix, e.g., `orderNumber`.
11
+ *
12
+ * - Returns `-1` if `x` is less than `y`.
13
+ * - Returns `0` if `x` is equal to `y`.
14
+ * - Returns `1` if `x` is greater than `y`.
15
+ */
16
+ export type Order<in A> = (x: A, y: A) => Ordering;
17
+
18
+ /**
19
+ * A type representing the result of an ordering operation.
20
+ *
21
+ * Compatible with the return values expected by `Array.prototype.sort`.
22
+ */
23
+ export type Ordering = -1 | 0 | 1;
24
+
25
+ /**
26
+ * Creates an ordering function from a "less than" comparator.
27
+ *
28
+ * ### Example
29
+ *
30
+ * ```ts
31
+ * const orderNumber = createOrder<number>((x, y) => x < y);
32
+ * expect(orderNumber(1, 2)).toEqual(-1);
33
+ * expect(orderNumber(2, 1)).toEqual(1);
34
+ * expect(orderNumber(1, 1)).toEqual(0);
35
+ * ```
36
+ */
37
+ export const createOrder =
38
+ <A>(isLessThan: (x: A, y: A) => boolean): Order<A> =>
39
+ (x, y) =>
40
+ x === y ? 0 : isLessThan(x, y) ? -1 : 1;
41
+
42
+ /**
43
+ * Returns an order that reverses the order of the given order.
44
+ *
45
+ * ### Example
46
+ *
47
+ * ```ts
48
+ * reverseOrder(orderNumber)(1, 2); // 1
49
+ * reverseOrder(orderNumber)(2, 1); // -1
50
+ * reverseOrder(orderNumber)(1, 1); // 0
51
+ * ```
52
+ */
53
+ export const reverseOrder =
54
+ <A>(order: Order<A>): Order<A> =>
55
+ (a, b) =>
56
+ order(b, a);
57
+
58
+ /**
59
+ * An order for `string` values in ascending order.
60
+ *
61
+ * ### Example
62
+ *
63
+ * ```ts
64
+ * orderString("a", "b"); // -1
65
+ * orderString("b", "a"); // 1
66
+ * orderString("a", "a"); // 0
67
+ * ["c", "b", "a"].toSorted(orderString); // ["a", "b", "c"]
68
+ * ```
69
+ */
70
+ export const orderString: Order<string> = createOrder((a, b) => a < b);
71
+
72
+ /**
73
+ * An order for numbers in ascending order.
74
+ *
75
+ * ### Example
76
+ *
77
+ * ```ts
78
+ * orderNumber(1, 2); // -1
79
+ * orderNumber(2, 1); // 1
80
+ * orderNumber(1, 1); // 0
81
+ * [2, 1, 3].toSorted(orderNumber); // [1, 2, 3]
82
+ * reverseOrder(orderNumber)(1, 2); // 1
83
+ * reverseOrder(orderNumber)(2, 1); // -1
84
+ * reverseOrder(orderNumber)(1, 1); // 0
85
+ * ```
86
+ */
87
+ export const orderNumber = createOrder<number>((a, b) => a < b);
88
+
89
+ /**
90
+ * An order for bigints in ascending order.
91
+ *
92
+ * ### Example
93
+ *
94
+ * ```ts
95
+ * orderBigInt(1n, 2n); // -1
96
+ * orderBigInt(2n, 1n); // 1
97
+ * orderBigInt(1n, 1n); // 0
98
+ * [2n, 1n, 3n].toSorted(orderBigInt); // [1n, 2n, 3n]
99
+ * ```
100
+ */
101
+ export const orderBigInt = createOrder<bigint>((a, b) => a < b);
102
+
103
+ export const orderUint8Array: Order<globalThis.Uint8Array> = (a, b) => {
104
+ if (a.byteLength > b.byteLength) return 1;
105
+ if (a.byteLength < b.byteLength) return -1;
106
+
107
+ for (let i = 0; i < a.byteLength; i++) {
108
+ if (a[i] < b[i]) return -1;
109
+ if (a[i] > b[i]) return 1;
110
+ }
111
+
112
+ return 0;
113
+ };
package/src/Promise.ts ADDED
@@ -0,0 +1,295 @@
1
+ import { constTrue } from "./Function.js";
2
+ import { Result, err, ok } from "./Result.js";
3
+ import { Predicate } from "./Types.js";
4
+
5
+ /**
6
+ * Helper function to delay execution for a specified number of milliseconds.
7
+ *
8
+ * ### Example
9
+ *
10
+ * ```ts
11
+ * await wait(10);
12
+ * ```
13
+ */
14
+ export const wait = (ms: number): Promise<Result<void, never>> =>
15
+ new Promise((resolve) =>
16
+ setTimeout(() => {
17
+ resolve(ok());
18
+ }, ms),
19
+ );
20
+
21
+ /** Options for configuring retry behavior. */
22
+ export interface RetryOptions<E> {
23
+ /**
24
+ * Maximum number of retry attempts after the initial attempt (default: 3).
25
+ * For example, with maxRetries = 3, the function will be called up to 4 times
26
+ * (1 initial attempt + 3 retries).
27
+ */
28
+ maxRetries?: number;
29
+
30
+ /**
31
+ * Initial delay between retry attempts in milliseconds (default: 100). This
32
+ * is the delay after the first failed attempt. Subsequent delays increase
33
+ * exponentially according to the factor option.
34
+ */
35
+ initialDelay?: number;
36
+
37
+ /**
38
+ * Maximum delay between retry attempts in milliseconds (default: 10000). This
39
+ * caps the exponential backoff to prevent extremely long delays after many
40
+ * retries.
41
+ */
42
+ maxDelay?: number;
43
+
44
+ /**
45
+ * Multiplier that determines how quickly the delay increases (default: 2).
46
+ * With the default value, each successive delay is twice as long as the
47
+ * previous one (e.g., 100ms, 200ms, 400ms, 800ms, etc).
48
+ */
49
+ factor?: number;
50
+
51
+ /**
52
+ * Random jitter factor between 0 and 1 (default: 0.1). Adds randomness to
53
+ * delay times to prevent retry storms in distributed systems.
54
+ */
55
+ jitter?: number;
56
+
57
+ /**
58
+ * Optional AbortSignal to cancel retries. If the signal is aborted, the retry
59
+ * operation stops and returns a RetryAbortError.
60
+ */
61
+ signal?: AbortSignal;
62
+
63
+ /**
64
+ * Optional predicate to determine if an error should be retried. Returns true
65
+ * if the error is retryable, false otherwise. This allows selectively
66
+ * retrying only certain types of errors. By default, all errors are
67
+ * considered retryable.
68
+ */
69
+ retryable?: Predicate<E>;
70
+
71
+ /**
72
+ * Optional callback called before each retry attempt. Receives the error that
73
+ * caused the retry, the current attempt number (starting at 1), and the delay
74
+ * in milliseconds before the next attempt.
75
+ */
76
+ onRetry?: (error: E, attempt: number, delay: number) => void;
77
+ }
78
+
79
+ /** Error representing a retry operation that failed after multiple attempts. */
80
+ export interface RetryError<E> {
81
+ readonly type: "RetryError";
82
+ /** The original error that caused the retry to fail */
83
+ readonly cause: E;
84
+ /** Number of retry attempts made */
85
+ readonly attempts: number;
86
+ }
87
+
88
+ /** Error representing a retry operation that was aborted. */
89
+ export interface RetryAbortError {
90
+ readonly type: "RetryAbortError";
91
+ readonly abortedBeforeExecution: boolean;
92
+ }
93
+
94
+ /**
95
+ * Executes a function with retry logic using exponential backoff and jitter.
96
+ *
97
+ * ### Example with Result-based API
98
+ *
99
+ * ```ts
100
+ * interface ApiError {
101
+ * type: "ApiError";
102
+ * statusCode: number;
103
+ * }
104
+ *
105
+ * const fetchData = async (
106
+ * url: string,
107
+ * ): Promise<Result<Data, ApiError>> => {
108
+ * // Implementation that returns Result
109
+ * };
110
+ *
111
+ * const result = await retry(
112
+ * async () => fetchData("https://api.example.com/data"),
113
+ * {
114
+ * maxRetries: 5,
115
+ * initialDelay: 200,
116
+ * // Only retry on specific status codes
117
+ * retryable: (error) =>
118
+ * error.type === "ApiError" && [429, 503].includes(error.statusCode),
119
+ * },
120
+ * );
121
+ *
122
+ * if (!result.ok) {
123
+ * if (result.error.type === "RetryAbortError") {
124
+ * console.log("Operation was aborted");
125
+ * } else {
126
+ * console.log(`Failed after ${result.error.attempts} attempts`);
127
+ * }
128
+ * return;
129
+ * }
130
+ *
131
+ * // Use result.value
132
+ * ```
133
+ *
134
+ * ### Example with tryAsync for exception-based API
135
+ *
136
+ * ```ts
137
+ * interface FetchError {
138
+ * type: "FetchError";
139
+ * message: string;
140
+ * }
141
+ *
142
+ * const controller = new AbortController();
143
+ *
144
+ * const result = await retry(
145
+ * async () =>
146
+ * tryAsync(
147
+ * async () => {
148
+ * const response = await fetch("https://api.example.com/data", {
149
+ * signal: controller.signal,
150
+ * });
151
+ *
152
+ * if (!response.ok) {
153
+ * throw new Error(`HTTP error ${response.status}`);
154
+ * }
155
+ *
156
+ * return await response.json();
157
+ * },
158
+ * (error): FetchError => ({
159
+ * type: "FetchError",
160
+ * message: String(error),
161
+ * }),
162
+ * ),
163
+ * {
164
+ * maxRetries: 3,
165
+ * signal: controller.signal,
166
+ * },
167
+ * );
168
+ * ```
169
+ *
170
+ * ## HTTP Request Recommendations
171
+ *
172
+ * For HTTP requests, configure the `retryable` option to only retry on
173
+ * appropriate errors:
174
+ *
175
+ * - **DO retry**: 429 (Too Many Requests), 503 (Service Unavailable), network
176
+ * errors
177
+ * - **DON'T retry**: 4xx client errors (except 429), most 5xx server errors
178
+ */
179
+ export const retry = async <T, E>(
180
+ fn: () => Promise<Result<T, E>>,
181
+ options: RetryOptions<E> = {},
182
+ ): Promise<Result<T, RetryError<E> | RetryAbortError>> => {
183
+ const {
184
+ maxRetries = 3,
185
+ initialDelay = 100,
186
+ maxDelay = 10000,
187
+ factor = 2,
188
+ jitter = 0.1,
189
+ signal,
190
+ retryable = constTrue,
191
+ onRetry,
192
+ } = options;
193
+
194
+ let attempt = 0;
195
+
196
+ if (signal?.aborted) {
197
+ return err({ type: "RetryAbortError", abortedBeforeExecution: true });
198
+ }
199
+
200
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
201
+ while (true) {
202
+ const result = await fn();
203
+
204
+ if (result.ok) {
205
+ return result;
206
+ }
207
+
208
+ attempt += 1;
209
+
210
+ if (attempt > maxRetries || !retryable(result.error)) {
211
+ return err({
212
+ type: "RetryError",
213
+ cause: result.error,
214
+ attempts: attempt,
215
+ });
216
+ }
217
+
218
+ // Calculate delay with exponential backoff
219
+ const exponentialDelay = initialDelay * Math.pow(factor, attempt);
220
+ const cappedDelay = Math.min(exponentialDelay, maxDelay);
221
+
222
+ // Apply jitter to prevent thundering herd problem
223
+ const randomFactor = 1 - jitter + Math.random() * jitter * 2;
224
+ const delay = Math.floor(cappedDelay * randomFactor);
225
+
226
+ if (onRetry) {
227
+ onRetry(result.error, attempt, delay);
228
+ }
229
+
230
+ if (signal?.aborted) {
231
+ return err({ type: "RetryAbortError", abortedBeforeExecution: false });
232
+ }
233
+
234
+ // Wait with abort support
235
+ if (signal) {
236
+ const abortPromise = new Promise<Result<never, RetryAbortError>>(
237
+ (resolve) => {
238
+ const onAbort = () => {
239
+ resolve(
240
+ err({ type: "RetryAbortError", abortedBeforeExecution: false }),
241
+ );
242
+ };
243
+ signal.addEventListener("abort", onAbort, { once: true });
244
+ },
245
+ );
246
+
247
+ const delayPromise = wait(delay);
248
+ const raceResult = await Promise.race([abortPromise, delayPromise]);
249
+
250
+ if (!raceResult.ok) {
251
+ return raceResult;
252
+ }
253
+ } else {
254
+ await wait(delay);
255
+ }
256
+ }
257
+ };
258
+
259
+ export interface TimeoutError {
260
+ readonly type: "TimeoutError";
261
+ readonly timeoutMs: number;
262
+ }
263
+
264
+ /**
265
+ * Wraps an async function with a timeout, returning {@link Result} that fails
266
+ * with {@link TimeoutError} if the timeout is exceeded. The provided function
267
+ * must accept an AbortSignal and return a Result.
268
+ *
269
+ * ### Example
270
+ *
271
+ * ```ts
272
+ * const fetchWithTimeout = () =>
273
+ * withTimeout((signal) => fetch("url", signal), 5000);
274
+ * const result = await retry(fetchWithTimeout, { maxRetries: 3 });
275
+ * ```
276
+ */
277
+ export const withTimeout = async <T, E>(
278
+ fn: (signal: AbortSignal) => Promise<Result<T, E>>,
279
+ timeoutMs: number,
280
+ ): Promise<Result<T, E | TimeoutError>> => {
281
+ const controller = new AbortController();
282
+ const signal = controller.signal;
283
+ const timeoutId = setTimeout(() => {
284
+ controller.abort();
285
+ }, timeoutMs);
286
+
287
+ const result = await fn(signal);
288
+ clearTimeout(timeoutId);
289
+
290
+ if (signal.aborted) {
291
+ return err({ type: "TimeoutError", timeoutMs });
292
+ }
293
+
294
+ return result;
295
+ };
package/src/Random.ts ADDED
@@ -0,0 +1,68 @@
1
+ /**
2
+ * 🎲
3
+ *
4
+ * @module
5
+ */
6
+
7
+ import { Random as RandomLib } from "random";
8
+
9
+ /**
10
+ * A simple wrapper around Math.random(). Most apps need only this. For more
11
+ * complex needs check {@link RandomLibDep}.
12
+ *
13
+ * ### Example
14
+ *
15
+ * ```ts
16
+ * // For apps
17
+ * const random = createRandom();
18
+ * random.next();
19
+ *
20
+ * // For tests
21
+ * const random = createRandomWithSeed("test");
22
+ * random.next();
23
+ * ```
24
+ */
25
+ export interface Random {
26
+ /** Returns a floating point number in [0, 1). Just like Math.random(). */
27
+ next: () => number;
28
+ }
29
+
30
+ export interface RandomDep {
31
+ random: Random;
32
+ }
33
+
34
+ /** Creates a {@link Random} using Math.random(). */
35
+ export const createRandom = (): Random => ({
36
+ next: () => Math.random(),
37
+ });
38
+
39
+ /**
40
+ * Creates {@link Random} using {@link RandomLibDep} with a seed which is useful
41
+ * for tests.
42
+ */
43
+ export const createRandomWithSeed = (seed: string): Random => {
44
+ const random = new RandomLib(seed);
45
+ return {
46
+ next: () => random.next(),
47
+ };
48
+ };
49
+
50
+ /**
51
+ * A random number generator using the NPM `random` package dependency.
52
+ *
53
+ * https://github.com/transitive-bullshit/random
54
+ */
55
+ export interface RandomLibDep {
56
+ random: RandomLib;
57
+ }
58
+
59
+ /** Creates a `RandomLib` using the NPM `random` package. */
60
+ export const createRandomLib = (): RandomLib => new RandomLib();
61
+
62
+ /**
63
+ * Creates {@link RandomLibDep} using the NPM `random` package with a seed which
64
+ * is useful for tests.
65
+ */
66
+ export const createRandomLibWithSeed = (seed: string): RandomLibDep => ({
67
+ random: new RandomLib(seed),
68
+ });
package/src/Ref.ts ADDED
@@ -0,0 +1,63 @@
1
+ /**
2
+ * A mutable reference for managing state
3
+ *
4
+ * @module
5
+ */
6
+ import type { Store } from "./Store.js";
7
+
8
+ /**
9
+ * `Ref` provides a simple API to hold and update a value, similar to a "ref" in
10
+ * functional programming or React. It exposes methods to get, set, and modify
11
+ * the current state.
12
+ *
13
+ * Use a Ref instead of a variable when you want to pass state around as an
14
+ * object or update it in a controlled way. If you need subscriptions, see
15
+ * {@link Store}.
16
+ *
17
+ * Updating in a controlled way means all changes go through specific methods
18
+ * (`set` or `modify`), making state updates predictable and easy to track.
19
+ *
20
+ * ### Example
21
+ *
22
+ * ```ts
23
+ * const count = createRef(0);
24
+ * count.set(1);
25
+ * count.modify((n) => n + 1);
26
+ * console.log(count.get()); // 2
27
+ * ```
28
+ *
29
+ * ### Example of using Ref as a dependency
30
+ *
31
+ * ```ts
32
+ * interface CounterRefDep {
33
+ * readonly counterRef: Ref<number>;
34
+ * }
35
+ * ```
36
+ */
37
+ export interface Ref<T> {
38
+ /** Returns the current state. */
39
+ readonly get: () => T;
40
+
41
+ /** Sets the state. */
42
+ readonly set: (state: T) => void;
43
+
44
+ /** Modifies the state using an updater function. */
45
+ readonly modify: (updater: (current: T) => T) => void;
46
+ }
47
+
48
+ /** Creates a {@link Ref} with the given initial state. */
49
+ export const createRef = <T>(initialState: T): Ref<T> => {
50
+ let currentState = initialState;
51
+
52
+ return {
53
+ get: () => currentState,
54
+
55
+ set: (state) => {
56
+ currentState = state;
57
+ },
58
+
59
+ modify: (updater) => {
60
+ currentState = updater(currentState);
61
+ },
62
+ };
63
+ };