@evolu/common 8.9.0 → 8.11.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 (337) hide show
  1. package/dist/src/Bytes.d.ts +647 -0
  2. package/dist/src/Bytes.d.ts.map +1 -0
  3. package/dist/src/{Binary.js → Bytes.js} +266 -16
  4. package/dist/src/Config.d.ts +142 -0
  5. package/dist/src/Config.d.ts.map +1 -0
  6. package/dist/src/Config.js +181 -0
  7. package/dist/src/Console.d.ts +62 -7
  8. package/dist/src/Console.d.ts.map +1 -1
  9. package/dist/src/Console.js +20 -4
  10. package/dist/src/Crypto.d.ts +76 -4
  11. package/dist/src/Crypto.d.ts.map +1 -1
  12. package/dist/src/Crypto.js +55 -4
  13. package/dist/src/Error.d.ts +45 -0
  14. package/dist/src/Error.d.ts.map +1 -1
  15. package/dist/src/Error.js +69 -0
  16. package/dist/src/Fs.d.ts +376 -0
  17. package/dist/src/Fs.d.ts.map +1 -0
  18. package/dist/src/Fs.js +113 -0
  19. package/dist/src/Identicon.d.ts +2 -2
  20. package/dist/src/Identicon.js +2 -2
  21. package/dist/src/LeakDetector.d.ts +22 -3
  22. package/dist/src/LeakDetector.d.ts.map +1 -1
  23. package/dist/src/LeakDetector.js +12 -2
  24. package/dist/src/LockManager.d.ts +8 -0
  25. package/dist/src/LockManager.d.ts.map +1 -1
  26. package/dist/src/LockManager.js +6 -0
  27. package/dist/src/Number.d.ts +50 -7
  28. package/dist/src/Number.d.ts.map +1 -1
  29. package/dist/src/Number.js +47 -8
  30. package/dist/src/Object.d.ts +32 -0
  31. package/dist/src/Object.d.ts.map +1 -1
  32. package/dist/src/Object.js +46 -0
  33. package/dist/src/Platform.d.ts +47 -7
  34. package/dist/src/Platform.d.ts.map +1 -1
  35. package/dist/src/Platform.js +24 -5
  36. package/dist/src/Random.d.ts +25 -2
  37. package/dist/src/Random.d.ts.map +1 -1
  38. package/dist/src/Random.js +14 -2
  39. package/dist/src/Resource.d.ts +156 -1
  40. package/dist/src/Resource.d.ts.map +1 -1
  41. package/dist/src/Resource.js +201 -72
  42. package/dist/src/Schedule.d.ts +11 -10
  43. package/dist/src/Schedule.d.ts.map +1 -1
  44. package/dist/src/Schedule.js +1 -1
  45. package/dist/src/Sqlite.d.ts +132 -16
  46. package/dist/src/Sqlite.d.ts.map +1 -1
  47. package/dist/src/Sqlite.js +64 -10
  48. package/dist/src/Task.d.ts +15 -4
  49. package/dist/src/Task.d.ts.map +1 -1
  50. package/dist/src/Task.js +41 -15
  51. package/dist/src/Test.d.ts +9 -0
  52. package/dist/src/Test.d.ts.map +1 -1
  53. package/dist/src/Test.js +4 -0
  54. package/dist/src/Time.d.ts +179 -20
  55. package/dist/src/Time.d.ts.map +1 -1
  56. package/dist/src/Time.js +95 -6
  57. package/dist/src/Type.d.ts +3056 -1539
  58. package/dist/src/Type.d.ts.map +1 -1
  59. package/dist/src/Type.js +2548 -584
  60. package/dist/src/WebSocket.d.ts +164 -13
  61. package/dist/src/WebSocket.d.ts.map +1 -1
  62. package/dist/src/WebSocket.js +133 -24
  63. package/dist/src/Worker.d.ts +90 -8
  64. package/dist/src/Worker.d.ts.map +1 -1
  65. package/dist/src/Worker.js +28 -2
  66. package/dist/src/index.d.ts +9 -8
  67. package/dist/src/index.d.ts.map +1 -1
  68. package/dist/src/index.js +5 -4
  69. package/dist/src/intl/_en.d.ts +24 -1
  70. package/dist/src/intl/_en.d.ts.map +1 -1
  71. package/dist/src/intl/_en.js +20 -0
  72. package/dist/src/intl/ar.d.ts +24 -1
  73. package/dist/src/intl/ar.d.ts.map +1 -1
  74. package/dist/src/intl/ar.js +20 -0
  75. package/dist/src/intl/bn.d.ts +24 -1
  76. package/dist/src/intl/bn.d.ts.map +1 -1
  77. package/dist/src/intl/bn.js +20 -0
  78. package/dist/src/intl/ca.d.ts +24 -1
  79. package/dist/src/intl/ca.d.ts.map +1 -1
  80. package/dist/src/intl/ca.js +20 -0
  81. package/dist/src/intl/cs.d.ts +24 -1
  82. package/dist/src/intl/cs.d.ts.map +1 -1
  83. package/dist/src/intl/cs.js +20 -0
  84. package/dist/src/intl/da.d.ts +24 -1
  85. package/dist/src/intl/da.d.ts.map +1 -1
  86. package/dist/src/intl/da.js +20 -0
  87. package/dist/src/intl/de.d.ts +24 -1
  88. package/dist/src/intl/de.d.ts.map +1 -1
  89. package/dist/src/intl/de.js +20 -0
  90. package/dist/src/intl/el.d.ts +24 -1
  91. package/dist/src/intl/el.d.ts.map +1 -1
  92. package/dist/src/intl/el.js +20 -0
  93. package/dist/src/intl/es.d.ts +24 -1
  94. package/dist/src/intl/es.d.ts.map +1 -1
  95. package/dist/src/intl/es.js +20 -0
  96. package/dist/src/intl/fa.d.ts +24 -1
  97. package/dist/src/intl/fa.d.ts.map +1 -1
  98. package/dist/src/intl/fa.js +20 -0
  99. package/dist/src/intl/fi.d.ts +24 -1
  100. package/dist/src/intl/fi.d.ts.map +1 -1
  101. package/dist/src/intl/fi.js +20 -0
  102. package/dist/src/intl/fil.d.ts +24 -1
  103. package/dist/src/intl/fil.d.ts.map +1 -1
  104. package/dist/src/intl/fil.js +20 -0
  105. package/dist/src/intl/fr.d.ts +24 -1
  106. package/dist/src/intl/fr.d.ts.map +1 -1
  107. package/dist/src/intl/fr.js +20 -0
  108. package/dist/src/intl/he.d.ts +24 -1
  109. package/dist/src/intl/he.d.ts.map +1 -1
  110. package/dist/src/intl/he.js +20 -0
  111. package/dist/src/intl/hi.d.ts +24 -1
  112. package/dist/src/intl/hi.d.ts.map +1 -1
  113. package/dist/src/intl/hi.js +20 -0
  114. package/dist/src/intl/hr.d.ts +24 -1
  115. package/dist/src/intl/hr.d.ts.map +1 -1
  116. package/dist/src/intl/hr.js +20 -0
  117. package/dist/src/intl/hu.d.ts +22 -1
  118. package/dist/src/intl/hu.d.ts.map +1 -1
  119. package/dist/src/intl/hu.js +18 -0
  120. package/dist/src/intl/id.d.ts +24 -1
  121. package/dist/src/intl/id.d.ts.map +1 -1
  122. package/dist/src/intl/id.js +20 -0
  123. package/dist/src/intl/it.d.ts +24 -1
  124. package/dist/src/intl/it.d.ts.map +1 -1
  125. package/dist/src/intl/it.js +20 -0
  126. package/dist/src/intl/ja.d.ts +24 -1
  127. package/dist/src/intl/ja.d.ts.map +1 -1
  128. package/dist/src/intl/ja.js +20 -0
  129. package/dist/src/intl/ko.d.ts +24 -1
  130. package/dist/src/intl/ko.d.ts.map +1 -1
  131. package/dist/src/intl/ko.js +20 -0
  132. package/dist/src/intl/ml.d.ts +24 -1
  133. package/dist/src/intl/ml.d.ts.map +1 -1
  134. package/dist/src/intl/ml.js +20 -0
  135. package/dist/src/intl/mr.d.ts +24 -1
  136. package/dist/src/intl/mr.d.ts.map +1 -1
  137. package/dist/src/intl/mr.js +20 -0
  138. package/dist/src/intl/ms.d.ts +24 -1
  139. package/dist/src/intl/ms.d.ts.map +1 -1
  140. package/dist/src/intl/ms.js +20 -0
  141. package/dist/src/intl/nb.d.ts +22 -1
  142. package/dist/src/intl/nb.d.ts.map +1 -1
  143. package/dist/src/intl/nb.js +18 -0
  144. package/dist/src/intl/nl.d.ts +24 -1
  145. package/dist/src/intl/nl.d.ts.map +1 -1
  146. package/dist/src/intl/nl.js +20 -0
  147. package/dist/src/intl/pa.d.ts +24 -1
  148. package/dist/src/intl/pa.d.ts.map +1 -1
  149. package/dist/src/intl/pa.js +20 -0
  150. package/dist/src/intl/pl.d.ts +23 -0
  151. package/dist/src/intl/pl.d.ts.map +1 -1
  152. package/dist/src/intl/pl.js +20 -0
  153. package/dist/src/intl/pt-BR.d.ts +24 -1
  154. package/dist/src/intl/pt-BR.d.ts.map +1 -1
  155. package/dist/src/intl/pt-BR.js +20 -0
  156. package/dist/src/intl/pt.d.ts +24 -1
  157. package/dist/src/intl/pt.d.ts.map +1 -1
  158. package/dist/src/intl/pt.js +20 -0
  159. package/dist/src/intl/ro.d.ts +24 -1
  160. package/dist/src/intl/ro.d.ts.map +1 -1
  161. package/dist/src/intl/ro.js +20 -0
  162. package/dist/src/intl/sk.d.ts +24 -1
  163. package/dist/src/intl/sk.d.ts.map +1 -1
  164. package/dist/src/intl/sk.js +20 -0
  165. package/dist/src/intl/sl.d.ts +24 -1
  166. package/dist/src/intl/sl.d.ts.map +1 -1
  167. package/dist/src/intl/sl.js +20 -0
  168. package/dist/src/intl/sv.d.ts +24 -1
  169. package/dist/src/intl/sv.d.ts.map +1 -1
  170. package/dist/src/intl/sv.js +20 -0
  171. package/dist/src/intl/sw.d.ts +21 -0
  172. package/dist/src/intl/sw.d.ts.map +1 -1
  173. package/dist/src/intl/sw.js +18 -0
  174. package/dist/src/intl/ta.d.ts +24 -1
  175. package/dist/src/intl/ta.d.ts.map +1 -1
  176. package/dist/src/intl/ta.js +20 -0
  177. package/dist/src/intl/te.d.ts +24 -1
  178. package/dist/src/intl/te.d.ts.map +1 -1
  179. package/dist/src/intl/te.js +20 -0
  180. package/dist/src/intl/th.d.ts +24 -1
  181. package/dist/src/intl/th.d.ts.map +1 -1
  182. package/dist/src/intl/th.js +20 -0
  183. package/dist/src/intl/tr.d.ts +24 -1
  184. package/dist/src/intl/tr.d.ts.map +1 -1
  185. package/dist/src/intl/tr.js +20 -0
  186. package/dist/src/intl/uk.d.ts +80 -57
  187. package/dist/src/intl/uk.d.ts.map +1 -1
  188. package/dist/src/intl/uk.js +174 -149
  189. package/dist/src/intl/ur.d.ts +24 -1
  190. package/dist/src/intl/ur.d.ts.map +1 -1
  191. package/dist/src/intl/ur.js +20 -0
  192. package/dist/src/intl/vi.d.ts +24 -1
  193. package/dist/src/intl/vi.d.ts.map +1 -1
  194. package/dist/src/intl/vi.js +20 -0
  195. package/dist/src/intl/zh-CN.d.ts +24 -1
  196. package/dist/src/intl/zh-CN.d.ts.map +1 -1
  197. package/dist/src/intl/zh-CN.js +20 -0
  198. package/dist/src/intl/zh-TW.d.ts +24 -1
  199. package/dist/src/intl/zh-TW.d.ts.map +1 -1
  200. package/dist/src/intl/zh-TW.js +20 -0
  201. package/dist/src/local-first/Db.d.ts +52 -3
  202. package/dist/src/local-first/Db.d.ts.map +1 -1
  203. package/dist/src/local-first/Db.js +412 -137
  204. package/dist/src/local-first/Evolu.d.ts +336 -211
  205. package/dist/src/local-first/Evolu.d.ts.map +1 -1
  206. package/dist/src/local-first/Evolu.js +102 -15
  207. package/dist/src/local-first/Owner.d.ts +13 -30
  208. package/dist/src/local-first/Owner.d.ts.map +1 -1
  209. package/dist/src/local-first/Owner.js +13 -30
  210. package/dist/src/local-first/Protocol.d.ts +95 -17
  211. package/dist/src/local-first/Protocol.d.ts.map +1 -1
  212. package/dist/src/local-first/Protocol.js +119 -39
  213. package/dist/src/local-first/Query.d.ts +8 -15
  214. package/dist/src/local-first/Query.d.ts.map +1 -1
  215. package/dist/src/local-first/Schema.d.ts +345 -21
  216. package/dist/src/local-first/Schema.d.ts.map +1 -1
  217. package/dist/src/local-first/Schema.js +214 -17
  218. package/dist/src/local-first/Shared.d.ts +537 -22
  219. package/dist/src/local-first/Shared.d.ts.map +1 -1
  220. package/dist/src/local-first/Shared.js +1437 -234
  221. package/dist/src/local-first/Storage.d.ts +192 -14
  222. package/dist/src/local-first/Storage.d.ts.map +1 -1
  223. package/dist/src/local-first/Storage.js +82 -21
  224. package/dist/src/local-first/Timestamp.d.ts +392 -41
  225. package/dist/src/local-first/Timestamp.d.ts.map +1 -1
  226. package/dist/src/local-first/Timestamp.js +404 -82
  227. package/dist/src/local-first/index.d.ts +0 -1
  228. package/dist/src/local-first/index.d.ts.map +1 -1
  229. package/dist/src/local-first/index.js +0 -1
  230. package/package.json +1 -1
  231. package/src/Assert.test.ts +2 -5
  232. package/src/{Binary.test.ts → Bytes.test.ts} +286 -1
  233. package/src/{Binary.ts → Bytes.ts} +652 -21
  234. package/src/Config.test.ts +668 -0
  235. package/src/Config.ts +410 -0
  236. package/src/Console.ts +62 -7
  237. package/src/Crypto.ts +76 -4
  238. package/src/Eq.test.ts +2 -3
  239. package/src/Error.test.ts +76 -3
  240. package/src/Error.ts +71 -0
  241. package/src/Fs.test.ts +105 -0
  242. package/src/Fs.ts +488 -0
  243. package/src/Identicon.ts +2 -2
  244. package/src/LeakDetector.ts +22 -3
  245. package/src/LockManager.ts +8 -0
  246. package/src/Number.test.ts +82 -18
  247. package/src/Number.ts +76 -8
  248. package/src/Object.test.ts +139 -10
  249. package/src/Object.ts +49 -0
  250. package/src/Platform.ts +50 -8
  251. package/src/Random.ts +25 -2
  252. package/src/Resource.test.ts +837 -0
  253. package/src/Resource.ts +235 -15
  254. package/src/Schedule.test.ts +50 -12
  255. package/src/Schedule.ts +24 -14
  256. package/src/Sqlite.ts +138 -18
  257. package/src/Task.test.ts +189 -8
  258. package/src/Task.ts +56 -17
  259. package/src/Test.ts +9 -0
  260. package/src/Time.test.ts +82 -11
  261. package/src/Time.ts +246 -24
  262. package/src/Type.test.ts +3994 -1119
  263. package/src/Type.ts +7258 -3842
  264. package/src/Types.test.ts +4 -14
  265. package/src/WebSocket.ts +313 -40
  266. package/src/Worker.ts +90 -8
  267. package/src/index.ts +18 -7
  268. package/src/intl/_en.ts +70 -0
  269. package/src/intl/ar.ts +71 -0
  270. package/src/intl/bn.ts +70 -0
  271. package/src/intl/ca.ts +70 -0
  272. package/src/intl/cs.ts +70 -0
  273. package/src/intl/da.ts +70 -0
  274. package/src/intl/de.ts +70 -0
  275. package/src/intl/el.ts +70 -0
  276. package/src/intl/es.ts +70 -0
  277. package/src/intl/fa.ts +70 -0
  278. package/src/intl/fi.ts +70 -0
  279. package/src/intl/fil.ts +70 -0
  280. package/src/intl/fr.ts +70 -0
  281. package/src/intl/he.ts +70 -0
  282. package/src/intl/hi.ts +70 -0
  283. package/src/intl/hr.ts +70 -0
  284. package/src/intl/hu.ts +69 -0
  285. package/src/intl/id.ts +70 -0
  286. package/src/intl/intl.test.ts +819 -1
  287. package/src/intl/it.ts +70 -0
  288. package/src/intl/ja.ts +70 -0
  289. package/src/intl/ko.ts +68 -0
  290. package/src/intl/ml.ts +70 -0
  291. package/src/intl/mr.ts +70 -0
  292. package/src/intl/ms.ts +71 -0
  293. package/src/intl/nb.ts +69 -0
  294. package/src/intl/nl.ts +70 -0
  295. package/src/intl/pa.ts +70 -0
  296. package/src/intl/pl.ts +63 -0
  297. package/src/intl/pt-BR.ts +70 -0
  298. package/src/intl/pt.ts +71 -0
  299. package/src/intl/ro.ts +70 -0
  300. package/src/intl/sk.ts +71 -0
  301. package/src/intl/sl.ts +70 -0
  302. package/src/intl/sv.ts +70 -0
  303. package/src/intl/sw.ts +62 -0
  304. package/src/intl/ta.ts +70 -0
  305. package/src/intl/te.ts +70 -0
  306. package/src/intl/th.ts +68 -0
  307. package/src/intl/tr.ts +70 -0
  308. package/src/intl/uk.ts +228 -155
  309. package/src/intl/ur.ts +70 -0
  310. package/src/intl/vi.ts +70 -0
  311. package/src/intl/zh-CN.ts +68 -0
  312. package/src/intl/zh-TW.ts +68 -0
  313. package/src/local-first/Db.ts +644 -339
  314. package/src/local-first/Evolu.test.ts +686 -21
  315. package/src/local-first/Evolu.ts +450 -228
  316. package/src/local-first/Owner.ts +13 -30
  317. package/src/local-first/Protocol.test.ts +618 -11
  318. package/src/local-first/Protocol.ts +197 -73
  319. package/src/local-first/Query.ts +8 -15
  320. package/src/local-first/Schema.test.ts +143 -0
  321. package/src/local-first/Schema.ts +374 -24
  322. package/src/local-first/Shared.test.ts +7731 -559
  323. package/src/local-first/Shared.ts +2036 -267
  324. package/src/local-first/Storage.ts +219 -33
  325. package/src/local-first/Timestamp.test.ts +344 -70
  326. package/src/local-first/Timestamp.ts +435 -119
  327. package/src/local-first/index.ts +0 -1
  328. package/dist/src/Binary.d.ts +0 -254
  329. package/dist/src/Binary.d.ts.map +0 -1
  330. package/dist/src/local-first/Error.d.ts +0 -12
  331. package/dist/src/local-first/Error.d.ts.map +0 -1
  332. package/dist/src/local-first/Error.js +0 -6
  333. package/dist/src/local-first/LocalAuth.d.ts +0 -150
  334. package/dist/src/local-first/LocalAuth.d.ts.map +0 -1
  335. package/dist/src/local-first/LocalAuth.js +0 -179
  336. package/src/local-first/Error.ts +0 -17
  337. package/src/local-first/LocalAuth.ts +0 -457
