@livestore/utils 0.4.0-dev.9 → 0.5.0-dev.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 (316) hide show
  1. package/dist/.tsbuildinfo +1 -0
  2. package/dist/NoopTracer.d.ts +7 -8
  3. package/dist/NoopTracer.d.ts.map +1 -1
  4. package/dist/NoopTracer.js +13 -15
  5. package/dist/NoopTracer.js.map +1 -1
  6. package/dist/NoopTracer.test.d.ts +2 -0
  7. package/dist/NoopTracer.test.d.ts.map +1 -0
  8. package/dist/NoopTracer.test.js +17 -0
  9. package/dist/NoopTracer.test.js.map +1 -0
  10. package/dist/binary.js +1 -1
  11. package/dist/binary.js.map +1 -1
  12. package/dist/browser/Opfs/Opfs.d.ts +201 -0
  13. package/dist/browser/Opfs/Opfs.d.ts.map +1 -0
  14. package/dist/browser/Opfs/Opfs.js +170 -0
  15. package/dist/browser/Opfs/Opfs.js.map +1 -0
  16. package/dist/browser/Opfs/debug-utils.d.ts +20 -0
  17. package/dist/browser/Opfs/debug-utils.d.ts.map +1 -0
  18. package/dist/browser/Opfs/debug-utils.js +95 -0
  19. package/dist/browser/Opfs/debug-utils.js.map +1 -0
  20. package/dist/browser/Opfs/mod.d.ts +4 -0
  21. package/dist/browser/Opfs/mod.d.ts.map +1 -0
  22. package/dist/browser/Opfs/mod.js +4 -0
  23. package/dist/browser/Opfs/mod.js.map +1 -0
  24. package/dist/browser/Opfs/utils.d.ts +71 -0
  25. package/dist/browser/Opfs/utils.d.ts.map +1 -0
  26. package/dist/browser/Opfs/utils.js +227 -0
  27. package/dist/browser/Opfs/utils.js.map +1 -0
  28. package/dist/browser/QuotaExceededError.d.ts +60 -0
  29. package/dist/browser/QuotaExceededError.d.ts.map +1 -0
  30. package/dist/browser/QuotaExceededError.js +2 -0
  31. package/dist/browser/QuotaExceededError.js.map +1 -0
  32. package/dist/browser/WebChannelBrowser.d.ts +22 -0
  33. package/dist/browser/WebChannelBrowser.d.ts.map +1 -0
  34. package/dist/browser/WebChannelBrowser.js +87 -0
  35. package/dist/browser/WebChannelBrowser.js.map +1 -0
  36. package/dist/browser/WebError.d.ts +389 -0
  37. package/dist/browser/WebError.d.ts.map +1 -0
  38. package/dist/browser/WebError.js +423 -0
  39. package/dist/browser/WebError.js.map +1 -0
  40. package/dist/browser/WebError.test.d.ts +2 -0
  41. package/dist/browser/WebError.test.d.ts.map +1 -0
  42. package/dist/browser/WebError.test.js +46 -0
  43. package/dist/browser/WebError.test.js.map +1 -0
  44. package/dist/browser/WebLock.d.ts.map +1 -0
  45. package/dist/{effect → browser}/WebLock.js +21 -21
  46. package/dist/browser/WebLock.js.map +1 -0
  47. package/dist/{browser.d.ts → browser/detect.d.ts} +1 -1
  48. package/dist/browser/detect.d.ts.map +1 -0
  49. package/dist/{browser.js → browser/detect.js} +7 -7
  50. package/dist/browser/detect.js.map +1 -0
  51. package/dist/browser/mod.d.ts +8 -0
  52. package/dist/browser/mod.d.ts.map +1 -0
  53. package/dist/browser/mod.js +8 -0
  54. package/dist/browser/mod.js.map +1 -0
  55. package/dist/cuid/cuid.browser.js +1 -1
  56. package/dist/cuid/cuid.browser.js.map +1 -1
  57. package/dist/cuid/cuid.node.js +1 -1
  58. package/dist/cuid/cuid.node.js.map +1 -1
  59. package/dist/effect/Debug.d.ts +38 -0
  60. package/dist/effect/Debug.d.ts.map +1 -0
  61. package/dist/effect/Debug.js +350 -0
  62. package/dist/effect/Debug.js.map +1 -0
  63. package/dist/effect/Effect.d.ts +91 -22
  64. package/dist/effect/Effect.d.ts.map +1 -1
  65. package/dist/effect/Effect.js +126 -38
  66. package/dist/effect/Effect.js.map +1 -1
  67. package/dist/effect/Effect.test.d.ts +2 -0
  68. package/dist/effect/Effect.test.d.ts.map +1 -0
  69. package/dist/effect/Effect.test.js +72 -0
  70. package/dist/effect/Effect.test.js.map +1 -0
  71. package/dist/effect/Error.d.ts +4 -6
  72. package/dist/effect/Error.d.ts.map +1 -1
  73. package/dist/effect/Error.js +1 -1
  74. package/dist/effect/Error.js.map +1 -1
  75. package/dist/effect/Logger.d.ts +0 -6
  76. package/dist/effect/Logger.d.ts.map +1 -1
  77. package/dist/effect/Logger.js +0 -32
  78. package/dist/effect/Logger.js.map +1 -1
  79. package/dist/effect/OtelTracer.d.ts +1 -1
  80. package/dist/effect/OtelTracer.d.ts.map +1 -1
  81. package/dist/effect/OtelTracer.js +2 -3
  82. package/dist/effect/OtelTracer.js.map +1 -1
  83. package/dist/effect/RpcClient.d.ts +7 -7
  84. package/dist/effect/RpcClient.d.ts.map +1 -1
  85. package/dist/effect/RpcClient.js +74 -54
  86. package/dist/effect/RpcClient.js.map +1 -1
  87. package/dist/effect/Schedule.d.ts +1 -1
  88. package/dist/effect/Schedule.d.ts.map +1 -1
  89. package/dist/effect/Schedule.js +2 -2
  90. package/dist/effect/Schedule.js.map +1 -1
  91. package/dist/effect/Schema/debug-diff.d.ts +1 -1
  92. package/dist/effect/Schema/debug-diff.d.ts.map +1 -1
  93. package/dist/effect/Schema/debug-diff.js +9 -10
  94. package/dist/effect/Schema/debug-diff.js.map +1 -1
  95. package/dist/effect/Schema/debug-diff.test.js +7 -4
  96. package/dist/effect/Schema/debug-diff.test.js.map +1 -1
  97. package/dist/effect/Schema/index.d.ts +28 -18
  98. package/dist/effect/Schema/index.d.ts.map +1 -1
  99. package/dist/effect/Schema/index.js +55 -28
  100. package/dist/effect/Schema/index.js.map +1 -1
  101. package/dist/effect/Stream.d.ts +13 -5
  102. package/dist/effect/Stream.d.ts.map +1 -1
  103. package/dist/effect/Stream.js +19 -12
  104. package/dist/effect/Stream.js.map +1 -1
  105. package/dist/effect/Stream.test.js +12 -12
  106. package/dist/effect/Stream.test.js.map +1 -1
  107. package/dist/effect/Subscribable.d.ts +9 -3
  108. package/dist/effect/Subscribable.d.ts.map +1 -1
  109. package/dist/effect/Subscribable.js +20 -35
  110. package/dist/effect/Subscribable.js.map +1 -1
  111. package/dist/effect/SubscriptionRef.d.ts +5 -6
  112. package/dist/effect/SubscriptionRef.d.ts.map +1 -1
  113. package/dist/effect/SubscriptionRef.js +2 -3
  114. package/dist/effect/SubscriptionRef.js.map +1 -1
  115. package/dist/effect/TaskTracing.d.ts +1 -1
  116. package/dist/effect/TaskTracing.d.ts.map +1 -1
  117. package/dist/effect/TaskTracing.js +12 -16
  118. package/dist/effect/TaskTracing.js.map +1 -1
  119. package/dist/effect/WebChannel/WebChannel.d.ts +11 -30
  120. package/dist/effect/WebChannel/WebChannel.d.ts.map +1 -1
  121. package/dist/effect/WebChannel/WebChannel.js +70 -125
  122. package/dist/effect/WebChannel/WebChannel.js.map +1 -1
  123. package/dist/effect/WebChannel/WebChannel.test.js +16 -16
  124. package/dist/effect/WebChannel/WebChannel.test.js.map +1 -1
  125. package/dist/effect/WebChannel/broadcastChannelWithAck.d.ts.map +1 -1
  126. package/dist/effect/WebChannel/broadcastChannelWithAck.js +20 -13
  127. package/dist/effect/WebChannel/broadcastChannelWithAck.js.map +1 -1
  128. package/dist/effect/WebChannel/common.d.ts +17 -18
  129. package/dist/effect/WebChannel/common.d.ts.map +1 -1
  130. package/dist/effect/WebChannel/common.js +7 -7
  131. package/dist/effect/WebChannel/common.js.map +1 -1
  132. package/dist/effect/WebSocket.d.ts +4 -6
  133. package/dist/effect/WebSocket.d.ts.map +1 -1
  134. package/dist/effect/WebSocket.js +17 -17
  135. package/dist/effect/WebSocket.js.map +1 -1
  136. package/dist/effect/WebSocket.test.js +2 -2
  137. package/dist/effect/WebSocket.test.js.map +1 -1
  138. package/dist/effect/mod.browser-build.test.d.ts +2 -0
  139. package/dist/effect/mod.browser-build.test.d.ts.map +1 -0
  140. package/dist/effect/mod.browser-build.test.js +18 -0
  141. package/dist/effect/mod.browser-build.test.js.map +1 -0
  142. package/dist/effect/mod.d.ts +30 -0
  143. package/dist/effect/mod.d.ts.map +1 -0
  144. package/dist/effect/mod.js +30 -0
  145. package/dist/effect/mod.js.map +1 -0
  146. package/dist/effect/spanEvent.d.ts +12 -0
  147. package/dist/effect/spanEvent.d.ts.map +1 -0
  148. package/dist/effect/spanEvent.js +12 -0
  149. package/dist/effect/spanEvent.js.map +1 -0
  150. package/dist/effect/spanEvent.test.d.ts +2 -0
  151. package/dist/effect/spanEvent.test.d.ts.map +1 -0
  152. package/dist/effect/spanEvent.test.js +79 -0
  153. package/dist/effect/spanEvent.test.js.map +1 -0
  154. package/dist/env.d.ts.map +1 -1
  155. package/dist/env.js +1 -1
  156. package/dist/env.js.map +1 -1
  157. package/dist/fast-deep-equal.d.ts.map +1 -1
  158. package/dist/fast-deep-equal.js +9 -10
  159. package/dist/fast-deep-equal.js.map +1 -1
  160. package/dist/global.d.ts +3 -0
  161. package/dist/global.d.ts.map +1 -1
  162. package/dist/global.js.map +1 -1
  163. package/dist/misc.d.ts +9 -1
  164. package/dist/misc.d.ts.map +1 -1
  165. package/dist/misc.js +12 -4
  166. package/dist/misc.js.map +1 -1
  167. package/dist/mod.d.ts +177 -20
  168. package/dist/mod.d.ts.map +1 -1
  169. package/dist/mod.js +159 -22
  170. package/dist/mod.js.map +1 -1
  171. package/dist/node/mod.d.ts +45 -6
  172. package/dist/node/mod.d.ts.map +1 -1
  173. package/dist/node/mod.js +51 -9
  174. package/dist/node/mod.js.map +1 -1
  175. package/dist/object/index.d.ts +0 -9
  176. package/dist/object/index.d.ts.map +1 -1
  177. package/dist/object/index.js +0 -9
  178. package/dist/object/index.js.map +1 -1
  179. package/dist/object/stringify-object.js +2 -2
  180. package/dist/object/stringify-object.js.map +1 -1
  181. package/dist/object/stringify-object.test.js.map +1 -1
  182. package/dist/qr.d.ts +38 -0
  183. package/dist/qr.d.ts.map +1 -0
  184. package/dist/qr.js +109 -0
  185. package/dist/qr.js.map +1 -0
  186. package/dist/set.js +1 -1
  187. package/dist/set.js.map +1 -1
  188. package/dist/string.d.ts +0 -2
  189. package/dist/string.d.ts.map +1 -1
  190. package/dist/string.js +0 -2
  191. package/dist/string.js.map +1 -1
  192. package/package.json +61 -55
  193. package/src/NoopTracer.test.ts +20 -0
  194. package/src/NoopTracer.ts +28 -37
  195. package/src/binary.ts +1 -1
  196. package/src/browser/Opfs/Opfs.ts +463 -0
  197. package/src/browser/Opfs/debug-utils.ts +154 -0
  198. package/src/browser/Opfs/mod.ts +3 -0
  199. package/src/browser/Opfs/utils.ts +297 -0
  200. package/src/browser/QuotaExceededError.ts +60 -0
  201. package/src/browser/WebChannelBrowser.ts +145 -0
  202. package/src/browser/WebError.test.ts +66 -0
  203. package/src/browser/WebError.ts +587 -0
  204. package/src/{effect → browser}/WebLock.ts +27 -28
  205. package/src/{browser.ts → browser/detect.ts} +6 -6
  206. package/src/browser/mod.ts +8 -0
  207. package/src/cuid/cuid.browser.ts +1 -1
  208. package/src/cuid/cuid.node.ts +1 -1
  209. package/src/effect/Debug.ts +477 -0
  210. package/src/effect/Effect.test.ts +117 -0
  211. package/src/effect/Effect.ts +174 -79
  212. package/src/effect/Error.ts +1 -1
  213. package/src/effect/Logger.ts +0 -48
  214. package/src/effect/OtelTracer.ts +2 -3
  215. package/src/effect/RpcClient.ts +85 -65
  216. package/src/effect/Schedule.ts +4 -5
  217. package/src/effect/Schema/debug-diff.test.ts +7 -7
  218. package/src/effect/Schema/debug-diff.ts +11 -12
  219. package/src/effect/Schema/index.ts +121 -57
  220. package/src/effect/Stream.test.ts +14 -13
  221. package/src/effect/Stream.ts +23 -16
  222. package/src/effect/Subscribable.ts +31 -42
  223. package/src/effect/SubscriptionRef.ts +9 -13
  224. package/src/effect/TaskTracing.ts +13 -16
  225. package/src/effect/WebChannel/WebChannel.test.ts +24 -24
  226. package/src/effect/WebChannel/WebChannel.ts +102 -192
  227. package/src/effect/WebChannel/broadcastChannelWithAck.ts +25 -15
  228. package/src/effect/WebChannel/common.ts +16 -17
  229. package/src/effect/WebSocket.test.ts +2 -2
  230. package/src/effect/WebSocket.ts +20 -16
  231. package/src/effect/mod.browser-build.test.ts +19 -0
  232. package/src/effect/{index.ts → mod.ts} +43 -69
  233. package/src/effect/spanEvent.test.ts +92 -0
  234. package/src/effect/spanEvent.ts +17 -0
  235. package/src/env.ts +2 -1
  236. package/src/fast-deep-equal.ts +9 -10
  237. package/src/global.ts +4 -2
  238. package/src/misc.ts +13 -5
  239. package/src/mod.ts +188 -32
  240. package/src/node/mod.ts +57 -12
  241. package/src/object/index.ts +0 -24
  242. package/src/object/stringify-object.test.ts +1 -0
  243. package/src/object/stringify-object.ts +2 -2
  244. package/src/qr.ts +125 -0
  245. package/src/set.ts +1 -1
  246. package/src/string.ts +0 -3
  247. package/dist/.tsbuildinfo.json +0 -1
  248. package/dist/browser.d.ts.map +0 -1
  249. package/dist/browser.js.map +0 -1
  250. package/dist/effect/BucketQueue.d.ts +0 -20
  251. package/dist/effect/BucketQueue.d.ts.map +0 -1
  252. package/dist/effect/BucketQueue.js +0 -34
  253. package/dist/effect/BucketQueue.js.map +0 -1
  254. package/dist/effect/Scheduler.d.ts +0 -4
  255. package/dist/effect/Scheduler.d.ts.map +0 -1
  256. package/dist/effect/Scheduler.js +0 -9
  257. package/dist/effect/Scheduler.js.map +0 -1
  258. package/dist/effect/ServiceContext.d.ts +0 -37
  259. package/dist/effect/ServiceContext.d.ts.map +0 -1
  260. package/dist/effect/ServiceContext.js +0 -55
  261. package/dist/effect/ServiceContext.js.map +0 -1
  262. package/dist/effect/WebLock.d.ts.map +0 -1
  263. package/dist/effect/WebLock.js.map +0 -1
  264. package/dist/effect/index.d.ts +0 -32
  265. package/dist/effect/index.d.ts.map +0 -1
  266. package/dist/effect/index.js +0 -35
  267. package/dist/effect/index.js.map +0 -1
  268. package/dist/guards.d.ts +0 -6
  269. package/dist/guards.d.ts.map +0 -1
  270. package/dist/guards.js +0 -6
  271. package/dist/guards.js.map +0 -1
  272. package/dist/node/ChildProcessRunner/ChildProcessRunner.d.ts +0 -4
  273. package/dist/node/ChildProcessRunner/ChildProcessRunner.d.ts.map +0 -1
  274. package/dist/node/ChildProcessRunner/ChildProcessRunner.js +0 -122
  275. package/dist/node/ChildProcessRunner/ChildProcessRunner.js.map +0 -1
  276. package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/ChildProcessRunner.test.d.ts +0 -2
  277. package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/ChildProcessRunner.test.d.ts.map +0 -1
  278. package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/ChildProcessRunner.test.js +0 -218
  279. package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/ChildProcessRunner.test.js.map +0 -1
  280. package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/schema.d.ts +0 -84
  281. package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/schema.d.ts.map +0 -1
  282. package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/schema.js +0 -68
  283. package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/schema.js.map +0 -1
  284. package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/serializedWorker.d.ts +0 -2
  285. package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/serializedWorker.d.ts.map +0 -1
  286. package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/serializedWorker.js +0 -52
  287. package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/serializedWorker.js.map +0 -1
  288. package/dist/node/ChildProcessRunner/ChildProcessWorker.d.ts +0 -23
  289. package/dist/node/ChildProcessRunner/ChildProcessWorker.d.ts.map +0 -1
  290. package/dist/node/ChildProcessRunner/ChildProcessWorker.js +0 -138
  291. package/dist/node/ChildProcessRunner/ChildProcessWorker.js.map +0 -1
  292. package/dist/object/omit.d.ts +0 -3
  293. package/dist/object/omit.d.ts.map +0 -1
  294. package/dist/object/omit.js +0 -14
  295. package/dist/object/omit.js.map +0 -1
  296. package/dist/object/pick.d.ts +0 -14
  297. package/dist/object/pick.d.ts.map +0 -1
  298. package/dist/object/pick.js +0 -17
  299. package/dist/object/pick.js.map +0 -1
  300. package/dist/time.d.ts +0 -12
  301. package/dist/time.d.ts.map +0 -1
  302. package/dist/time.js +0 -22
  303. package/dist/time.js.map +0 -1
  304. package/src/effect/BucketQueue.ts +0 -49
  305. package/src/effect/Scheduler.ts +0 -13
  306. package/src/effect/ServiceContext.ts +0 -108
  307. package/src/guards.ts +0 -8
  308. package/src/node/ChildProcessRunner/ChildProcessRunner.ts +0 -135
  309. package/src/node/ChildProcessRunner/ChildProcessRunnerTest/ChildProcessRunner.test.ts +0 -302
  310. package/src/node/ChildProcessRunner/ChildProcessRunnerTest/schema.ts +0 -78
  311. package/src/node/ChildProcessRunner/ChildProcessRunnerTest/serializedWorker.ts +0 -66
  312. package/src/node/ChildProcessRunner/ChildProcessWorker.ts +0 -168
  313. package/src/object/omit.ts +0 -17
  314. package/src/object/pick.ts +0 -27
  315. package/src/time.ts +0 -25
  316. /package/dist/{effect → browser}/WebLock.d.ts +0 -0
