@livestore/utils 0.4.0-dev.8 → 0.4.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 (226) hide show
  1. package/dist/.tsbuildinfo +1 -0
  2. package/dist/NoopTracer.d.ts.map +1 -1
  3. package/dist/NoopTracer.js +17 -4
  4. package/dist/NoopTracer.js.map +1 -1
  5. package/dist/binary.js +1 -1
  6. package/dist/binary.js.map +1 -1
  7. package/dist/browser/Opfs/Opfs.d.ts +51 -0
  8. package/dist/browser/Opfs/Opfs.d.ts.map +1 -0
  9. package/dist/browser/Opfs/Opfs.js +345 -0
  10. package/dist/browser/Opfs/Opfs.js.map +1 -0
  11. package/dist/browser/Opfs/debug-utils.d.ts +20 -0
  12. package/dist/browser/Opfs/debug-utils.d.ts.map +1 -0
  13. package/dist/browser/Opfs/debug-utils.js +94 -0
  14. package/dist/browser/Opfs/debug-utils.js.map +1 -0
  15. package/dist/browser/Opfs/mod.d.ts +4 -0
  16. package/dist/browser/Opfs/mod.d.ts.map +1 -0
  17. package/dist/browser/Opfs/mod.js +4 -0
  18. package/dist/browser/Opfs/mod.js.map +1 -0
  19. package/dist/browser/Opfs/utils.d.ts +71 -0
  20. package/dist/browser/Opfs/utils.d.ts.map +1 -0
  21. package/dist/browser/Opfs/utils.js +218 -0
  22. package/dist/browser/Opfs/utils.js.map +1 -0
  23. package/dist/browser/QuotaExceededError.d.ts +59 -0
  24. package/dist/browser/QuotaExceededError.d.ts.map +1 -0
  25. package/dist/browser/QuotaExceededError.js +2 -0
  26. package/dist/browser/QuotaExceededError.js.map +1 -0
  27. package/dist/browser/WebChannelBrowser.d.ts +22 -0
  28. package/dist/browser/WebChannelBrowser.d.ts.map +1 -0
  29. package/dist/browser/WebChannelBrowser.js +76 -0
  30. package/dist/browser/WebChannelBrowser.js.map +1 -0
  31. package/dist/browser/WebError.d.ts +421 -0
  32. package/dist/browser/WebError.d.ts.map +1 -0
  33. package/dist/browser/WebError.js +416 -0
  34. package/dist/browser/WebError.js.map +1 -0
  35. package/dist/browser/WebError.test.d.ts +2 -0
  36. package/dist/browser/WebError.test.d.ts.map +1 -0
  37. package/dist/browser/WebError.test.js +46 -0
  38. package/dist/browser/WebError.test.js.map +1 -0
  39. package/dist/browser/WebLock.d.ts.map +1 -0
  40. package/dist/{effect → browser}/WebLock.js +9 -9
  41. package/dist/browser/WebLock.js.map +1 -0
  42. package/dist/{browser.d.ts → browser/detect.d.ts} +1 -1
  43. package/dist/browser/detect.d.ts.map +1 -0
  44. package/dist/{browser.js → browser/detect.js} +7 -7
  45. package/dist/browser/detect.js.map +1 -0
  46. package/dist/browser/mod.d.ts +8 -0
  47. package/dist/browser/mod.d.ts.map +1 -0
  48. package/dist/browser/mod.js +8 -0
  49. package/dist/browser/mod.js.map +1 -0
  50. package/dist/cuid/cuid.browser.js +1 -1
  51. package/dist/cuid/cuid.browser.js.map +1 -1
  52. package/dist/cuid/cuid.node.js +1 -1
  53. package/dist/cuid/cuid.node.js.map +1 -1
  54. package/dist/effect/BucketQueue.d.ts +1 -1
  55. package/dist/effect/BucketQueue.d.ts.map +1 -1
  56. package/dist/effect/BucketQueue.js.map +1 -1
  57. package/dist/effect/Debug.d.ts +41 -0
  58. package/dist/effect/Debug.d.ts.map +1 -0
  59. package/dist/effect/Debug.js +354 -0
  60. package/dist/effect/Debug.js.map +1 -0
  61. package/dist/effect/Effect.d.ts +72 -12
  62. package/dist/effect/Effect.d.ts.map +1 -1
  63. package/dist/effect/Effect.js +96 -12
  64. package/dist/effect/Effect.js.map +1 -1
  65. package/dist/effect/Error.js +1 -1
  66. package/dist/effect/Error.js.map +1 -1
  67. package/dist/effect/Logger.js +2 -2
  68. package/dist/effect/Logger.js.map +1 -1
  69. package/dist/effect/RpcClient.d.ts.map +1 -1
  70. package/dist/effect/RpcClient.js +11 -4
  71. package/dist/effect/RpcClient.js.map +1 -1
  72. package/dist/effect/Schema/debug-diff.js +5 -4
  73. package/dist/effect/Schema/debug-diff.js.map +1 -1
  74. package/dist/effect/Schema/debug-diff.test.js +1 -1
  75. package/dist/effect/Schema/debug-diff.test.js.map +1 -1
  76. package/dist/effect/Schema/index.d.ts +5 -3
  77. package/dist/effect/Schema/index.d.ts.map +1 -1
  78. package/dist/effect/Schema/index.js +2 -2
  79. package/dist/effect/Schema/index.js.map +1 -1
  80. package/dist/effect/ServiceContext.js +6 -6
  81. package/dist/effect/ServiceContext.js.map +1 -1
  82. package/dist/effect/Stream.test.js +3 -3
  83. package/dist/effect/Stream.test.js.map +1 -1
  84. package/dist/effect/SubscriptionRef.d.ts +4 -4
  85. package/dist/effect/SubscriptionRef.d.ts.map +1 -1
  86. package/dist/effect/WebChannel/WebChannel.d.ts +4 -23
  87. package/dist/effect/WebChannel/WebChannel.d.ts.map +1 -1
  88. package/dist/effect/WebChannel/WebChannel.js +9 -85
  89. package/dist/effect/WebChannel/WebChannel.js.map +1 -1
  90. package/dist/effect/WebChannel/WebChannel.test.js +1 -1
  91. package/dist/effect/WebChannel/WebChannel.test.js.map +1 -1
  92. package/dist/effect/WebChannel/broadcastChannelWithAck.js +4 -4
  93. package/dist/effect/WebChannel/broadcastChannelWithAck.js.map +1 -1
  94. package/dist/effect/WebChannel/common.d.ts +2 -2
  95. package/dist/effect/WebChannel/common.d.ts.map +1 -1
  96. package/dist/effect/WebChannel/common.js +2 -2
  97. package/dist/effect/WebChannel/common.js.map +1 -1
  98. package/dist/effect/WebSocket.d.ts.map +1 -1
  99. package/dist/effect/WebSocket.js +14 -14
  100. package/dist/effect/WebSocket.js.map +1 -1
  101. package/dist/effect/{index.d.ts → mod.d.ts} +4 -4
  102. package/dist/effect/mod.d.ts.map +1 -0
  103. package/dist/effect/{index.js → mod.js} +5 -5
  104. package/dist/effect/mod.js.map +1 -0
  105. package/dist/effect/spanEvent.d.ts +12 -0
  106. package/dist/effect/spanEvent.d.ts.map +1 -0
  107. package/dist/effect/spanEvent.js +12 -0
  108. package/dist/effect/spanEvent.js.map +1 -0
  109. package/dist/effect/spanEvent.test.d.ts +2 -0
  110. package/dist/effect/spanEvent.test.d.ts.map +1 -0
  111. package/dist/effect/spanEvent.test.js +82 -0
  112. package/dist/effect/spanEvent.test.js.map +1 -0
  113. package/dist/env.d.ts.map +1 -1
  114. package/dist/env.js +1 -1
  115. package/dist/env.js.map +1 -1
  116. package/dist/fast-deep-equal.js +9 -9
  117. package/dist/fast-deep-equal.js.map +1 -1
  118. package/dist/global.d.ts +3 -0
  119. package/dist/global.d.ts.map +1 -1
  120. package/dist/global.js.map +1 -1
  121. package/dist/guards.d.ts +14 -0
  122. package/dist/guards.d.ts.map +1 -1
  123. package/dist/guards.js +14 -0
  124. package/dist/guards.js.map +1 -1
  125. package/dist/misc.d.ts +9 -1
  126. package/dist/misc.d.ts.map +1 -1
  127. package/dist/misc.js +11 -3
  128. package/dist/misc.js.map +1 -1
  129. package/dist/mod.d.ts +197 -5
  130. package/dist/mod.d.ts.map +1 -1
  131. package/dist/mod.js +162 -17
  132. package/dist/mod.js.map +1 -1
  133. package/dist/node/ChildProcessRunner/ChildProcessRunner.d.ts.map +1 -1
  134. package/dist/node/ChildProcessRunner/ChildProcessRunner.js +15 -9
  135. package/dist/node/ChildProcessRunner/ChildProcessRunner.js.map +1 -1
  136. package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/ChildProcessRunner.test.d.ts +8 -0
  137. package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/ChildProcessRunner.test.d.ts.map +1 -1
  138. package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/ChildProcessRunner.test.js +16 -17
  139. package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/ChildProcessRunner.test.js.map +1 -1
  140. package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/schema.d.ts +4 -4
  141. package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/serializedWorker.js +3 -3
  142. package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/serializedWorker.js.map +1 -1
  143. package/dist/node/ChildProcessRunner/ChildProcessWorker.d.ts.map +1 -1
  144. package/dist/node/ChildProcessRunner/ChildProcessWorker.js +4 -4
  145. package/dist/node/ChildProcessRunner/ChildProcessWorker.js.map +1 -1
  146. package/dist/node/mod.d.ts +34 -1
  147. package/dist/node/mod.d.ts.map +1 -1
  148. package/dist/node/mod.js +37 -2
  149. package/dist/node/mod.js.map +1 -1
  150. package/dist/object/index.d.ts.map +1 -1
  151. package/dist/object/index.js.map +1 -1
  152. package/dist/object/omit.js +1 -1
  153. package/dist/object/omit.js.map +1 -1
  154. package/dist/object/stringify-object.js +2 -2
  155. package/dist/object/stringify-object.js.map +1 -1
  156. package/dist/object/stringify-object.test.js.map +1 -1
  157. package/dist/qr.d.ts +38 -0
  158. package/dist/qr.d.ts.map +1 -0
  159. package/dist/qr.js +109 -0
  160. package/dist/qr.js.map +1 -0
  161. package/dist/set.js +1 -1
  162. package/dist/set.js.map +1 -1
  163. package/dist/time.js +1 -1
  164. package/dist/time.js.map +1 -1
  165. package/package.json +78 -54
  166. package/src/NoopTracer.ts +22 -8
  167. package/src/binary.ts +1 -1
  168. package/src/browser/Opfs/Opfs.ts +436 -0
  169. package/src/browser/Opfs/debug-utils.ts +153 -0
  170. package/src/browser/Opfs/mod.ts +3 -0
  171. package/src/browser/Opfs/utils.ts +287 -0
  172. package/src/browser/QuotaExceededError.ts +57 -0
  173. package/src/browser/WebChannelBrowser.ts +131 -0
  174. package/src/browser/WebError.test.ts +66 -0
  175. package/src/browser/WebError.ts +613 -0
  176. package/src/{effect → browser}/WebLock.ts +15 -15
  177. package/src/{browser.ts → browser/detect.ts} +6 -6
  178. package/src/browser/mod.ts +8 -0
  179. package/src/cuid/cuid.browser.ts +1 -1
  180. package/src/cuid/cuid.node.ts +1 -1
  181. package/src/effect/BucketQueue.ts +1 -1
  182. package/src/effect/Debug.ts +470 -0
  183. package/src/effect/Effect.ts +118 -36
  184. package/src/effect/Error.ts +1 -1
  185. package/src/effect/Logger.ts +2 -2
  186. package/src/effect/RpcClient.ts +14 -6
  187. package/src/effect/Schema/debug-diff.test.ts +2 -2
  188. package/src/effect/Schema/debug-diff.ts +6 -5
  189. package/src/effect/Schema/index.ts +10 -7
  190. package/src/effect/ServiceContext.ts +6 -6
  191. package/src/effect/Stream.test.ts +5 -4
  192. package/src/effect/SubscriptionRef.ts +5 -5
  193. package/src/effect/WebChannel/WebChannel.test.ts +1 -1
  194. package/src/effect/WebChannel/WebChannel.ts +19 -141
  195. package/src/effect/WebChannel/broadcastChannelWithAck.ts +4 -4
  196. package/src/effect/WebChannel/common.ts +5 -5
  197. package/src/effect/WebSocket.ts +13 -12
  198. package/src/effect/{index.ts → mod.ts} +10 -2
  199. package/src/effect/spanEvent.test.ts +95 -0
  200. package/src/effect/spanEvent.ts +15 -0
  201. package/src/env.ts +2 -1
  202. package/src/fast-deep-equal.ts +9 -9
  203. package/src/global.ts +4 -2
  204. package/src/guards.ts +15 -0
  205. package/src/misc.ts +12 -4
  206. package/src/mod.ts +209 -17
  207. package/src/node/ChildProcessRunner/ChildProcessRunner.ts +23 -10
  208. package/src/node/ChildProcessRunner/ChildProcessRunnerTest/ChildProcessRunner.test.ts +30 -21
  209. package/src/node/ChildProcessRunner/ChildProcessRunnerTest/serializedWorker.ts +10 -11
  210. package/src/node/ChildProcessRunner/ChildProcessWorker.ts +5 -4
  211. package/src/node/mod.ts +41 -2
  212. package/src/object/index.ts +1 -1
  213. package/src/object/omit.ts +1 -1
  214. package/src/object/stringify-object.test.ts +1 -0
  215. package/src/object/stringify-object.ts +2 -2
  216. package/src/qr.ts +125 -0
  217. package/src/set.ts +1 -1
  218. package/src/time.ts +1 -1
  219. package/dist/.tsbuildinfo.json +0 -1
  220. package/dist/browser.d.ts.map +0 -1
  221. package/dist/browser.js.map +0 -1
  222. package/dist/effect/WebLock.d.ts.map +0 -1
  223. package/dist/effect/WebLock.js.map +0 -1
  224. package/dist/effect/index.d.ts.map +0 -1
  225. package/dist/effect/index.js.map +0 -1
  226. /package/dist/{effect → browser}/WebLock.d.ts +0 -0
