@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
package/src/mod.ts CHANGED
@@ -1,67 +1,109 @@
1
1
  export { default as prettyBytes } from 'pretty-bytes'
2
2
  export * as base64 from './base64.ts'
3
3
  export * from './binary.ts'
4
- export * from './browser.ts'
5
4
  export * from './Deferred.ts'
6
5
  export * from './env.ts'
7
6
  export * from './fast-deep-equal.ts'
8
- export * from './guards.ts'
9
7
  export * from './misc.ts'
10
8
  export * from './NoopTracer.ts'
11
9
  export * from './object/index.ts'
12
10
  export * from './promise.ts'
11
+ export * as QR from './qr.ts'
13
12
  export * from './set.ts'
14
13
  export * from './string.ts'
15
- export * from './time.ts'
16
14
 
17
15
  import type * as otel from '@opentelemetry/api'
18
16
  import type { Types } from 'effect'
19
17
 
20
18
  import { objectToString } from './misc.ts'
21
19
 
22
- export type Prettify<T> = T extends infer U ? { [K in keyof U]: Prettify<U[K]> } : never
23
-
24
- export type TypeEq<A, B> = (<T>() => T extends A ? 1 : 2) extends <T>() => T extends B ? 1 : 2 ? true : false
25
-
26
- /** `A` is subtype of `B` */
20
+ /**
21
+ * Type-level subtype check. Returns `true` if `A` extends `B`.
22
+ *
23
+ * @example
24
+ * ```ts
25
+ * type Test1 = IsSubtype<'foo', string> // true
26
+ * type Test2 = IsSubtype<string, 'foo'> // false
27
+ * ```
28
+ */
27
29
  export type IsSubtype<A, B> = A extends B ? true : false
28
- export type AssertTrue<T extends true> = T
29
30
 
30
- export type Writeable<T> = { -readonly [P in keyof T]: T[P] }
31
- export type DeepWriteable<T> = { -readonly [P in keyof T]: DeepWriteable<T[P]> }
31
+ /** Compile-time assertion that `T` is `true`. Useful for type tests. */
32
+ export type AssertTrue<T extends true> = T
32
33
 
34
+ /** Makes all properties of `T` nullable (allows `null`). */
33
35
  export type Nullable<T> = { [K in keyof T]: T[K] | null }
34
36
 
37
+ /** Union of JavaScript primitive types. */
35
38
  export type Primitive = null | undefined | string | number | boolean | symbol | bigint
36
39
 
40
+ /**
41
+ * Creates a union type that allows specific literals while still accepting the base type.
42
+ * Useful for string/number enums with autocomplete support.
43
+ *
44
+ * @example
45
+ * ```ts
46
+ * type Status = LiteralUnion<'pending' | 'active', string>
47
+ * // Allows 'pending', 'active', or any other string
48
+ * ```
49
+ */
37
50
  export type LiteralUnion<LiteralType, BaseType extends Primitive> = LiteralType | (BaseType & Record<never, never>)
38
51
 
52
+ /** Extracts the value type for key `K` from object type `T`, or `never` if key doesn't exist. */
39
53
  export type GetValForKey<T, K> = K extends keyof T ? T[K] : never
40
54
 
55
+ /** Accepts either a single value or a readonly array of values. */
41
56
  export type SingleOrReadonlyArray<T> = T | ReadonlyArray<T>
42
57
 
58
+ /** Returns a Promise that resolves after `ms` milliseconds. */
43
59
  export const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms))
44
60
 
61
+ /**
62
+ * Creates a mutable reference object with a `current` property.
63
+ * Similar to React's `useRef` but works outside of React.
64
+ *
65
+ * @example
66
+ * ```ts
67
+ * const counter = ref(0)
68
+ * counter.current += 1
69
+ * ```
70
+ */
45
71
  export const ref = <T>(val: T): { current: T } => ({ current: val })
46
72
 
73
+ /**
74
+ * Calls a function `n` times with the current index.
75
+ *
76
+ * @example
77
+ * ```ts
78
+ * times(3, (i) => console.log(i)) // logs 0, 1, 2
79
+ * ```
80
+ */
47
81
  export const times = (n: number, fn: (index: number) => {}): void => {
48
82
  for (let i = 0; i < n; i++) {
49
83
  fn(i)
50
84
  }
51
85
  }