@@ -0,0 +1,463 @@
1
+ /// <reference lib="webworker" />
2
+
3
+ import { Context, Effect, Layer, Option, Schema, type Scope, Stream } from 'effect'
4
+
5
+ import * as WebError from '../WebError.ts'
6
+
7
+ /**
8
+ * Effect service that exposes ergonomic wrappers around Origin Private File System (OPFS) operations.
9
+ *
10
+ * @remarks
11
+ * - Helpers mirror the File System Access API where possible and parse web exceptions into Effect errors.
12
+ * - Sync access handle helpers can only be used in dedicated workers; invoking them in other contexts fails at runtime.
13
+ *
14
+ * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Origin_private_file_system | MDN Reference}
15
+ */
16
+ export interface Service {
17
+ /**
18
+ * Acquire the OPFS root directory handle.
19
+ *
20
+ * @returns Root directory handle for the current origin.
21
+ *
22
+ * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/StorageManager/getDirectory | MDN Reference}
23
+ */
24
+ readonly getRootDirectoryHandle: Effect.Effect<FileSystemDirectoryHandle, WebError.WebError>
25
+ /**
26
+ * Resolve (and optionally create) a file handle relative to a directory.
27
+ *
28
+ * @param parent - Directory to search.
29
+ * @param name - Target file name.
30
+ * @param options - Forwarded `getFileHandle` options such as `{ create: true }`.
31
+ *
32
+ * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/FileSystemDirectoryHandle/getFileHandle | MDN Reference}
33
+ */
34
+ readonly getFileHandle: (
35
+ parent: FileSystemDirectoryHandle,
36
+ name: string,
37
+ options?: FileSystemGetFileOptions,
38
+ ) => Effect.Effect<FileSystemFileHandle, WebError.WebError>
39
+ /**
40
+ * Resolve (and optionally create) a directory handle relative to another directory.
41
+ *
42
+ * @param parent - Directory to search.
43
+ * @param name - Target directory name.
44
+ * @param options - Forwarded `getDirectoryHandle` options such as `{ create: true }`.
45
+ *
46
+ * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/FileSystemDirectoryHandle/getDirectoryHandle | MDN Reference}
47
+ */
48
+ readonly getDirectoryHandle: (
49
+ parent: FileSystemDirectoryHandle,
50
+ name: string,
51
+ options?: FileSystemGetDirectoryOptions,
52
+ ) => Effect.Effect<FileSystemDirectoryHandle, WebError.WebError>
53
+ /**
54
+ * Remove a file-system entry (file or directory) from its parent directory.
55
+ *
56
+ * @param parent - Directory containing the entry.
57
+ * @param name - Entry name.
58
+ * @param options - Removal behavior (for example `{ recursive: true }`).
59
+ *
60
+ * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/FileSystemDirectoryHandle/removeEntry | MDN Reference}
61
+ */
62
+ readonly removeEntry: (
63
+ parent: FileSystemDirectoryHandle,
64
+ name: string,
65
+ options?: FileSystemRemoveOptions,
66
+ ) => Effect.Effect<void, WebError.WebError>
67
+ /**
68
+ * Return a stream of child file-system handles for a directory.
69
+ *
70
+ * @param directory - Directory whose children are to be streamed
71
+ * @returns `Stream` of `FileSystemHandle`
72
+ *
73
+ * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/FileSystemDirectoryHandle/values | MDN Reference}
74
+ */
75
+ readonly values: (
76
+ directory: FileSystemDirectoryHandle,
77
+ ) => Stream.Stream<FileSystemHandle, WebError.NotAllowedError | WebError.NotFoundError | WebError.UnknownError>
78
+ /**
79
+ * Resolve the relative path from a parent directory to a descendant handle.
80
+ *
81
+ * @param parent - Reference directory.
82
+ * @param child - File or directory handle within the parent hierarchy.
83
+ * @returns `Option.some(pathSegments)` when reachable, otherwise `Option.none()`.
84
+ *
85
+ * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/FileSystemDirectoryHandle/resolve | MDN Reference}
86
+ */
87
+ readonly resolve: (
88
+ parent: FileSystemDirectoryHandle,
89
+ child: FileSystemHandle,
90
+ ) => Effect.Effect<Option.Option<ReadonlyArray<string>>, WebError.WebError>
91
+ /**
92
+ * Read the underlying `File` for a file handle.
93
+ *
94
+ * @param handle - Handle referencing the target file.
95
+ *
96
+ * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/FileSystemFileHandle/getFile | MDN Reference}
97
+ */
98
+ readonly getFile: (handle: FileSystemFileHandle) => Effect.Effect<File, WebError.WebError>
99
+ /**
100
+ * Overwrite the contents of a file with the provided data.
101
+ *
102
+ * @param handle - File to write to.
103
+ * @param data - Chunk(s) accepted by `FileSystemWritableFileStream.write`.
104
+ * @param options - Stream creation options (for example, `{ keepExistingData: false }`).
105
+ *
106
+ * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/FileSystemFileHandle/createWritable | MDN Reference}
107
+ */
108
+ readonly writeFile: (
109
+ handle: FileSystemFileHandle,
110
+ data: FileSystemWriteChunkType,
111
+ options?: FileSystemCreateWritableOptions,
112
+ ) => Effect.Effect<void, WebError.WebError>
113
+ /**
114
+ * Append data to the end of an existing file.
115
+ *
116
+ * @param handle - File to extend.
117
+ * @param data - Data to append.
118
+ *
119
+ * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/FileSystemWritableFileStream/write | MDN Reference}
120
+ */
121
+ readonly appendToFile: (
122
+ handle: FileSystemFileHandle,
123
+ data: FileSystemWriteChunkType,
124
+ ) => Effect.Effect<void, WebError.WebError>
125
+ /**
126
+ * Truncate a file to the specified size in bytes.
127
+ *
128
+ * @param handle - File to shrink or pad.
129
+ * @param size - Target byte length.
130
+ *
131
+ * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/FileSystemWritableFileStream/truncate | MDN Reference}
132
+ */
133
+ readonly truncateFile: (handle: FileSystemFileHandle, size: number) => Effect.Effect<void, WebError.WebError>
134
+ /**
135
+ * Create a synchronous access handle for a file.
136
+ *
137
+ * @param handle - File handle to open.
138
+ * @returns A managed handle that is automatically closed when released.
139
+ *
140
+ * @remarks
141
+ * - Only available in Dedicated Web Workers.
142
+ * - This method is asynchronous even though the `FileSystemSyncAccessHandle` APIs are synchronous.
143
+ *
144
+ * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/FileSystemFileHandle/createSyncAccessHandle | MDN Reference}
145
+ */
146
+ readonly createSyncAccessHandle: (
147
+ handle: FileSystemFileHandle,
148
+ ) => Effect.Effect<FileSystemSyncAccessHandle, WebError.WebError, Scope.Scope>
149
+ /**
150
+ * Perform a synchronous read into the provided buffer from a sync access handle.
151
+ *
152
+ * @param handle - Sync access handle to read from.
153
+ * @param buffer - Destination buffer (can be a specific view like Uint8Array).
154
+ * @param options - Read position options.
155
+ * @returns Number of bytes read.
156
+ *
157
+ * @remarks
158
+ * Only available in Dedicated Web Workers.
159
+ *
160
+ * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/FileSystemSyncAccessHandle/read | MDN Reference}
161
+ */
162
+ readonly syncRead: (
163
+ handle: FileSystemSyncAccessHandle,
164
+ buffer: ArrayBuffer | ArrayBufferView,
165
+ options?: FileSystemReadWriteOptions,
166
+ ) => Effect.Effect<number, WebError.WebError>
167
+ /**
168
+ * Perform a synchronous write from the provided buffer into the file.
169
+ *
170
+ * @param handle - Sync access handle to write to.
171
+ * @param buffer - Source data.
172
+ * @param options - Write position options.
173
+ * @returns Number of bytes written.
174
+ *
175
+ * @remarks
176
+ * Only available in Dedicated Web Workers.
177
+ *
178
+ * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/FileSystemSyncAccessHandle/write | MDN Reference}
179
+ */
180
+ readonly syncWrite: (
181
+ handle: FileSystemSyncAccessHandle,
182
+ buffer: AllowSharedBufferSource,
183
+ options?: FileSystemReadWriteOptions,
184
+ ) => Effect.Effect<number, WebError.WebError>
185
+ /**
186
+ * Truncate the file associated with a sync access handle to the specified size.
187
+ *
188
+ * @param handle - Sync access handle to mutate.
189
+ * @param size - Desired byte length.
190
+ *
191
+ * @remarks
192
+ * Only available in Dedicated Web Workers.
193
+ *
194
+ * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/FileSystemSyncAccessHandle/truncate | MDN Reference}
195
+ */
196
+ readonly syncTruncate: (handle: FileSystemSyncAccessHandle, size: number) => Effect.Effect<void, WebError.WebError>
197
+ /**
198
+ * Retrieve the current size of a file via its sync access handle.
199
+ *
200
+ * @param handle - Sync access handle.
201
+ * @returns File size in bytes.
202
+ *
203
+ * @remarks
204
+ * Only available in Dedicated Web Workers.
205
+ *
206
+ * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/FileSystemSyncAccessHandle/getSize | MDN Reference}
207
+ */
208
+ readonly syncGetSize: (handle: FileSystemSyncAccessHandle) => Effect.Effect<number, WebError.WebError>
209
+ /**
210
+ * Flush pending synchronous writes to durable storage.
211
+ *
212
+ * @param handle - Sync access handle to flush.
213
+ *
214
+ * @remarks
215
+ * Only available in Dedicated Web Workers.
216
+ *
217
+ * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/FileSystemSyncAccessHandle/flush | MDN Reference}
218
+ */
219
+ readonly syncFlush: (handle: FileSystemSyncAccessHandle) => Effect.Effect<void, WebError.WebError>
220
+ }
221
+
222
+ export class Opfs extends Context.Service<Opfs, Service>()('@livestore/utils/Opfs') {}
223
+
224
+ export const layer = Layer.succeed(
225
+ Opfs,
226
+ Opfs.of({
227
+ getRootDirectoryHandle: Effect.tryPromise({
228
+ try: () => navigator.storage.getDirectory(),
229
+ catch: (u) => WebError.classifyWebError(u, [WebError.SecurityError]),
230
+ }),
231
+ getFileHandle: (parent: FileSystemDirectoryHandle, name: string, options?: FileSystemGetFileOptions) =>
232
+ Effect.tryPromise({
233
+ try: () => parent.getFileHandle(name, options),
234
+ catch: (u) =>
235
+ WebError.classifyWebError(u, [
236
+ WebError.NotAllowedError,
237
+ WebError.TypeError,
238
+ WebError.TypeMismatchError,
239
+ WebError.NotFoundError,
240
+ ]),
241
+ }),
242
+ getDirectoryHandle: (parent: FileSystemDirectoryHandle, name: string, options?: FileSystemGetDirectoryOptions) =>
243
+ Effect.tryPromise({
244
+ try: () => parent.getDirectoryHandle(name, options),
245
+ catch: (u) =>
246
+ WebError.classifyWebError(u, [
247
+ WebError.NotAllowedError,
248
+ WebError.TypeError,
249
+ WebError.TypeMismatchError,
250
+ WebError.NotFoundError,
251
+ ]),
252
+ }),
253
+ removeEntry: (parent: FileSystemDirectoryHandle, name: string, options?: FileSystemRemoveOptions) =>
254
+ Effect.tryPromise({
255
+ try: () => parent.removeEntry(name, options),
256
+ catch: (u) =>
257
+ WebError.classifyWebError(u, [
258
+ WebError.TypeError,
259
+ WebError.NotAllowedError,
260
+ WebError.InvalidModificationError,
261
+ WebError.NotFoundError,
262
+ WebError.NoModificationAllowedError,
263
+ ]),
264
+ }),
265
+ values: (
266
+ directory: FileSystemDirectoryHandle,
267
+ ): Stream.Stream<
268
+ FileSystemHandle,
269
+ WebError.NotAllowedError | WebError.NotFoundError | WebError.UnknownError,
270
+ never
271
+ > =>
272
+ Stream.fromAsyncIterable(directory.values(), (u) =>
273
+ WebError.classifyWebError(u, [WebError.NotAllowedError, WebError.NotFoundError]),
274
+ ),
275
+ resolve: (parent: FileSystemDirectoryHandle, child: FileSystemHandle) =>
276
+ Effect.tryPromise({
277
+ try: () => parent.resolve(child),
278
+ catch: (u) => WebError.classifyWebError(u),
279
+ }).pipe(Effect.map((path) => (path === null ? Option.none() : Option.some(path)))),
280
+ getFile: (handle: FileSystemFileHandle) =>
281
+ Effect.tryPromise({
282
+ try: () => handle.getFile(),
283
+ catch: (u) => WebError.classifyWebError(u, [WebError.NotAllowedError, WebError.NotFoundError]),
284
+ }),
285
+ writeFile: (
286
+ handle: FileSystemFileHandle,
287
+ data: FileSystemWriteChunkType,
288
+ options?: FileSystemCreateWritableOptions,
289
+ ) =>
290
+ Effect.acquireUseRelease(
291
+ Effect.tryPromise({
292
+ try: () => handle.createWritable(options),
293
+ catch: (u) =>
294
+ WebError.classifyWebError(u, [
295
+ WebError.NotAllowedError,
296
+ WebError.NotFoundError,
297
+ WebError.NoModificationAllowedError,
298
+ WebError.AbortError,
299
+ ]),
300
+ }),
301
+ (stream) =>
302
+ Effect.tryPromise({
303
+ try: () => stream.write(data),
304
+ catch: (u) =>
305
+ WebError.classifyWebError(u, [WebError.NotAllowedError, WebError.QuotaExceededError, WebError.TypeError]),
306
+ }),
307
+ (stream) =>
308
+ Effect.tryPromise({
309
+ try: () => stream.close(),
310
+ catch: (u) => WebError.classifyWebError(u, [WebError.TypeError]),
311
+ }).pipe(Effect.catchCause(() => Effect.void)),
312
+ ),
313
+ appendToFile: (handle: FileSystemFileHandle, data: FileSystemWriteChunkType) =>
314
+ Effect.acquireUseRelease(
315
+ Effect.tryPromise({
316
+ try: () => handle.createWritable({ keepExistingData: true }),
317
+ catch: (u) =>
318
+ WebError.classifyWebError(u, [
319
+ WebError.NotAllowedError,
320
+ WebError.NotFoundError,
321
+ WebError.NoModificationAllowedError,
322
+ WebError.AbortError,
323
+ ]),
324
+ }),
325
+ (stream) =>
326
+ Effect.gen(function* () {
327
+ const file = yield* Effect.tryPromise({
328
+ try: () => handle.getFile(),
329
+ catch: (u) => WebError.classifyWebError(u, [WebError.NotAllowedError, WebError.NotFoundError]),
330
+ })
331
+ yield* Effect.tryPromise({
332
+ try: () => stream.seek(file.size),
333
+ catch: (u) => WebError.classifyWebError(u, [WebError.NotAllowedError, WebError.TypeError]),
334
+ })
335
+ yield* Effect.tryPromise({
336
+ try: () => stream.write(data),
337
+ catch: (u) =>
338
+ WebError.classifyWebError(u, [
339
+ WebError.NotAllowedError,
340
+ WebError.QuotaExceededError,
341
+ WebError.TypeError,
342
+ ]),
343
+ })
344
+ }),
345
+ (stream) =>
346
+ Effect.tryPromise({
347
+ try: () => stream.close(),
348
+ catch: (u) => WebError.classifyWebError(u, [WebError.TypeError]),
349
+ }).pipe(Effect.catchCause(() => Effect.void)),
350
+ ),
351
+ truncateFile: (handle: FileSystemFileHandle, size: number) =>
352
+ Effect.acquireUseRelease(
353
+ Effect.tryPromise({
354
+ try: () => handle.createWritable({ keepExistingData: true }),
355
+ catch: (u) =>
356
+ WebError.classifyWebError(u, [
357
+ WebError.NotAllowedError,
358
+ WebError.NotFoundError,
359
+ WebError.NoModificationAllowedError,
360
+ WebError.AbortError,
361
+ ]),
362
+ }),
363
+ (stream) =>
364
+ Effect.tryPromise({
365
+ try: () => stream.truncate(size),
366
+ catch: (u) =>
367
+ WebError.classifyWebError(u, [WebError.NotAllowedError, WebError.TypeError, WebError.QuotaExceededError]),
368
+ }),
369
+ (stream) =>
370
+ Effect.tryPromise({
371
+ try: () => stream.close(),
372
+ catch: (u) => WebError.classifyWebError(u, [WebError.TypeError]),
373
+ }).pipe(Effect.catchCause(() => Effect.void)),
374
+ ),
375
+ createSyncAccessHandle: (handle: FileSystemFileHandle) =>
376
+ Effect.acquireRelease(
377
+ Effect.tryPromise({
378
+ try: () => handle.createSyncAccessHandle(),
379
+ catch: (u) =>
380
+ WebError.classifyWebError(u, [
381
+ WebError.NotAllowedError,
382
+ WebError.InvalidStateError,
383
+ WebError.NotFoundError,
384
+ WebError.NoModificationAllowedError,
385
+ ]),
386
+ }),
387
+ (syncHandle) => Effect.sync(() => syncHandle.close()),
388
+ ),
389
+ syncRead: (
390
+ handle: FileSystemSyncAccessHandle,
391
+ buffer: ArrayBuffer | ArrayBufferView,
392
+ options?: FileSystemReadWriteOptions,
393
+ ) =>
394
+ Effect.try({
395
+ try: () => handle.read(buffer, options),
396
+ catch: (u) =>
397
+ WebError.classifyWebError(u, [WebError.RangeError, WebError.InvalidStateError, WebError.TypeError]),
398
+ }),
399
+ syncWrite: (
400
+ handle: FileSystemSyncAccessHandle,
401
+ buffer: AllowSharedBufferSource,
402
+ options?: FileSystemReadWriteOptions,
403
+ ) =>
404
+ Effect.try({
405
+ try: () => handle.write(buffer, options),
406
+ catch: (u) => WebError.classifyWebError(u),
407
+ }),
408
+ syncTruncate: (handle: FileSystemSyncAccessHandle, size: number) =>
409
+ Effect.try({
410
+ try: () => handle.truncate(size),
411
+ catch: (u) => WebError.classifyWebError(u),
412
+ }),
413
+ syncGetSize: (handle: FileSystemSyncAccessHandle) =>
414
+ Effect.try({
415
+ try: () => handle.getSize(),
416
+ catch: (u) => WebError.classifyWebError(u),
417
+ }),
418
+ syncFlush: (handle: FileSystemSyncAccessHandle) =>
419
+ Effect.try({
420
+ try: () => handle.flush(),
421
+ catch: (u) => WebError.classifyWebError(u),
422
+ }),
423
+ }),
424
+ )
425
+
426
+ const notFoundError = new WebError.NotFoundError({
427
+ cause: new DOMException('The object can not be found here.', 'NotFoundError'),
428
+ })
429
+
430
+ const unknownError = (message: string) => new WebError.UnknownError({ description: message })
431
+
432
+ /**
433
+ * A no-op Opfs service that can be used for testing.
434
+ */
435
+ export const layerNoop = Layer.succeed(
436
+ Opfs,
437
+ Opfs.of({
438
+ getRootDirectoryHandle: Effect.fail(unknownError('OPFS is not supported in this environment')),
439
+ getFileHandle: () => Effect.fail(notFoundError),
440
+ getDirectoryHandle: () => Effect.fail(notFoundError),
441
+ removeEntry: () => Effect.fail(notFoundError),
442
+ values: () => Stream.fail(notFoundError),
443
+ resolve: () => Effect.succeed(Option.none()),
444
+ getFile: () => Effect.fail(notFoundError),
445
+ writeFile: () => Effect.fail(notFoundError),
446
+ appendToFile: () => Effect.fail(notFoundError),
447
+ truncateFile: () => Effect.fail(notFoundError),
448
+ createSyncAccessHandle: () => Effect.fail(unknownError('OPFS is not supported in this environment')),
449
+ syncRead: () => Effect.fail(unknownError('OPFS is not supported in this environment')),
450
+ syncWrite: () => Effect.fail(unknownError('OPFS is not supported in this environment')),
451
+ syncTruncate: () => Effect.fail(unknownError('OPFS is not supported in this environment')),
452
+ syncGetSize: () => Effect.fail(unknownError('OPFS is not supported in this environment')),
453
+ syncFlush: () => Effect.fail(unknownError('OPFS is not supported in this environment')),
454
+ }),
455
+ )
456
+
457
+ /**
458
+ * Error raised when OPFS operations fail.
459
+ */
460
+ export class OpfsError extends Schema.TaggedError<OpfsError>('~@livestore/utils/OpfsError')('OpfsError', {
461
+ message: Schema.String,
462
+ cause: Schema.optional(Schema.Defect()),
463
+ }) {}
@@ -0,0 +1,154 @@
1
+ /**
2
+ * Debug utilities for OPFS (Origin Private File System) inspection and manipulation.
3
+ * These functions are designed for use in browser devtools console to help debug
4
+ * and inspect the OPFS structure during development.
5
+ */
6
+
7
+ import { Effect, Stream } from 'effect'
8
+ import prettyBytes from 'pretty-bytes'
9
+
10
+ import type * as WebError from '../WebError.ts'
11
+ import * as Opfs from './Opfs.ts'
12
+ import { getDirectoryHandleByPath, getMetadata, remove } from './utils.ts'
13
+
14
+ /**
15
+ * Metadata exposed directly on OPFS handles so we avoid `getFile()` reads.
16
+ */
17
+ interface OpfsEntryMetadata {
18
+ readonly name: string
19
+ readonly path: string
20
+ readonly kind: FileSystemHandleKind
21
+ readonly size?: number
22
+ readonly lastModified?: number
23
+ }
24
+
25
+ interface OpfsTreeNode {
26
+ readonly metadata: OpfsEntryMetadata
27
+ readonly children?: ReadonlyArray<OpfsTreeNode>
28
+ }
29
+
30
+ const ROOT_NAME = '/'
31
+
32
+ /**
33
+ * Materialize the entire OPFS tree starting from the origin root.
34
+ */
35
+ const buildTree = Effect.fn('@livestore/utils:Opfs.buildTree')(function* () {
36
+ const opfs = yield* Opfs.Opfs
37
+ const rootHandle = yield* opfs.getRootDirectoryHandle
38
+
39
+ const collectDirectory = (
40
+ handle: FileSystemDirectoryHandle,
41
+ pathSegments: ReadonlyArray<string>,
42
+ ): Effect.Effect<OpfsTreeNode, WebError.WebError, Opfs.Opfs> =>
43
+ Effect.gen(function* () {
44
+ const handlesStream = opfs.values(handle)
45
+ const handles = yield* handlesStream.pipe(
46
+ Stream.runCollect,
47
+ Effect.map((chunk) => Array.from(chunk).sort((a, b) => a.name.localeCompare(b.name))),
48
+ )
49
+
50
+ const children = yield* Effect.forEach(
51
+ handles,
52
+ (childHandle) =>
53
+ Effect.gen(function* () {
54
+ const nextSegments = [...pathSegments, childHandle.name]
55
+ const path = formatPath(nextSegments)
56
+
57
+ if (childHandle.kind === 'directory') {
58
+ return yield* collectDirectory(childHandle as FileSystemDirectoryHandle, nextSegments)
59
+ }
60
+
61
+ const metadata = yield* getMetadata(childHandle as FileSystemFileHandle)
62
+
63
+ return {
64
+ metadata: {
65
+ name: childHandle.name,
66
+ path,
67
+ kind: 'file',
68
+ size: metadata.size,
69
+ lastModified: metadata.lastModified,
70
+ },
71
+ } satisfies OpfsTreeNode
72
+ }),
73
+ { concurrency: 'unbounded' },
74
+ )
75
+
76
+ return {
77
+ metadata: {
78
+ name: pathSegments.length === 0 ? ROOT_NAME : pathSegments[pathSegments.length - 1]!,
79
+ path: formatPath(pathSegments),
80
+ kind: 'directory',
81
+ },
82
+ children,
83
+ }
84
+ })
85
+
86
+ return yield* collectDirectory(rootHandle, [])
87
+ })
88
+
89
+ const formatPath = (segments: ReadonlyArray<string>) => (segments.length === 0 ? ROOT_NAME : `/${segments.join('/')}`)
90
+
91
+ const formatLabel = ({ name, kind, size, lastModified }: OpfsEntryMetadata) => {
92
+ let label = name
93
+
94
+ if (kind === 'file' && lastModified !== undefined) {
95
+ const date = new Date(lastModified)
96
+ label += ` │ ${date.toISOString().split('T')[0]} ${date.toTimeString().split(' ')[0]}`
97
+
98
+ if (size !== undefined) {
99
+ label += ` │ ${prettyBytes(size)}`
100
+ }
101
+ }
102
+
103
+ return label
104
+ }
105
+
106
+ const logAsciiTree = (node: OpfsTreeNode): Effect.Effect<void> =>
107
+ logAsciiNode(node, { prefix: '', isLast: true, isRoot: true })
108
+
109
+ const logAsciiNode: (
110
+ node: OpfsTreeNode,
111
+ options: { readonly prefix: string; readonly isLast: boolean; readonly isRoot?: boolean },
112
+ ) => Effect.Effect<void> = (node, options) =>
113
+ Effect.gen(function* () {
114
+ const label = formatLabel(node.metadata)
115
+ const branch = options.isRoot === true ? '' : `${options.prefix}${options.isLast === true ? '└── ' : '├── '}`
116
+ const nextPrefix = options.isRoot === true ? '' : `${options.prefix}${options.isLast === true ? ' ' : '│ '}`
117
+
118
+ console.log(`${branch}${label}`)
119
+
120
+ if (node.children === undefined || node.children.length === 0) return
121
+
122
+ for (let index = 0; index < node.children.length; index++) {
123
+ const child = node.children[index]!
124
+ const isLastChild = index === node.children.length - 1
125
+ yield* logAsciiNode(child, { prefix: nextPrefix, isLast: isLastChild })
126
+ }
127
+ })
128
+
129
+ const printTree = Effect.gen(function* () {
130
+ const tree = yield* buildTree()
131
+ yield* logAsciiTree(tree)
132
+ })
133
+
134
+ const resetTree = remove('/')
135
+
136
+ const getDirHandle = (path: string, options?: FileSystemGetDirectoryOptions) => getDirectoryHandleByPath(path, options)
137
+
138
+ const runOpfsEffect = <A, E>(effect: Effect.Effect<A, E, Opfs.Opfs>) =>
139
+ effect.pipe(Effect.provide(Opfs.layer), Effect.runPromise)
140
+
141
+ export const debugUtils = {
142
+ /**
143
+ * Print the entire OPFS tree structure to the console in an ASCII format.
144
+ */
145
+ printTree: (): Promise<void> => runOpfsEffect(printTree),
146
+ /**
147
+ * Reset the entire OPFS tree by removing all files and directories.
148
+ */
149
+ resetTree: (): Promise<void> => runOpfsEffect(resetTree),
150
+ /**
151
+ * Get a directory handle for a given path, useful for inspecting or manipulating specific directories.
152
+ */
153
+ getDirHandle: (path: string, options?: FileSystemGetDirectoryOptions) => runOpfsEffect(getDirHandle(path, options)),
154
+ } as const
@@ -0,0 +1,3 @@
1
+ export * from './debug-utils.ts'
2
+ export * from './Opfs.ts'
3
+ export * from './utils.ts'