package/src/Fs.ts ADDED
@@ -0,0 +1,488 @@
1
+ /**
2
+ * File system operations for {@link Task}s.
3
+ *
4
+ * {@link Fs} reads, writes, copies, and renames files; lists and manages
5
+ * directories; and provides metadata and existence checks. Each operation
6
+ * returns a Task.
7
+ *
8
+ * Tasks can sequence file operations without synchronous I/O. Node.js's
9
+ * synchronous methods are intentionally omitted to avoid accidentally blocking
10
+ * the event loop.
11
+ *
12
+ * Inject {@link @evolu/nodejs!createNodeFs | createNodeFs} through
13
+ * {@link @evolu/nodejs!runMain | runMain} or {@link createRun}. Tasks declare
14
+ * {@link FsDep} and access the file system through `run.deps.fs`.
15
+ *
16
+ * {@link FsError} includes the path, a diagnostic message, and a
17
+ * {@link FsErrorReason} such as `NotFound` or `PermissionDenied`.
18
+ * {@link Fs.exists} returns `false` for `NotFound` and preserves other errors.
19
+ *
20
+ * ### Example
21
+ *
22
+ * ```ts
23
+ * import {
24
+ * assertEqual,
25
+ * ok,
26
+ * type FsDep,
27
+ * type FsError,
28
+ * type Task,
29
+ * } from "@evolu/common";
30
+ * import { createNodeFs, runMain } from "@evolu/nodejs";
31
+ * import { join } from "node:path";
32
+ *
33
+ * const main: Task<void, FsError, FsDep> = async (run) => {
34
+ * const { fs } = run.deps;
35
+ * const temp = await run(
36
+ * fs.createTempDirectory({ prefix: "evolu-fs-" }),
37
+ * );
38
+ * if (!temp.ok) return temp;
39
+ *
40
+ * await using directory = temp.value;
41
+ * const path = join(directory.path, "message.txt");
42
+ *
43
+ * const result = await run(fs.writeFile(path, "hello"));
44
+ * if (!result.ok) return result;
45
+ *
46
+ * const text = await run(fs.readFile(path, "utf8"));
47
+ * if (!text.ok) return text;
48
+ * assertEqual(text.value, "hello");
49
+ *
50
+ * return ok();
51
+ * };
52
+ *
53
+ * await runMain({ fs: createNodeFs() }, { mode: "command" })(main);
54
+ * ```
55
+ *
56
+ * @module
57
+ */
58
+
59
+ import type { ByteLength } from "./Bytes.ts";
60
+ import type { createRun, Task } from "./Task.ts";
61
+ import type { Typed } from "./Type.ts";
62
+
63
+ /**
64
+ * Asynchronous file system operations.
65
+ *
66
+ * @group Core
67
+ */
68
+ export interface Fs {
69
+ /**
70
+ * Reads a whole file, as bytes by default or as a string with an encoding. An
71
+ * abort requests cancellation; pending operating system reads may still
72
+ * finish.
73
+ */
74
+ readonly readFile: FsReadFile;
75
+
76
+ /**
77
+ * Writes a file, creating it or truncating an existing file by default. An
78
+ * abort requests cancellation and can leave the file partially written or
79
+ * truncated.
80
+ */
81
+ readonly writeFile: (
82
+ path: FsPath,
83
+ data: string | Uint8Array,
84
+ options?: FsWriteFileOptions,
85
+ ) => Task<void, FsError>;
86
+
87
+ /**
88
+ * Lists entry names relative to `path`, in unspecified order. With
89
+ * `recursive`, includes nested entries with their relative paths.
90
+ */
91
+ readonly readDirectory: (
92
+ path: FsPath,
93
+ options?: FsReadDirectoryOptions,
94
+ ) => Task<ReadonlyArray<string>, FsError>;
95
+
96
+ /**
97
+ * Creates a directory. Without `recursive`, an existing directory fails with
98
+ * `AlreadyExists` and a missing parent with `NotFound`.
99
+ */
100
+ readonly createDirectory: (
101
+ path: FsPath,
102
+ options?: FsCreateDirectoryOptions,
103
+ ) => Task<void, FsError>;
104
+
105
+ /**
106
+ * Copies a file or directory tree using Node's recursive `cp` semantics. By
107
+ * default, existing directories are merged and files are replaced. Symbolic
108
+ * links follow Node's rules: `force: false` and `errorOnExist` do not
109
+ * guarantee that destination links are preserved.
110
+ *
111
+ * Copying is neither exclusive nor atomic, and a failure can leave a partial
112
+ * copy. Use {@link Fs.copyFile} for exclusive creation of a single file.
113
+ */
114
+ readonly copy: (
115
+ source: FsPath,
116
+ destination: FsPath,
117
+ options?: FsCopyOptions,
118
+ ) => Task<void, FsError>;
119
+
120
+ /**
121
+ * Copies a single file. An existing destination fails with `AlreadyExists`
122
+ * unless `overwrite` is enabled. Without overwrite, destination creation is
123
+ * exclusive even when other copies run concurrently. File contents are not
124
+ * published atomically; a failure can leave a partial copy.
125
+ */
126
+ readonly copyFile: (
127
+ source: FsPath,
128
+ destination: FsPath,
129
+ options?: FsCopyFileOptions,
130
+ ) => Task<void, FsError>;
131
+
132
+ /**
133
+ * Renames or moves a file or directory using the platform's rename semantics.
134
+ * An existing destination file can be replaced.
135
+ */
136
+ readonly rename: (source: FsPath, destination: FsPath) => Task<void, FsError>;
137
+
138
+ /**
139
+ * Removes a file, or a directory with `recursive`. Removing a directory
140
+ * without `recursive` fails with `IsDirectory`. With `force`, a missing path
141
+ * succeeds.
142
+ */
143
+ readonly remove: (
144
+ path: FsPath,
145
+ options?: FsRemoveOptions,
146
+ ) => Task<void, FsError>;
147
+
148
+ /** Reads file metadata, following symbolic links. */
149
+ readonly getMetadata: (path: FsPath) => Task<FsMetadata, FsError>;
150
+
151
+ /**
152
+ * Checks whether a path exists. `NotFound` produces `false`; other errors are
153
+ * returned. A `true` result does not establish read or write permission.
154
+ */
155
+ readonly exists: (path: FsPath) => Task<boolean, FsError>;
156
+
157
+ /**
158
+ * Creates a unique directory in the system temporary directory by default.
159
+ * Options can specify a parent directory and a name prefix. The returned
160
+ * resource removes the directory and its contents on disposal. Use `await
161
+ * using` for cleanup; disposal can throw if removal fails.
162
+ *
163
+ * Once started, this operation returns its result even if its Run aborts.
164
+ */
165
+ readonly createTempDirectory: (
166
+ options?: FsCreateTempDirectoryOptions,
167
+ ) => Task<FsTempDirectory, FsError>;
168
+ }
169
+
170
+ /**
171
+ * Dependency wrapper for {@link Fs}.
172
+ *
173
+ * @group Core
174
+ */
175
+ export interface FsDep {
176
+ readonly fs: Fs;
177
+ }
178
+
179
+ /**
180
+ * A file system path, or a `file:` URL.
181
+ *
182
+ * @group Core
183
+ */
184
+ export type FsPath = string | URL;
185
+
186
+ /**
187
+ * Supported text encodings.
188
+ *
189
+ * @group Core
190
+ */
191
+ export type FsEncoding =
192
+ | "ascii"
193
+ | "utf8"
194
+ | "utf-8"
195
+ | "utf16le"
196
+ | "utf-16le"
197
+ | "ucs2"
198
+ | "ucs-2"
199
+ | "base64"
200
+ | "base64url"
201
+ | "latin1"
202
+ | "binary"
203
+ | "hex";
204
+
205
+ /**
206
+ * Supported file opening modes for {@link Fs.writeFile}.
207
+ *
208
+ * @group Core
209
+ */
210
+ export type FsOpenFlag =
211
+ | "a"
212
+ | "ax"
213
+ | "a+"
214
+ | "ax+"
215
+ | "as"
216
+ | "as+"
217
+ | "r"
218
+ | "r+"
219
+ | "rs+"
220
+ | "w"
221
+ | "wx"
222
+ | "w+"
223
+ | "wx+";
224
+
225
+ /**
226
+ * Reads bytes by default, or text when an encoding is specified.
227
+ *
228
+ * @group Core
229
+ */
230
+ export interface FsReadFile {
231
+ (path: FsPath): Task<Uint8Array, FsError>;
232
+ (
233
+ path: FsPath,
234
+ encoding: FsEncoding | { readonly encoding: FsEncoding },
235
+ ): Task<string, FsError>;
236
+ }
237
+
238
+ /**
239
+ * Options for {@link Fs.writeFile}.
240
+ *
241
+ * @group Options
242
+ */
243
+ export interface FsWriteFileOptions {
244
+ /** Encoding of string data. Defaults to `utf8`. */
245
+ readonly encoding?: FsEncoding;
246
+ /** File mode of a created file. Defaults to `0o666`. */
247
+ readonly mode?: number;
248
+ /** Open flag. Defaults to `w`; use `wx` to fail when the file exists. */
249
+ readonly flag?: FsOpenFlag;
250
+ }
251
+
252
+ /**
253
+ * Options for {@link Fs.readDirectory}.
254
+ *
255
+ * @group Options
256
+ */
257
+ export interface FsReadDirectoryOptions {
258
+ /** Includes entries from nested directories. Defaults to `false`. */
259
+ readonly recursive?: boolean;
260
+ }
261
+
262
+ /**
263
+ * Options for {@link Fs.createDirectory}.
264
+ *
265
+ * @group Options
266
+ */
267
+ export interface FsCreateDirectoryOptions {
268
+ /** Creates missing parents and accepts an existing directory. */
269
+ readonly recursive?: boolean;
270
+ /** Directory mode. Defaults to `0o777`. */
271
+ readonly mode?: number;
272
+ }
273
+
274
+ /**
275
+ * Options for {@link Fs.copy}.
276
+ *
277
+ * @group Options
278
+ */
279
+ export interface FsCopyOptions {
280
+ /**
281
+ * Node's `force` option. Replaces existing files; `false` skips them unless
282
+ * `errorOnExist` is enabled. Defaults to `true`. This does not protect
283
+ * destination symbolic links.
284
+ */
285
+ readonly force?: boolean;
286
+ /**
287
+ * Node's `errorOnExist` option. With `force: false`, existing files and
288
+ * directories fail with `AlreadyExists`. Defaults to `false`. Symbolic links
289
+ * retain Node's behavior and may still be replaced.
290
+ */
291
+ readonly errorOnExist?: boolean;
292
+ /** Preserves access and modification times. Defaults to `false`. */
293
+ readonly preserveTimestamps?: boolean;
294
+ }
295
+
296
+ /**
297
+ * Options for {@link Fs.copyFile}.
298
+ *
299
+ * @group Options
300
+ */
301
+ export interface FsCopyFileOptions {
302
+ /** Replaces an existing destination file. Defaults to `false`. */
303
+ readonly overwrite?: boolean;
304
+ }
305
+
306
+ /**
307
+ * Options for {@link Fs.remove}.
308
+ *
309
+ * @group Options
310
+ */
311
+ export interface FsRemoveOptions {
312
+ /** Removes directories and their contents. */
313
+ readonly recursive?: boolean;
314
+ /** Ignores a missing path. */
315
+ readonly force?: boolean;
316
+ /**
317
+ * Number of retries for `EBUSY`, `EMFILE`, `ENFILE`, `ENOTEMPTY`, or `EPERM`
318
+ * on Node.js. Applies only with `recursive: true`. Defaults to `0`.
319
+ */
320
+ readonly maxRetries?: number;
321
+ /**
322
+ * Base retry delay in milliseconds. Each retry waits one additional interval.
323
+ * Applies only with `recursive: true`. Defaults to `100`.
324
+ */
325
+ readonly retryDelay?: number;
326
+ }
327
+
328
+ /**
329
+ * File metadata as data, with Node's numeric and timestamp field names.
330
+ *
331
+ * @group Core
332
+ */
333
+ export interface FsMetadata {
334
+ readonly type: FsEntryType;
335
+ readonly dev: number;
336
+ readonly ino: number;
337
+ readonly mode: number;
338
+ readonly nlink: number;
339
+ readonly uid: number;
340
+ readonly gid: number;
341
+ readonly rdev: number;
342
+ readonly size: ByteLength;
343
+ readonly blksize: number;
344
+ readonly blocks: number;
345
+ readonly atimeMs: number;
346
+ readonly mtimeMs: number;
347
+ readonly ctimeMs: number;
348
+ readonly birthtimeMs: number;
349
+ readonly atime: Date;
350
+ readonly mtime: Date;
351
+ readonly ctime: Date;
352
+ readonly birthtime: Date;
353
+ }
354
+
355
+ /**
356
+ * The kind of file system entry described by {@link FsMetadata}.
357
+ *
358
+ * @group Core
359
+ */
360
+ export type FsEntryType =
361
+ | "File"
362
+ | "Directory"
363
+ | "SymbolicLink"
364
+ | "BlockDevice"
365
+ | "CharacterDevice"
366
+ | "FIFO"
367
+ | "Socket"
368
+ | "Unknown";
369
+
370
+ /**
371
+ * Options for {@link Fs.createTempDirectory}.
372
+ *
373
+ * @group Options
374
+ */
375
+ export interface FsCreateTempDirectoryOptions {
376
+ /** Existing parent directory. Defaults to the system temporary directory. */
377
+ readonly directory?: string;
378
+ /** Prefix for the directory name. Defaults to an empty string. */
379
+ readonly prefix?: string;
380
+ }
381
+
382
+ /**
383
+ * A temporary directory removed, with its contents, on asynchronous disposal.
384
+ *
385
+ * @group Core
386
+ */
387
+ export interface FsTempDirectory extends AsyncDisposable {
388
+ readonly path: string;
389
+ }
390
+
391
+ /**
392
+ * A failed file system operation.
393
+ *
394
+ * @group Errors
395
+ */
396
+ export interface FsError extends Typed<"FsError"> {
397
+ readonly reason: FsErrorReason;
398
+ /**
399
+ * The operation's path, or source for copying and renaming. URL inputs use
400
+ * their `href`. Temporary directories use the supplied parent if its
401
+ * resolution fails, or the resolved parent combined with the name prefix if
402
+ * creation fails.
403
+ */
404
+ readonly path: string;
405
+ /**
406
+ * Destination for copying and renaming, with URL inputs represented by
407
+ * `href`.
408
+ */
409
+ readonly destination?: string;
410
+ /** The failing system call, or the method name when the platform reports none. */
411
+ readonly syscall: string;
412
+ /** The platform's diagnostic message. */
413
+ readonly message: string;
414
+ }
415
+
416
+ /**
417
+ * Why a file system operation failed, mapped from the platform's error code.
418
+ *
419
+ * @group Errors
420
+ */
421
+ export type FsErrorReason =
422
+ | "NotFound"
423
+ | "AlreadyExists"
424
+ | "PermissionDenied"
425
+ | "IsDirectory"
426
+ | "NotDirectory"
427
+ | "NotEmpty"
428
+ | "Busy"
429
+ | "Unknown";
430
+
431
+ /**
432
+ * Creates a test {@link Fs} with the supplied operation overrides.
433
+ *
434
+ * Unconfigured operations throw a defect naming the method when their Task
435
+ * runs. Constructing a Task does not execute it. This helper performs no file
436
+ * system I/O; overrides provide the behavior needed by each test.
437
+ *
438
+ * ### Example
439
+ *
440
+ * ```ts
441
+ * import {
442
+ * assertEqual,
443
+ * assertOk,
444
+ * ok,
445
+ * testCreateFs,
446
+ * testCreateRun,
447
+ * type FsDep,
448
+ * type FsError,
449
+ * type Task,
450
+ * } from "@evolu/common";
451
+ *
452
+ * const saveMessage: Task<void, FsError, FsDep> = (run) =>
453
+ * run(run.deps.fs.writeFile("message.txt", "hello"));
454
+ *
455
+ * await using run = testCreateRun({
456
+ * fs: testCreateFs({
457
+ * writeFile: (path, data) => () => {
458
+ * assertEqual(path, "message.txt");
459
+ * assertEqual(data, "hello");
460
+ * return ok();
461
+ * },
462
+ * }),
463
+ * });
464
+ *
465
+ * assertOk(await run(saveMessage));
466
+ * ```
467
+ *
468
+ * @group Testing
469
+ */
470
+ export const testCreateFs = (overrides: Partial<Fs> = {}): Fs => ({
471
+ readFile: createUnexpectedFsOperation("readFile"),
472
+ writeFile: createUnexpectedFsOperation("writeFile"),
473
+ readDirectory: createUnexpectedFsOperation("readDirectory"),
474
+ createDirectory: createUnexpectedFsOperation("createDirectory"),
475
+ copy: createUnexpectedFsOperation("copy"),
476
+ copyFile: createUnexpectedFsOperation("copyFile"),
477
+ rename: createUnexpectedFsOperation("rename"),
478
+ remove: createUnexpectedFsOperation("remove"),
479
+ getMetadata: createUnexpectedFsOperation("getMetadata"),
480
+ exists: createUnexpectedFsOperation("exists"),
481
+ createTempDirectory: createUnexpectedFsOperation("createTempDirectory"),
482
+ ...overrides,
483
+ });
484
+
485
+ const createUnexpectedFsOperation =
486
+ (method: keyof Fs) => (): Task<never> => () => {
487
+ throw new Error(`Unexpected Fs.${method} call`);
488
+ };
package/src/Identicon.ts CHANGED
@@ -37,6 +37,7 @@ export type IdenticonStyle = "github" | "quadrant" | "gradient" | "sutnar";
37
37
  * assertTrue,