52
86
 
87
+ /**
88
+ * Wraps a function call in a try/catch that triggers the debugger on error.
89
+ * Useful for debugging exceptions during development.
90
+ */
53
91
  export const debugCatch = <T>(try_: () => T): T => {
54
92
  try {
55
93
  return try_()
56
94
  } catch (e: any) {
57
- // biome-ignore lint/suspicious/noDebugger: debugging
95
+ // oxlint-disable-next-line eslint(no-debugger) -- intentional breakpoint for debugging exceptions
58
96
  debugger
59
97
  throw e
60
98
  }
61
99
  }
62
100
 
101
+ /**
102
+ * Recursively removes `undefined` values from an object or array in place.
103
+ * Mutates the input value.
104
+ */
63
105
  export const recRemoveUndefinedValues = (val: any): void => {
64
- if (Array.isArray(val)) {
106
+ if (Array.isArray(val) === true) {
65
107
  val.forEach(recRemoveUndefinedValues)
66
108
  } else if (typeof val === 'object') {
67
109
  Object.keys(val).forEach((key) => {
@@ -79,36 +121,60 @@ export const recRemoveUndefinedValues = (val: any): void => {
79
121
  */
80
122
  export const sluggify = (str: string, separator = '-') => str.replace(/[^a-zA-Z0-9]/g, separator)
81
123
 
124
+ /**
125
+ * Creates a property accessor function for use in pipelines.
126
+ *
127
+ * @example
128
+ * ```ts
129
+ * const users = [{ name: 'Alice' }, { name: 'Bob' }]
130
+ * const names = users.map(prop('name')) // ['Alice', 'Bob']
131
+ * ```
132
+ */
82
133
  export const prop =
83
134
  <T extends {}, K extends keyof T>(key: K) =>
84
135
  (obj: T): T[K] =>
85
136
  obj[key]
86
137
 
138
+ /** Capitalizes the first letter of a string. */
87
139
  export const capitalizeFirstLetter = (str: string): string => str.charAt(0).toUpperCase() + str.slice(1)
88
140
 
89
- export const isReadonlyArray = <I, T>(value: ReadonlyArray<I> | T): value is ReadonlyArray<I> => Array.isArray(value)
90
-
91
141
  /**
92
142
  * Use this to make assertion at end of if-else chain that all members of a
93
143
  * union have been accounted for.
94
144
  */
95
145
 
96
- export function casesHandled(unexpectedCase: never): never {
97
- // biome-ignore lint/suspicious/noDebugger: debugging
146
+ export const casesHandled = (unexpectedCase: never): never => {
147
+ // oxlint-disable-next-line eslint(no-debugger) -- intentional breakpoint for unhandled cases
98
148
  debugger
99
149
  throw new Error(`A case was not handled for value: ${truncate(objectToString(unexpectedCase), 1000)}`)
100
150
  }
101
151
 
152
+ /**
153
+ * Throws if the condition is false. Use for runtime assertions that should never fail.
154
+ *
155
+ * @example
156
+ * ```ts
157
+ * assertNever(user !== undefined, 'User must be loaded')
158
+ * ```
159
+ */
102
160
  export const assertNever = (failIfFalse: boolean, msg?: string): void => {
103
161
  if (failIfFalse === false) {
104
- // biome-ignore lint/suspicious/noDebugger: debugging
162
+ // oxlint-disable-next-line eslint(no-debugger) -- intentional breakpoint for impossible states
105
163
  debugger
106
164
  throw new Error(`This should never happen: ${msg}`)
107
165
  }
108
166
  }
109
167
 
168
+ /**
169
+ * Identity function that triggers the debugger. Useful for debugging pipelines.
170
+ *
171
+ * @example
172
+ * ```ts
173
+ * data.pipe(transform, debuggerPipe, format) // Pauses debugger here
174
+ * ```
175
+ */
110
176
  export const debuggerPipe = <T>(val: T): T => {
111
- // biome-ignore lint/suspicious/noDebugger: debugging
177
+ // oxlint-disable-next-line eslint(no-debugger) -- intentional: this function's purpose is to trigger debugger
112
178
  debugger
113
179
  return val
114
180
  }
@@ -121,16 +187,40 @@ const truncate = (str: string, length: number): string => {
121
187
  }
122
188
  }
123
189
 
190
+ /**
191
+ * Placeholder for unimplemented code paths. Triggers debugger and throws.
192
+ *
193
+ * @example
194
+ * ```ts
195
+ * const parseFormat = (format: Format) => {
196
+ * switch (format) {
197
+ * case 'json': return parseJson
198
+ * case 'xml': return notYetImplemented('XML parsing')
199
+ * }
200
+ * }
201
+ * ```
202
+ */
124
203
  export const notYetImplemented = (msg?: string): never => {
125
- // biome-ignore lint/suspicious/noDebugger: debugging
204
+ // oxlint-disable-next-line eslint(no-debugger) -- intentional breakpoint for unimplemented code paths
126
205
  debugger
127
206
  throw new Error(`Not yet implemented: ${msg}`)
128
207
  }
129
208
 
209
+ /** A function that does nothing. Useful as a default callback. */
130
210
  export const noop = () => {}
131
211
 
212
+ /** A function that returns a value of type `T`. */
132
213
  export type Thunk<T> = () => T
133
214
 
215
+ /**
216
+ * If the input is a function, calls it and returns the result. Otherwise returns the value directly.
217
+ *
218
+ * @example
219
+ * ```ts
220
+ * unwrapThunk(5) // 5
221
+ * unwrapThunk(() => 5) // 5
222
+ * ```
223
+ */
134
224
  export const unwrapThunk = <T>(_: T | (() => T)): T => {
135
225
  if (typeof _ === 'function') {
136
226
  return (_ as any)()
@@ -139,6 +229,10 @@ export const unwrapThunk = <T>(_: T | (() => T)): T => {
139
229
  }
140
230
  }
141
231
 
232
+ /**
233
+ * Transforms nullable fields (those that include `null`) into optional fields.
234
+ * Useful for converting database schemas to TypeScript types.
235
+ */
142
236
  export type NullableFieldsToOptional<T> = Types.Simplify<
143
237
  Partial<T> & {
144
238
  [K in keyof T as null extends T[K] ? K : never]?: Exclude<T[K], null>
@@ -147,18 +241,39 @@ export type NullableFieldsToOptional<T> = Types.Simplify<
147
241
  }
148
242
  >
149
243
 
150
- /** `end` is not included */
244
+ /**
245
+ * Creates an array of numbers from `start` (inclusive) to `end` (exclusive).
246
+ *
247
+ * @example
248
+ * ```ts
249
+ * range(0, 5) // [0, 1, 2, 3, 4]
250
+ * range(3, 7) // [3, 4, 5, 6]
251
+ * ```
252
+ */
151
253
  export const range = (start: number, end: number): number[] => {
152
254
  const length = end - start
153
255
  return Array.from({ length }, (_, i) => start + i)
154
256
  }
155
257
 
258
+ /**
259
+ * Rate-limits function calls to at most once per `ms` milliseconds.
260
+ * Trailing calls are preserved—if called during the wait period, the function
261
+ * will be called again after the timeout.
262
+ *
263
+ * @example
264
+ * ```ts
265
+ * const throttledSave = throttle(() => saveData(), 1000)
266
+ * throttledSave() // Executes immediately
267
+ * throttledSave() // Queued, executes after 1 second
268
+ * throttledSave() // Ignored (already queued)
269
+ * ```
270
+ */
156
271
  export const throttle = (fn: () => void, ms: number) => {
157
272
  let shouldWait = false
158
273
  let shouldCallAgain = false
159
274
 
160
275
  const timeoutFunc = () => {
161
- if (shouldCallAgain) {
276
+ if (shouldCallAgain === true) {
162
277
  fn()
163
278
  shouldCallAgain = false
164
279
  setTimeout(timeoutFunc, ms)
@@ -168,7 +283,7 @@ export const throttle = (fn: () => void, ms: number) => {
168
283
  }
169
284
 
170
285
  return () => {
171
- if (shouldWait) {
286
+ if (shouldWait === true) {
172
287
  shouldCallAgain = true
173
288
  return
174
289
  }
@@ -179,13 +294,26 @@ export const throttle = (fn: () => void, ms: number) => {
179
294
  }
180
295
  }
181
296
 
297
+ /**
298
+ * Generates a W3C Trace Context `traceparent` header from an OpenTelemetry span.
299
+ * @see https://www.w3.org/TR/trace-context/#examples-of-http-traceparent-headers
300
+ */
182
301
  export const getTraceParentHeader = (parentSpan: otel.Span) => {
183
302
  const spanContext = parentSpan.spanContext()
184
- // Format: {version}-{trace_id}-{span_id}-{trace_flags}
185
- // https://www.w3.org/TR/trace-context/#examples-of-http-traceparent-headers
186
303
  return `00-${spanContext.traceId}-${spanContext.spanId}-01`
187
304
  }
188
305
 
306
+ /**
307
+ * Asserts that a tagged union value has a specific tag, narrowing its type.
308
+ * Throws if the tag doesn't match.
309
+ *
310
+ * @example
311
+ * ```ts
312
+ * type Result = { _tag: 'ok'; value: number } | { _tag: 'error'; message: string }
313
+ * const result: Result = ...
314
+ * const ok = assertTag(result, 'ok') // Type is { _tag: 'ok'; value: number }
315
+ * ```
316
+ */
189
317
  export const assertTag = <TObj extends { _tag: string }, TTag extends TObj['_tag']>(
190
318
  obj: TObj,
191
319
  tag: TTag,
@@ -197,12 +325,26 @@ export const assertTag = <TObj extends { _tag: string }, TTag extends TObj['_tag
197
325
  return obj as any
198
326
  }
199
327
 
328
+ /**
329
+ * Memoizes a function by JSON-stringifying its arguments as the cache key.
330
+ * Suitable for functions with serializable arguments.
331
+ *
332
+ * @example
333
+ * ```ts
334
+ * const expensiveCalc = memoizeByStringifyArgs((a: number, b: number) => {
335
+ * console.log('Computing...')
336
+ * return a + b
337
+ * })
338
+ * expensiveCalc(1, 2) // logs 'Computing...', returns 3
339
+ * expensiveCalc(1, 2) // returns 3 (cached, no log)
340
+ * ```
341
+ */
200
342
  export const memoizeByStringifyArgs = <T extends (...args: any[]) => any>(fn: T): T => {
201
343
  const cache = new Map<string, ReturnType<T>>()
202
344
 
203
345
  return ((...args: any[]) => {
204
346
  const key = JSON.stringify(args)
205
- if (cache.has(key)) {
347
+ if (cache.has(key) === true) {
206
348
  return cache.get(key)
207
349
  }
208
350
 
@@ -212,11 +354,23 @@ export const memoizeByStringifyArgs = <T extends (...args: any[]) => any>(fn: T)
212
354
  }) as any
213
355
  }
214
356
 
357
+ /**
358
+ * Memoizes a single-argument function using reference equality for cache lookup.
359
+ * Suitable for functions where arguments are objects that should be compared by reference.
360
+ *
361
+ * @example
362
+ * ```ts
363
+ * const processUser = memoizeByRef((user: User) => expensiveTransform(user))
364
+ * processUser(userA) // Computes
365
+ * processUser(userA) // Returns cached (same reference)
366
+ * processUser(userB) // Computes (different reference)
367
+ * ```
368
+ */
215
369
  export const memoizeByRef = <T extends (arg: any) => any>(fn: T): T => {
216
370
  const cache = new Map<Parameters<T>[0], ReturnType<T>>()
217
371
 
218
372
  return ((arg: any) => {
219
- if (cache.has(arg)) {
373
+ if (cache.has(arg) === true) {
220
374
  return cache.get(arg)
221
375
  }
222
376
 
@@ -226,15 +380,17 @@ export const memoizeByRef = <T extends (arg: any) => any>(fn: T): T => {
226
380
  }) as any
227
381
  }
228
382
 
383
+ /** Type guard that checks if a value is a non-empty string. */
229
384
  export const isNonEmptyString = (str: string | undefined | null): str is string => {
230
385
  return typeof str === 'string' && str.length > 0
231
386
  }
232
387
 
233
- export const isPromise = (value: any): value is Promise<unknown> => typeof value?.then === 'function'
234
-
235
- export const isIterable = <T>(value: any): value is Iterable<T> => typeof value?.[Symbol.iterator] === 'function'
236
-
237
- /** This utility "lies" as a means of compat with libs that don't explicitly type optionals as unioned with `undefined`. */
388
+ /**
389
+ * Type-level utility that removes `undefined` from all property types.
390
+ * Used for compatibility with libraries that don't type optionals as `| undefined`.
391
+ *
392
+ * Note: This is a type-level lie—the runtime value is unchanged.
393
+ */
238
394
  export const omitUndefineds = <T extends Record<keyof any, unknown>>(
239
395
  rec: T,
240
396
  ): {
package/src/node/mod.ts CHANGED
@@ -1,23 +1,21 @@
1
1
  import * as http from 'node:http'
2
2
 
3
- import { Effect, Layer } from 'effect'
3
+ import { NodeFileSystem } from '@effect/platform-node'
4
+ import { Effect, FileSystem, Layer, Option } from 'effect'
4
5
 
5
- import { OtelTracer, UnknownError } from '../effect/index.ts'
6
+ import { OtelTracer, Tracer, UnknownError } from '../effect/mod.ts'
6
7
  import { makeNoopTracer } from '../NoopTracer.ts'
7
8
 
8
- export * as Cli from '@effect/cli'
9
- export * as SocketServer from '@effect/platform/SocketServer'
9
+ export * as Cli from 'effect/unstable/cli'
10
+ export * as SocketServer from 'effect/unstable/socket/SocketServer'
10
11
  export * as PlatformNode from '@effect/platform-node'
11
12
 
12
- export * as ChildProcessRunner from './ChildProcessRunner/ChildProcessRunner.ts'
13
- export * as ChildProcessWorker from './ChildProcessRunner/ChildProcessWorker.ts'
14
-
15
13
  // Enable debug logging for OpenTelemetry
16
14
  // otel.diag.setLogger(new otel.DiagConsoleLogger(), otel.DiagLogLevel.ERROR)
17
15
 
18
16
  // export const OtelLiveHttp = (args: any): Layer.Layer<never> => Layer.empty
19
17
 
20
- export const getFreePort: Effect.Effect<number, UnknownError> = Effect.async<number, UnknownError>((cb, signal) => {
18
+ export const getFreePort: Effect.Effect<number, UnknownError> = Effect.callback<number, UnknownError>((cb, signal) => {
21
19
  const server = http.createServer()
22
20
 
23
21
  signal.addEventListener('abort', () => {
@@ -28,7 +26,7 @@ export const getFreePort: Effect.Effect<number, UnknownError> = Effect.async<num
28
26
  server.listen(0, () => {
29
27
  const address = server.address()
30
28
 
31
- if (address && typeof address === 'object') {
29
+ if (address !== null && typeof address === 'object') {
32
30
  const port = address.port
33
31
  server.close(() => cb(Effect.succeed(port)))
34
32
  } else {
@@ -45,9 +43,56 @@ export const getFreePort: Effect.Effect<number, UnknownError> = Effect.async<num
45
43
  export const OtelLiveDummy: Layer.Layer<OtelTracer.OtelTracer> = Layer.suspend(() => {
46
44
  const OtelTracerLive = Layer.succeed(OtelTracer.OtelTracer, makeNoopTracer())
47
45
 
48
- const TracingLive = Layer.unwrapEffect(Effect.map(OtelTracer.make, Layer.setTracer)).pipe(
49
- Layer.provideMerge(OtelTracerLive),
50
- )
46
+ const TracingLive = OtelTracer.layerWithoutOtelTracer.pipe(Layer.provideMerge(OtelTracerLive))
51
47
 
52
48
  return TracingLive
53
49
  })
50
+
51
+ /**
52
+ * Compatibility layer for the old Effect v3 @parcel/watcher backend.
53
+ *
54
+ * Effect v4 removed `@effect/platform-node/NodeFileSystem/ParcelWatcher`; its
55
+ * NodeFileSystem layer now performs recursive watching through Node's native
56
+ * `fs.watch(path, { recursive: true })` fallback. This layer deliberately
57
+ * registers no custom backend so existing `NodeRecursiveWatchLayer` consumers
58
+ * continue to compile while the v4 NodeFileSystem implementation handles watch
59
+ * events.
60
+ *
61
+ * This layer alone does NOT provide FileSystem - it only provides the watch backend.
62
+ *
63
+ * IMPORTANT: Layer ordering matters! When composing with NodeFileSystem.layer, use
64
+ * `NodeFileSystemWithWatch` instead, or ensure WatchBackend is available when FileSystem
65
+ * is constructed by using `Layer.provideMerge`:
66
+ *
67
+ * ```ts
68
+ * // ✅ CORRECT: Use the pre-composed layer
69
+ * Effect.provide(NodeFileSystemWithWatch)
70
+ *
71
+ * // ✅ CORRECT: Manual composition with Layer.provideMerge
72
+ * const layer = PlatformNode.NodeFileSystem.layer.pipe(Layer.provideMerge(NodeRecursiveWatchLayer))
73
+ * Effect.provide(layer)
74
+ *
75
+ * // ❌ WRONG: Chained Effect.provide - WatchBackend won't be used!
76
+ * Effect.provide(NodeRecursiveWatchLayer).pipe(Effect.provide(PlatformNode.NodeFileSystem.layer))
77
+ * ```
78
+ *
79
+ * @see https://github.com/Effect-TS/effect/issues/5913
80
+ */
81
+ export const NodeRecursiveWatchLayer: Layer.Layer<FileSystem.WatchBackend> = Layer.succeed(
82
+ FileSystem.WatchBackend,
83
+ FileSystem.WatchBackend.of({
84
+ register: () => Option.none(),
85
+ }),
86
+ )
87
+
88
+ /**
89
+ * Pre-composed layer providing FileSystem with recursive file watching.
90
+ * This is the recommended way to get a FileSystem that supports recursive watching.
91
+ *
92
+ * Use this layer when you need to watch files recursively (e.g., watching nested directories).
93
+ * Effect v4's NodeFileSystem uses Node.js's native recursive `fs.watch` support
94
+ * instead of the removed ParcelWatcher backend from Effect v3.
95
+ */
96
+ export { NodeFileSystem } from '@effect/platform-node'
97
+
98
+ export const NodeFileSystemWithWatch = NodeFileSystem.layer.pipe(Layer.provideMerge(NodeRecursiveWatchLayer))
@@ -1,25 +1 @@
1
- import { pipe } from 'effect'
2
-
3
- export * from './omit.ts'
4
- export * from './pick.ts'
5
1
  export * from './stringify-object.ts'
6
-
7
- type ValueOfRecord<R extends Record<any, any>> = R extends Record<any, infer V> ? V : never
8
-
9
- export const mapObjectValues = <O_In extends Record<string, any>, V_Out>(
10
- obj: O_In,
11
- mapValue: (key: keyof O_In, val: ValueOfRecord<O_In>) => V_Out,
12
- ): { [K in keyof O_In]: V_Out } => {
13
- const mappedEntries = Object.entries(obj).map(([key, val]) => [key, mapValue(key as keyof O_In, val)] as const)
14
- return Object.fromEntries(mappedEntries) as any
15
- }
16
-
17
- export type Entries<T> = { [K in keyof T]: [K, T[K]] }[keyof T][]
18
-
19
- export const objectEntries = <T extends Record<string, any>>(obj: T): Entries<T> => Object.entries(obj) as Entries<T>
20
-
21
- export const keyObjectFromObject = <TObj extends Record<string, any>>(obj: TObj): { [K in keyof TObj]: K } =>
22
- pipe(
23
- objectEntries(obj).map(([k]) => [k, k]),
24
- Object.fromEntries,
25
- ) as any
@@ -1,4 +1,5 @@
1
1
  import { describe, expect, it } from 'vitest'
2
+
2
3
  import { stringifyObject } from './stringify-object.ts'
3
4
 
4
5
  describe('stringifyObject', () => {
@@ -11,10 +11,10 @@ export const stringifyObject = (obj: object, prefix = ''): string => {
11
11
  for (const [key, value] of Object.entries(obj)) {
12
12
  const fullKey = prefix !== '' ? `${prefix}.${key}` : key
13
13
 
14
- if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
14
+ if (typeof value === 'object' && value !== null && Array.isArray(value) === false) {
15
15
  // Recursively stringify nested objects with dot notation
16
16
  entries.push(stringifyObject(value, fullKey))
17
- } else if (Array.isArray(value)) {
17
+ } else if (Array.isArray(value) === true) {
18
18
  // Arrays get converted to comma-separated values
19
19
  entries.push(`${fullKey}=${value.join(',')}`)
20
20
  } else {
package/src/qr.ts ADDED
@@ -0,0 +1,125 @@
1
+ import qrcode from 'qrcode-generator'
2
+
3
+ export { default as QR } from 'qrcode-generator'
4
+
5
+ export type QRErrorCorrectionLevel = 'L' | 'M' | 'Q' | 'H'
6
+
7
+ export type PrintQrTerminalOptions = {
8
+ errorCorrectionLevel?: QRErrorCorrectionLevel
9
+ /** number of white-cell rows/cols around the QR (default 2) */
10
+ margin?: number
11
+ /** Swap dark/light rendering (default false) */
12
+ invert?: boolean
13
+ /** Use ANSI colors for compact half-block rendering. Default: auto (true on TTY) */
14
+ useAnsi?: boolean
15
+ }
16
+
17
+ /**
18
+ * Print a QR code to the terminal using Unicode block characters.
19
+ *
20
+ * Notes
21
+ * - Uses `qrcode-generator` under the hood. `typeNumber` is set to `0` which means
22
+ * “auto-select the smallest QR version (1..40) that fits the data”. Larger versions
23
+ * create larger matrices (each version adds 4 modules per side). See the library:
24
+ * - Types (TypeNumber, ErrorCorrectionLevel):
25
+ * https://unpkg.com/qrcode-generator@2.0.4/dist/qrcode.d.ts
26
+ * - Repository / README:
27
+ * https://github.com/kazuhikoarase/qrcode-generator
28
+ * - Error correction level (ECL) controls how much damage/occlusion can be
29
+ * tolerated by adding redundancy. Higher ECL means bigger codes and lower
30
+ * payload capacity:
31
+ * - L ≈ 7% recovery (highest capacity, lowest redundancy)
32
+ * - M ≈ 15% recovery (balanced, common default)
33
+ * - Q ≈ 25% recovery (more robust in noisy/low-contrast prints)
34
+ * - H ≈ 30% recovery (most robust; largest symbols)
35
+ * Reference: https://www.qrcode.com/en/about/error_correction.html
36
+ * - `small` mode compresses two QR rows into one terminal row using the half-block
37
+ * character `▀` and optional ANSI colors (foreground=top, background=bottom).
38
+ * This roughly halves the height while keeping readability in modern terminals.
39
+ * - The QR “quiet zone” (margin) is important for scanners. If scanning is unreliable,
40
+ * consider increasing `margin` to 2–4.
41
+ */
42
+ export const printQrTerminal = (text: string, options?: PrintQrTerminalOptions): void => {
43
+ const ec: QRErrorCorrectionLevel = options?.errorCorrectionLevel ?? 'M'
44
+ const margin = options?.margin ?? 2
45
+ const invert = options?.invert ?? false
46
+ // Auto-enable ANSI on TTY; avoid in browsers
47
+ const useAnsi = options?.useAnsi ?? (typeof process !== 'undefined' && !!(process as any)?.stdout?.isTTY)
48
+
49
+ // Create the QR code. `0` means: choose the smallest possible version automatically.
50
+ // Error correction level (L/M/Q/H) trades capacity for redundancy.
51
+ const qr = qrcode(0, ec)
52
+ qr.addData(text)
53
+ qr.make()
54
+
55
+ const size = qr.getModuleCount()
56
+
57
+ // Helper: read a module, applying margin and optional inversion.
58
+ // qrcode-generator’s `isDark` signature is (row, col) i.e. (y, x).
59
+ const isDarkAt = (x: number, y: number): boolean => {
60
+ const row = y - margin
61
+ const col = x - margin
62
+ if (row < 0 || col < 0 || row >= size || col >= size) return false
63
+ const bit = qr.isDark(row, col)
64
+ return invert === true ? !bit : bit
65
+ }
66
+
67
+ const lines: string[] = []
68
+
69
+ // Compact rendering: combine two QR rows into one terminal row using half-blocks.
70
+ // With ANSI enabled: use `▀` and paint top as FG, bottom as BG (clear and crisp).
71
+ // Without ANSI: fall back to block chars (`█`, `▀`, `▄`, space). Note that the
72
+ // non-ANSI fallback cannot enforce a white background on dark themes, which may
73
+ // reduce scanner reliability—prefer ANSI when possible.
74
+ const width = size + 2 * margin
75
+ const height = size + 2 * margin
76
+
77
+ // ANSI helpers (only used when enabled)
78
+ // Use bright white for a strong “paper-like” background on dark terminals.
79
+ const RESET = '\x1b[0m'
80
+ const FG_BLACK = '\x1b[30m'
81
+ const FG_WHITE = '\x1b[97m'
82
+ const BG_BLACK = '\x1b[40m'
83
+ const BG_WHITE = '\x1b[107m'
84
+
85
+ for (let y = 0; y < height; y += 2) {
86
+ let row = ''
87
+ let currentStyle = ''
88
+ const setStyle = (style: string) => {
89
+ if (style !== currentStyle) {
90
+ if (currentStyle !== undefined) row += RESET
91
+ if (style !== undefined) row += style
92
+ currentStyle = style
93
+ }
94
+ }
95
+
96
+ for (let x = 0; x < width; x++) {
97
+ const top = isDarkAt(x, y)
98
+ const bottom = isDarkAt(x, y + 1)
99
+
100
+ if (useAnsi === true) {
101
+ // Represent two stacked modules with a single `▀` character.
102
+ // Foreground corresponds to the top pixel; background to the bottom pixel.
103
+ // Always paint both halves to enforce a white quiet zone even on dark terminals.
104
+ const fg = top === true ? FG_BLACK : FG_WHITE
105
+ const bg = bottom === true ? BG_BLACK : BG_WHITE
106
+ setStyle(fg + bg)
107
+ row += '▀'
108
+ } else {
109
+ // No ANSI: approximate using block characters.
110
+ let ch = ' '
111
+ if (top === true && bottom === true) ch = '█'
112
+ else if (top === true && bottom === false) ch = '▀'
113
+ else if (top === false && bottom === true) ch = '▄'
114
+ row += ch
115
+ }
116
+ }
117
+
118
+ if (useAnsi === true) {
119
+ if (currentStyle !== undefined) row += RESET
120
+ }
121
+ lines.push(row)
122
+ }
123
+
124
+ console.log(lines.join('\n'))
125
+ }
package/src/set.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  export const difference = <T>(a: Set<T>, b: Set<T>) => {
2
2
  const diff = new Set<T>()
3
3
  for (const item of a) {
4
- if (!b.has(item)) {
4
+ if (b.has(item) === false) {
5
5
  diff.add(item)
6
6
  }
7
7
  }
package/src/string.ts CHANGED
@@ -1,6 +1,3 @@
1
- export const lowercaseFirstChar = (str: string) => str.charAt(0).toLowerCase() + str.slice(1)
2
- export const uppercaseFirstChar = (str: string) => str.charAt(0).toUpperCase() + str.slice(1)
3
-
4
1
  /** Indents a string each line by `n` characters (default: spaces) */
5
2
  export const indent = (str: string, n: number, char = ' '): string =>
6
3
  str