@@ -1,4 +1,5 @@
1
1
  import type * as ChildProcess from 'node:child_process'
2
+
2
3
  import * as Worker from '@effect/platform/Worker'
3
4
  import { WorkerError } from '@effect/platform/WorkerError'
4
5
  import * as Deferred from 'effect/Deferred'
@@ -14,7 +15,7 @@ const childProcesses = new Set<ChildProcess.ChildProcess>()
14
15
  const forceCleanupChildren = (signal: NodeJS.Signals = 'SIGKILL') => {
15
16
  for (const child of childProcesses) {
16
17
  try {
17
- if (!child.killed) {
18
+ if (child.killed === false) {
18
19
  child.kill(signal)
19
20
  }
20
21
  } catch {
@@ -28,7 +29,7 @@ const forceCleanupChildren = (signal: NodeJS.Signals = 'SIGKILL') => {
28
29
  let signalHandlersInstalled = false
29
30
 
30
31
  const installSignalHandlers = () => {
31
- if (signalHandlersInstalled) return
32
+ if (signalHandlersInstalled === true) return
32
33
  signalHandlersInstalled = true
33
34
 
34
35
  // Use 'beforeExit' instead of signal handlers since tests may interfere with signals
@@ -95,14 +96,14 @@ const platformWorkerImpl = Worker.makePlatform<ChildProcess.ChildProcess>()({
95
96
  Effect.catchAllCause(() =>
96
97
  Effect.sync(() => {
97
98
  // Enhanced cleanup with escalating signals
98
- if (!childProcess.killed) {
99
+ if (childProcess.killed === false) {
99
100
  try {
100
101
  // First try SIGTERM
101
102
  childProcess.kill('SIGTERM')
102
103
 
103
104
  // If still running after a short delay, use SIGKILL
104
105
  setTimeout(() => {
105
- if (!childProcess.killed) {
106
+ if (childProcess.killed === false) {
106
107
  childProcess.kill('SIGKILL')
107
108
  }
108
109
  }, 1000)
package/src/node/mod.ts CHANGED
@@ -1,8 +1,9 @@
1
1
  import * as http from 'node:http'
2
2
 
3
+ import { layer as ParcelWatcherLayer } from '@effect/platform-node/NodeFileSystem/ParcelWatcher'
3
4
  import { Effect, Layer } from 'effect'
4
5
 
5
- import { OtelTracer, UnknownError } from '../effect/index.ts'
6
+ import { OtelTracer, UnknownError } from '../effect/mod.ts'
6
7
  import { makeNoopTracer } from '../NoopTracer.ts'
7
8
 
8
9
  export * as Cli from '@effect/cli'
@@ -28,7 +29,7 @@ export const getFreePort: Effect.Effect<number, UnknownError> = Effect.async<num
28
29
  server.listen(0, () => {
29
30
  const address = server.address()
30
31
 
31
- if (address && typeof address === 'object') {
32
+ if (address !== null && typeof address === 'object') {
32
33
  const port = address.port
33
34
  server.close(() => cb(Effect.succeed(port)))
34
35
  } else {
@@ -51,3 +52,41 @@ export const OtelLiveDummy: Layer.Layer<OtelTracer.OtelTracer> = Layer.suspend((
51
52
 
52
53
  return TracingLive
53
54
  })
55
+
56
+ /**
57
+ * Layer that provides WatchBackend for recursive file watching via @parcel/watcher.
58
+ * This layer alone does NOT provide FileSystem - it only provides the watch backend.
59
+ *
60
+ * IMPORTANT: Layer ordering matters! When composing with NodeFileSystem.layer, use
61
+ * `NodeFileSystemWithWatch` instead, or ensure WatchBackend is available when FileSystem
62
+ * is constructed by using `Layer.provideMerge`:
63
+ *
64
+ * ```ts
65
+ * // ✅ CORRECT: Use the pre-composed layer
66
+ * Effect.provide(NodeFileSystemWithWatch)
67
+ *
68
+ * // ✅ CORRECT: Manual composition with Layer.provideMerge
69
+ * const layer = PlatformNode.NodeFileSystem.layer.pipe(Layer.provideMerge(NodeRecursiveWatchLayer))
70
+ * Effect.provide(layer)
71
+ *
72
+ * // ❌ WRONG: Chained Effect.provide - WatchBackend won't be used!
73
+ * Effect.provide(NodeRecursiveWatchLayer).pipe(Effect.provide(PlatformNode.NodeFileSystem.layer))
74
+ * ```
75
+ *
76
+ * @see https://github.com/Effect-TS/effect/issues/5913
77
+ */
78
+ export const NodeRecursiveWatchLayer = ParcelWatcherLayer
79
+
80
+ /**
81
+ * Pre-composed layer providing FileSystem with recursive file watching via @parcel/watcher.
82
+ * This is the recommended way to get a FileSystem that supports recursive watching.
83
+ *
84
+ * Use this layer when you need to watch files recursively (e.g., watching nested directories).
85
+ * Without recursive watching, Node.js's built-in fs.watch only detects changes in the
86
+ * immediate directory, not in subdirectories.
87
+ */
88
+ export { NodeFileSystem } from '@effect/platform-node'
89
+
90
+ import { NodeFileSystem } from '@effect/platform-node'
91
+
92
+ export const NodeFileSystemWithWatch = NodeFileSystem.layer.pipe(Layer.provideMerge(ParcelWatcherLayer))
@@ -22,4 +22,4 @@ export const keyObjectFromObject = <TObj extends Record<string, any>>(obj: TObj)
22
22
  pipe(
23
23
  objectEntries(obj).map(([k]) => [k, k]),
24
24
  Object.fromEntries,
25
- ) as any
25
+ )
@@ -9,7 +9,7 @@ export const omit = <Obj extends Record<string, any>, Keys extends keyof Obj>(
9
9
  keys: Keys[],
10
10
  ): Omit<Obj, Keys> => {
11
11
  return Object.keys(obj).reduce((acc, key: any) => {
12
- if (!keys.includes(key)) {
12
+ if (keys.includes(key) === false) {
13
13
  acc[key] = (obj as any)[key]
14
14
  }
15
15
  return acc
@@ -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/time.ts CHANGED
@@ -17,7 +17,7 @@ export const msAsTimeString = (ms: number) => {
17
17
  const remainingSeconds = (seconds % 60).toString().padStart(2, '0')
18
18
  const remainingMinutes = hours > 0 ? (minutes % 60).toString().padStart(2, '0') : minutes % 60
19
19
 
20
- const timeString = [hours > 0 ? `${hours}:` : '', `${remainingMinutes}:`, `${remainingSeconds}`]
20
+ const timeString = [hours > 0 ? `${hours}:` : '', `${remainingMinutes}:`, remainingSeconds]
21
21
  .filter((val) => val !== '')
22
22
  .join('')
23
23