38
38
  * createIdFromString,
39
39
  * createIdenticon,
40
+ * testTodoId,
40
41
  * } from "@evolu/common";
41
42
  *
42
43
  * const id = createIdFromString("identicon-example");
@@ -47,8 +48,7 @@ export type IdenticonStyle = "github" | "quadrant" | "gradient" | "sutnar";
47
48
  * );
48
49
  *
49
50
  * // Branded IDs work too.
50
- * const todoId = createIdFromString<"Todo">("todo-1");
51
- * const todoSvg = createIdenticon(todoId);
51
+ * const todoSvg = createIdenticon(testTodoId);
52
52
  *
53
53
  * assertTrue(svg.startsWith("<svg"));
54
54
  * assertEqual(new Set([svg, ...alternativeSvgs]).size, 4);
@@ -22,6 +22,8 @@ import { constVoid } from "./Function.ts";
22
22
  * a warning may come late or, in short-lived processes, never. It is a
23
23
  * development canary, not a guarantee. Production uses
24
24
  * {@link noopLeakDetector}.
25
+ *
26
+ * @group Core
25
27
  */
26
28
  export interface LeakDetector {
27
29
  /**
@@ -38,7 +40,11 @@ export interface LeakDetector {
38
40
  readonly untrack: (unregisterToken: object) => void;
39
41
  }
40
42
 
41
- /** Describes a tracked handle for {@link LeakDetector.track}. */
43
+ /**
44
+ * Describes a tracked handle for {@link LeakDetector.track}.
45
+ *
46
+ * @group Core
47
+ */
42
48
  export interface Leak {
43
49
  /** Handle name used in the warning, for example `"Lease"`. */
44
50
  readonly name: string;
@@ -50,6 +56,7 @@ export interface Leak {
50
56
  /**
51
57
  * Dependency wrapper for {@link LeakDetector}.
52
58
  *
59
+ * @group Core
53
60
  * @see {@link LeakDetector}
54
61
  */
55
62
  export interface LeakDetectorDep {
@@ -61,6 +68,8 @@ export interface LeakDetectorDep {
61
68
  *
62
69
  * Capturing a stack per track call is too expensive for production; use
63
70
  * {@link noopLeakDetector} there.
71
+ *
72
+ * @group Core
64
73
  */
65
74
  export const createLeakDetector = (deps: ConsoleDep): LeakDetector => {
66
75
  if (typeof globalThis.FinalizationRegistry !== "function")
@@ -86,7 +95,11 @@ export const createLeakDetector = (deps: ConsoleDep): LeakDetector => {
86
95
  };
87
96
  };
88
97
 
89
- /** No-op {@link LeakDetector} for production. */
98
+ /**
99
+ * No-op {@link LeakDetector} for production.
100
+ *
101
+ * @group Core
102
+ */
90
103
  export const noopLeakDetector: LeakDetector = {
91
104
  track: constVoid,
92
105
  untrack: constVoid,
@@ -118,6 +131,7 @@ const reportLeak =
118
131
  /**
119
132
  * Test {@link LeakDetector} with deterministic collection.
120
133
  *
134
+ * @group Testing
121
135
  * @see {@link testCreateLeakDetector}
122
136
  */
123
137
  export interface TestLeakDetector extends LeakDetector {
@@ -136,13 +150,18 @@ export interface TestLeakDetector extends LeakDetector {
136
150
  /**
137
151
  * Dependency wrapper for {@link TestLeakDetector}.
138
152
  *
153
+ * @group Testing
139
154
  * @see {@link TestLeakDetector}
140
155
  */
141
156
  export interface TestLeakDetectorDep extends LeakDetectorDep {
142
157
  readonly leakDetector: TestLeakDetector;
143
158
  }
144
159
 
145
- /** Creates {@link TestLeakDetector}. */
160
+ /**
161
+ * Creates {@link TestLeakDetector}.
162
+ *
163
+ * @group Testing
164
+ */
146
165
  export const testCreateLeakDetector = (deps: ConsoleDep): TestLeakDetector => {
147
166
  const trackedLeaksByToken = new Map<object, ReadonlyArray<TrackedLeak>>();
148
167
  const report = reportLeak(deps);
@@ -47,6 +47,8 @@ import type { Callback } from "./Types.ts";
47
47
  * );
48
48
  * assertEqual(result, "example");
49
49
  * ```
50
+ *
51
+ * @group Core
50
52
  */
51
53
 
52
54
  export interface LockManagerDep {
@@ -65,6 +67,8 @@ export interface LockManagerDep {
65
67
  * via internal namespacing, so tests can reuse the same lock names without
66
68
  * contending through the global Web Locks. Query results are filtered to that
67
69
  * private namespace and returned with the original visible names.
70
+ *
71
+ * @group Testing
68
72
  */
69
73
  export const testCreateLockManager = (
70
74
  nativeLockManager: LockManager = navigator.locks,
@@ -119,6 +123,8 @@ export const testCreateLockManager = (
119
123
  * Leadership is held until the returned handle is disposed. Once released,
120
124
  * another waiting caller may become the next leader. Waiting for leadership is
121
125
  * abortable via the calling {@link Task}'s signal.
126
+ *
127
+ * @group Leader election
122
128
  */
123
129
  export const acquireLeaderLock =
124
130
  (name: string): Task<AsyncDisposable, never, LockManagerDep> =>
@@ -156,6 +162,8 @@ export const acquireLeaderLock =
156
162
  * Leadership is held until the returned handle is disposed. Once released,
157
163
  * another waiting caller may become the next leader. Waiting for leadership is
158
164
  * abortable by disposing the returned handle.
165
+ *
166
+ * @group Leader election
159
167
  */
160
168
  export const acquireLeaderLockCallback =
161
169
  (deps: LockManagerDep) =>