@evolu/common 5.4.7 → 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/Result.ts ADDED
@@ -0,0 +1,453 @@
1
+ /**
2
+ * 🛡️ Type-safe errors
3
+ *
4
+ * ## Intro
5
+ *
6
+ * The problem with throwing an exception in JavaScript is that the caught error
7
+ * is always of an unknown type. The unknown type is a problem because we can't
8
+ * be sure all errors have been handled because the TypeScript compiler can't
9
+ * help us.
10
+ *
11
+ * Some other languages like Rust 🦀 or Haskell 📚 use a type-safe approach to
12
+ * error handling, where errors are explicitly represented as part of the return
13
+ * type, such as Result or Either, allowing the developer to handle all errors
14
+ * safely. ✅
15
+ *
16
+ * ✨ Evolu uses {@link Result}, and it looks like this:
17
+ *
18
+ * ```ts
19
+ * type Result<T, E> = Ok<T> | Err<E>;
20
+ *
21
+ * interface Ok<T> {
22
+ * readonly ok: true;
23
+ * readonly value: T;
24
+ * }
25
+ *
26
+ * interface Err<E> {
27
+ * readonly ok: false;
28
+ * readonly error: E;
29
+ * }
30
+ *
31
+ * const ok = <T>(value: T): Ok<T> => ({ ok: true, value });
32
+ * const err = <E>(error: E): Err<E> => ({ ok: false, error });
33
+ * ```
34
+ *
35
+ * Now let's look at how `Result` can be used for safe JSON parsing:
36
+ *
37
+ * ```ts
38
+ * interface ParseJsonError {
39
+ * readonly type: "ParseJsonError";
40
+ * readonly message: string;
41
+ * }
42
+ *
43
+ * const parseJson = (value: string): Result<unknown, ParseJsonError> => {
44
+ * try {
45
+ * return ok(JSON.parse(value));
46
+ * } catch (error) {
47
+ * return err({ type: "ParseJsonError", message: String(error) });
48
+ * }
49
+ * };
50
+ *
51
+ * // Result<unknown, ParseJsonError>
52
+ * const json = parseJson('{"key": "value"}');
53
+ *
54
+ * // Fail fast to handle errors early.
55
+ * if (!json.ok) return json; // Err<ParseJsonError>
56
+ *
57
+ * // Now, we have access to the json.value.
58
+ * expectTypeOf(json.value).toBeUnknown();
59
+ * ```
60
+ *
61
+ * Note how we didn't have to use the try/catch, just `if (!json.ok)`, and how
62
+ * the error isn't unknown but has a type.
63
+ *
64
+ * But we had to use `try/catch` in the `parseJson` function. For such a case,
65
+ * wrapping unsafe code, Evolu provides the {@link trySync} helper:
66
+ *
67
+ * ```ts
68
+ * const parseJson = (value: string): Result<unknown, ParseJsonError> =>
69
+ * trySync(
70
+ * () => JSON.parse(value) as unknown,
71
+ * (error) => ({ type: "ParseJsonError", message: String(error) }),
72
+ * );
73
+ * ```
74
+ *
75
+ * ✨ {@link trySync} helper makes unsafe (can throw) synchronous code safe; for
76
+ * unsafe asynchronous code, use {@link tryAsync}.
77
+ *
78
+ * Let's summarize it:
79
+ *
80
+ * - For synchronous safe code, use `ok` and `err`.
81
+ * - For unsafe code, use `trySync` or `tryAsync`.
82
+ * - For asynchronous safe code, use `Promise` with {@link Result}.
83
+ *
84
+ * Asynchronous safe (because of a Promise using Result) code is
85
+ * straightforward:
86
+ *
87
+ * ```ts
88
+ * const fetchUser = async (
89
+ * userId: string,
90
+ * ): Promise<Result<User, FetchUserError>> => {
91
+ * // Simulate an API call
92
+ * return new Promise((resolve) => {
93
+ * setTimeout(() => {
94
+ * if (userId === "1") {
95
+ * resolve(ok({ id: "1", name: "Alice" }));
96
+ * } else {
97
+ * resolve(
98
+ * err({ type: "FetchUserError", reason: "user not found" }),
99
+ * );
100
+ * }
101
+ * }, 1000);
102
+ * });
103
+ * };
104
+ * ```
105
+ *
106
+ * ## Examples
107
+ *
108
+ * ### Sequential Operations with Short-Circuiting
109
+ *
110
+ * When performing a sequence of operations where any failure should stop
111
+ * further processing, use the `Result` type with early returns.
112
+ *
113
+ * Here's an example of a database reset operation that drops tables, restores a
114
+ * schema, and initializes the database, stopping on the first error:
115
+ *
116
+ * ```ts
117
+ * const resetResult = deps.sqlite.transaction(() => {
118
+ * const dropAllTablesResult = dropAllTables(deps);
119
+ * if (!dropAllTablesResult.ok) return dropAllTablesResult;
120
+ *
121
+ * if (message.restore) {
122
+ * const dbSchema = getDbSchema(deps)();
123
+ * if (!dbSchema.ok) return dbSchema;
124
+ *
125
+ * const ensureDbSchemaResult = ensureDbSchema(deps)(
126
+ * message.restore.dbSchema,
127
+ * dbSchema.value,
128
+ * );
129
+ * if (!ensureDbSchemaResult.ok) return ensureDbSchemaResult;
130
+ *
131
+ * const initializeDbResult = initializeDb(deps)(
132
+ * message.restore.mnemonic,
133
+ * );
134
+ * if (!initializeDbResult.ok) return initializeDbResult;
135
+ * }
136
+ * return ok();
137
+ * });
138
+ *
139
+ * if (!resetResult.ok) {
140
+ * deps.postMessage({ type: "onError", error: resetResult.error });
141
+ * return;
142
+ * }
143
+ * ```
144
+ *
145
+ * In this pattern:
146
+ *
147
+ * - Each operation returns a `Result` (e.g., `Result<void, E>` or `Result<T,
148
+ * E>`).
149
+ * - After each operation, check `if (!result.ok)` and return the `Err` to
150
+ * short-circuit.
151
+ * - If all operations succeed, return `ok()` (or another value if needed).
152
+ * - Outside the transaction, handle the final `Result` to report success or
153
+ * failure.
154
+ *
155
+ * This approach ensures type-safe error handling, avoids nested try/catch
156
+ * blocks, and clearly communicates the control flow.
157
+ *
158
+ * ### A function with two different errors:
159
+ *
160
+ * ```ts
161
+ * const example = (value: string): Result<number, FooError | BarError> => {
162
+ * const foo = getFoo(value);
163
+ * if (!foo.ok) return foo;
164
+ *
165
+ * const bar = barize(foo.value);
166
+ * if (!bar.ok) return bar;
167
+ *
168
+ * return ok(barToNumber(bar.value));
169
+ * };
170
+ * ```
171
+ *
172
+ * ## FAQ
173
+ *
174
+ * ### What if my function doesn't return a value on success?
175
+ *
176
+ * If your function performs an operation but doesn't need to return a value on
177
+ * success, you can use `Result<void, E>`. Using `Result<void, E>` is clearer
178
+ * than using `Result<true, E>` or `Result<null, E>` because it communicates
179
+ * that the function doesn't produce a value but can produce errors.
180
+ *
181
+ * ### How do I short-circuit processing of an array on the first error?
182
+ *
183
+ * If you want to stop processing as soon as an error occurs (short-circuit),
184
+ * you should produce and check each `Result` inside a loop:
185
+ *
186
+ * ```ts
187
+ * for (const query of [
188
+ * sql`drop table evolu_owner;`,
189
+ * sql`drop table evolu_message;`,
190
+ * ]) {
191
+ * const result = deps.sqlite.exec(query);
192
+ * if (!result.ok) return result;
193
+ * }
194
+ * // All queries succeeded
195
+ * ```
196
+ *
197
+ * ### How do I handle an array of operations and short-circuit on the first error?
198
+ *
199
+ * If you have an array of operations (not results), you should make them
200
+ * _lazy_—that is, represent each operation as a function (see `LazyValue` in
201
+ * `Function.ts`). This way, you only execute each operation as needed, and can
202
+ * stop on the first error:
203
+ *
204
+ * ```ts
205
+ * import type { LazyValue } from "./Function";
206
+ *
207
+ * const operations: LazyValue<Result<void, MyError>>[] = [
208
+ * () => doSomething(),
209
+ * () => doSomethingElse(),
210
+ * ];
211
+ *
212
+ * for (const op of operations) {
213
+ * const result = op();
214
+ * if (!result.ok) return result;
215
+ * }
216
+ * // All operations succeeded
217
+ * ```
218
+ *
219
+ * If you already have an array of `Result`s, the processing has already
220
+ * happened, so you can't short-circuit. In that case, you can check for the
221
+ * first error:
222
+ *
223
+ * ```ts
224
+ * const firstError = results.find((r) => !r.ok);
225
+ * if (firstError) return firstError;
226
+ * // All results are Ok
227
+ * ```
228
+ *
229
+ * ### Why doesn't Evolu provide "handy helpers"?
230
+ *
231
+ * Evolu intentionally favors imperative patterns (like the `for...of` loop
232
+ * above) over monadic helpers. Imperative code is generally more readable,
233
+ * easier to debug, and more familiar to most JavaScript and TypeScript
234
+ * developers. While monads and functional helpers can be powerful, they often
235
+ * obscure control flow and make debugging harder. Evolu's approach keeps error
236
+ * handling explicit and straightforward.
237
+ *
238
+ * @module
239
+ */
240
+
241
+ /**
242
+ * A `Result` can be either {@link Ok} (success) or {@link Err} (error).
243
+ *
244
+ * Use {@link ok} to create a successful result and {@link err} to create an error
245
+ * result.
246
+ */
247
+ export type Result<T, E> = Ok<T> | Err<E>;
248
+
249
+ /** A successful {@link Result}. */
250
+ export interface Ok<T> {
251
+ readonly ok: true;
252
+ readonly value: T;
253
+ }
254
+
255
+ /**
256
+ * An error {@link Result}.
257
+ *
258
+ * The `error` property can be any type that describes the error. For normal
259
+ * business logic, use a plain object. This allows us to structure errors with
260
+ * custom fields (e.g., `{ type: "MyError", code: 123 }`). Messages for users
261
+ * belong to translations, not to error objects.
262
+ *
263
+ * If you need a stacktrace for debugging, use an `Error` instance or a custom
264
+ * error class to include additional metadata.
265
+ *
266
+ * ### Examples
267
+ *
268
+ * #### Business Logic Error (Plain Object, Recommended)
269
+ *
270
+ * ```ts
271
+ * const failure = err({
272
+ * type: "ParseJsonError",
273
+ * code: 1001,
274
+ * input: "foo",
275
+ * });
276
+ * ```
277
+ *
278
+ * #### Debugging with Stack Trace (Error Instance)
279
+ *
280
+ * ```ts
281
+ * const failure = err(new Error("Something went wrong"));
282
+ * ```
283
+ *
284
+ * #### Custom Error Class
285
+ *
286
+ * ```ts
287
+ * class MyCustomError extends Error {
288
+ * constructor(
289
+ * public code: number,
290
+ * public input: string,
291
+ * ) {
292
+ * super(`Error ${code} on input: ${input}`);
293
+ * this.name = "MyCustomError";
294
+ * }
295
+ * }
296
+ * const failure = err(new MyCustomError(404, "bad-input"));
297
+ * ```
298
+ */
299
+ export interface Err<E> {
300
+ readonly ok: false;
301
+ readonly error: E;
302
+ }
303
+
304
+ /**
305
+ * Creates an {@link Ok} result.
306
+ *
307
+ * - `ok()` creates an `Ok<void>` for operations that succeed without producing a
308
+ * value.
309
+ * - `ok(value)` creates an `Ok<T>` containing the specified value.
310
+ *
311
+ * ### Example
312
+ *
313
+ * ```ts
314
+ * const noValue = ok();
315
+ * console.log(noValue); // { ok: true, value: undefined }
316
+ *
317
+ * const success = ok(42);
318
+ * console.log(success); // { ok: true, value: 42 }
319
+ * ```
320
+ */
321
+ export function ok(): Ok<void>;
322
+ /** Creates an {@link Ok} result with a specified value. */
323
+ export function ok<T>(value: T): Ok<T>;
324
+ export function ok<T>(value = undefined): Ok<T> {
325
+ return { ok: true, value: value as T };
326
+ }
327
+
328
+ /**
329
+ * Creates an {@link Err} result.
330
+ *
331
+ * ### Example
332
+ *
333
+ * ```ts
334
+ * const failure = err("Something went wrong");
335
+ * console.log(failure); // { ok: false, error: "Something went wrong" }
336
+ * ```
337
+ */
338
+ export const err = <E>(error: E): Err<E> => ({ ok: false, error });
339
+
340
+ /**
341
+ * Extracts the value from a {@link Result} if it is an `Ok`, or throws an error
342
+ * if it is an `Err`.
343
+ *
344
+ * **Intended usage:**
345
+ *
346
+ * - For critical code paths (e.g., app startup, config values) where failure
347
+ * should crash the app.
348
+ * - Not recommended for general error handling in application logic—prefer
349
+ * explicit checks.
350
+ *
351
+ * ### Example
352
+ *
353
+ * ```ts
354
+ * // At app startup, crash if config is invalid:
355
+ * const config = getOrThrow(loadConfig());
356
+ * // Safe to use config here
357
+ * ```
358
+ */
359
+ export const getOrThrow = <T, E>(result: Result<T, E>): T => {
360
+ if (result.ok) {
361
+ return result.value;
362
+ } else {
363
+ throw new Error(`Result error: ${JSON.stringify(result.error)}`);
364
+ }
365
+ };
366
+
367
+ /**
368
+ * Wraps synchronous functions that may throw exceptions, returning a
369
+ * {@link Result}.
370
+ *
371
+ * The `trySync` function is designed to handle synchronous code safely by
372
+ * wrapping the execution in a try-catch block. If the function succeeds, it
373
+ * returns an `Ok` result. If an exception is thrown, it maps the error to a
374
+ * custom type and returns an `Err` result.
375
+ *
376
+ * ### Example
377
+ *
378
+ * ```ts
379
+ * interface ParseJsonError {
380
+ * readonly type: "ParseJsonError";
381
+ * readonly message: string;
382
+ * }
383
+ *
384
+ * const parseJson = (value: string): Result<unknown, ParseJsonError> =>
385
+ * trySync(
386
+ * () => JSON.parse(value) as unknown,
387
+ * (error) => ({ type: "ParseJsonError", message: String(error) }),
388
+ * );
389
+ * ```
390
+ */
391
+ export const trySync = <T, E>(
392
+ fn: () => T,
393
+ mapError: (error: unknown) => E,
394
+ ): Result<T, E> => {
395
+ try {
396
+ return ok(fn());
397
+ } catch (error) {
398
+ return err(mapError(error));
399
+ }
400
+ };
401
+
402
+ /**
403
+ * Wraps async functions or any operation returning a promise, returning a
404
+ * {@link Result}.
405
+ *
406
+ * The `tryAsync` function provides a way to handle asynchronous code safely by
407
+ * catching any rejected promises and mapping errors to a custom type. If the
408
+ * promise resolves, it returns an `Ok` result. If the promise rejects, it maps
409
+ * the error and returns an `Err` result.
410
+ *
411
+ * ### Example
412
+ *
413
+ * ```ts
414
+ * interface FetchError {
415
+ * readonly type: "FetchError";
416
+ * readonly message: string;
417
+ * }
418
+ *
419
+ * const tryFetch = async (
420
+ * url: string,
421
+ * ): Promise<Result<unknown, FetchError>> =>
422
+ * tryAsync(
423
+ * async () => {
424
+ * const response = await fetch(url);
425
+ * if (!response.ok) {
426
+ * throw new Error(`Request failed with status ${response.status}`);
427
+ * }
428
+ * return response.json();
429
+ * },
430
+ * (error) => ({
431
+ * type: "FetchError",
432
+ * message: String(error),
433
+ * }),
434
+ * );
435
+ *
436
+ * const result = await tryFetch(
437
+ * "https://jsonplaceholder.typicode.com/posts/1",
438
+ * );
439
+ * if (result.ok) {
440
+ * console.log("Data:", result.value);
441
+ * } else {
442
+ * console.error("Error:", result.error);
443
+ * }
444
+ * ```
445
+ */
446
+ export const tryAsync = async <T, E>(
447
+ promiseFn: () => Promise<T>,
448
+ mapError: (error: unknown) => E,
449
+ ): Promise<Result<T, E>> =>
450
+ promiseFn().then(
451
+ (value) => ok(value),
452
+ (error: unknown) => err(mapError(error)),
453
+ );
@@ -0,0 +1,102 @@
1
+ import { RandomDep } from "./Random.js";
2
+ import { PositiveInt } from "./Type.js";
3
+
4
+ export interface SkiplistLevel {
5
+ readonly create: () => PositiveInt;
6
+ }
7
+
8
+ export interface SkiplistLevelDep {
9
+ readonly skiplistLevel: SkiplistLevel;
10
+ }
11
+
12
+ export interface SkiplistLevelConfig {
13
+ readonly probability?: number;
14
+ readonly maxLevel?: number;
15
+ }
16
+
17
+ // TODO: Use in Storage.
18
+ export const createSkiplistLevel =
19
+ (deps: RandomDep) =>
20
+ (config: SkiplistLevelConfig = {}): SkiplistLevel => {
21
+ const { probability = 0.5, maxLevel = 32 } = config;
22
+ return {
23
+ create: () => {
24
+ let level = 1;
25
+ while (deps.random.next() <= probability && level < maxLevel) {
26
+ level += 1;
27
+ }
28
+ return level as PositiveInt;
29
+ },
30
+ };
31
+ };
32
+
33
+ export interface Skiplist {
34
+ readonly insert: (key: number) => void;
35
+ readonly find: (key: number) => SkiplistNode | null;
36
+ }
37
+
38
+ export interface SkiplistNode {
39
+ readonly key: number;
40
+ readonly pointers: Array<SkiplistNode>;
41
+ }
42
+
43
+ export const createSkiplist = (deps: SkiplistLevelDep): Skiplist => {
44
+ const tail: SkiplistNode = {
45
+ key: Number.MAX_VALUE,
46
+ pointers: [],
47
+ };
48
+
49
+ const head: SkiplistNode = {
50
+ key: Number.MIN_VALUE,
51
+ pointers: [tail],
52
+ };
53
+
54
+ // Cached to reuse the array.
55
+ const path: Array<SkiplistNode> = [];
56
+
57
+ return {
58
+ insert: (key) => {
59
+ let cur = head;
60
+
61
+ for (let i = head.pointers.length - 1; i >= 0; i--) {
62
+ while (cur.pointers[i].key < key) {
63
+ cur = cur.pointers[i];
64
+ }
65
+ path[i] = cur;
66
+ }
67
+
68
+ const level = deps.skiplistLevel.create();
69
+
70
+ if (level > head.pointers.length) {
71
+ const tails = Array<SkiplistNode>(level - head.pointers.length).fill(
72
+ tail,
73
+ );
74
+ const heads = Array<SkiplistNode>(level - head.pointers.length).fill(
75
+ head,
76
+ );
77
+ head.pointers.push(...tails);
78
+ path.push(...heads);
79
+ }
80
+
81
+ const newNode: SkiplistNode = { key, pointers: [] };
82
+
83
+ for (let i = 0; i < level; i++) {
84
+ newNode.pointers[i] = path[i].pointers[i];
85
+ path[i].pointers[i] = newNode;
86
+ }
87
+ },
88
+
89
+ find: (key) => {
90
+ let cur = head;
91
+
92
+ for (let i = head.pointers.length - 1; i >= 0; i--) {
93
+ while (cur.pointers[i] && cur.pointers[i].key < key) {
94
+ cur = cur.pointers[i];
95
+ }
96
+ }
97
+
98
+ const candidate = cur.pointers[0];
99
+ return candidate.key === key ? candidate : null;
100
+ },
101
+ };
102
+ };