@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.
- package/dist/.tsbuildinfo +1 -0
- package/dist/NoopTracer.d.ts +7 -8
- package/dist/NoopTracer.d.ts.map +1 -1
- package/dist/NoopTracer.js +13 -15
- package/dist/NoopTracer.js.map +1 -1
- package/dist/NoopTracer.test.d.ts +2 -0
- package/dist/NoopTracer.test.d.ts.map +1 -0
- package/dist/NoopTracer.test.js +17 -0
- package/dist/NoopTracer.test.js.map +1 -0
- package/dist/binary.js +1 -1
- package/dist/binary.js.map +1 -1
- package/dist/browser/Opfs/Opfs.d.ts +201 -0
- package/dist/browser/Opfs/Opfs.d.ts.map +1 -0
- package/dist/browser/Opfs/Opfs.js +170 -0
- package/dist/browser/Opfs/Opfs.js.map +1 -0
- package/dist/browser/Opfs/debug-utils.d.ts +20 -0
- package/dist/browser/Opfs/debug-utils.d.ts.map +1 -0
- package/dist/browser/Opfs/debug-utils.js +95 -0
- package/dist/browser/Opfs/debug-utils.js.map +1 -0
- package/dist/browser/Opfs/mod.d.ts +4 -0
- package/dist/browser/Opfs/mod.d.ts.map +1 -0
- package/dist/browser/Opfs/mod.js +4 -0
- package/dist/browser/Opfs/mod.js.map +1 -0
- package/dist/browser/Opfs/utils.d.ts +71 -0
- package/dist/browser/Opfs/utils.d.ts.map +1 -0
- package/dist/browser/Opfs/utils.js +227 -0
- package/dist/browser/Opfs/utils.js.map +1 -0
- package/dist/browser/QuotaExceededError.d.ts +60 -0
- package/dist/browser/QuotaExceededError.d.ts.map +1 -0
- package/dist/browser/QuotaExceededError.js +2 -0
- package/dist/browser/QuotaExceededError.js.map +1 -0
- package/dist/browser/WebChannelBrowser.d.ts +22 -0
- package/dist/browser/WebChannelBrowser.d.ts.map +1 -0
- package/dist/browser/WebChannelBrowser.js +87 -0
- package/dist/browser/WebChannelBrowser.js.map +1 -0
- package/dist/browser/WebError.d.ts +389 -0
- package/dist/browser/WebError.d.ts.map +1 -0
- package/dist/browser/WebError.js +423 -0
- package/dist/browser/WebError.js.map +1 -0
- package/dist/browser/WebError.test.d.ts +2 -0
- package/dist/browser/WebError.test.d.ts.map +1 -0
- package/dist/browser/WebError.test.js +46 -0
- package/dist/browser/WebError.test.js.map +1 -0
- package/dist/browser/WebLock.d.ts.map +1 -0
- package/dist/{effect → browser}/WebLock.js +21 -21
- package/dist/browser/WebLock.js.map +1 -0
- package/dist/{browser.d.ts → browser/detect.d.ts} +1 -1
- package/dist/browser/detect.d.ts.map +1 -0
- package/dist/{browser.js → browser/detect.js} +7 -7
- package/dist/browser/detect.js.map +1 -0
- package/dist/browser/mod.d.ts +8 -0
- package/dist/browser/mod.d.ts.map +1 -0
- package/dist/browser/mod.js +8 -0
- package/dist/browser/mod.js.map +1 -0
- package/dist/cuid/cuid.browser.js +1 -1
- package/dist/cuid/cuid.browser.js.map +1 -1
- package/dist/cuid/cuid.node.js +1 -1
- package/dist/cuid/cuid.node.js.map +1 -1
- package/dist/effect/Debug.d.ts +38 -0
- package/dist/effect/Debug.d.ts.map +1 -0
- package/dist/effect/Debug.js +350 -0
- package/dist/effect/Debug.js.map +1 -0
- package/dist/effect/Effect.d.ts +91 -22
- package/dist/effect/Effect.d.ts.map +1 -1
- package/dist/effect/Effect.js +126 -38
- package/dist/effect/Effect.js.map +1 -1
- package/dist/effect/Effect.test.d.ts +2 -0
- package/dist/effect/Effect.test.d.ts.map +1 -0
- package/dist/effect/Effect.test.js +72 -0
- package/dist/effect/Effect.test.js.map +1 -0
- package/dist/effect/Error.d.ts +4 -6
- package/dist/effect/Error.d.ts.map +1 -1
- package/dist/effect/Error.js +1 -1
- package/dist/effect/Error.js.map +1 -1
- package/dist/effect/Logger.d.ts +0 -6
- package/dist/effect/Logger.d.ts.map +1 -1
- package/dist/effect/Logger.js +0 -32
- package/dist/effect/Logger.js.map +1 -1
- package/dist/effect/OtelTracer.d.ts +1 -1
- package/dist/effect/OtelTracer.d.ts.map +1 -1
- package/dist/effect/OtelTracer.js +2 -3
- package/dist/effect/OtelTracer.js.map +1 -1
- package/dist/effect/RpcClient.d.ts +7 -7
- package/dist/effect/RpcClient.d.ts.map +1 -1
- package/dist/effect/RpcClient.js +74 -54
- package/dist/effect/RpcClient.js.map +1 -1
- package/dist/effect/Schedule.d.ts +1 -1
- package/dist/effect/Schedule.d.ts.map +1 -1
- package/dist/effect/Schedule.js +2 -2
- package/dist/effect/Schedule.js.map +1 -1
- package/dist/effect/Schema/debug-diff.d.ts +1 -1
- package/dist/effect/Schema/debug-diff.d.ts.map +1 -1
- package/dist/effect/Schema/debug-diff.js +9 -10
- package/dist/effect/Schema/debug-diff.js.map +1 -1
- package/dist/effect/Schema/debug-diff.test.js +7 -4
- package/dist/effect/Schema/debug-diff.test.js.map +1 -1
- package/dist/effect/Schema/index.d.ts +28 -18
- package/dist/effect/Schema/index.d.ts.map +1 -1
- package/dist/effect/Schema/index.js +55 -28
- package/dist/effect/Schema/index.js.map +1 -1
- package/dist/effect/Stream.d.ts +13 -5
- package/dist/effect/Stream.d.ts.map +1 -1
- package/dist/effect/Stream.js +19 -12
- package/dist/effect/Stream.js.map +1 -1
- package/dist/effect/Stream.test.js +12 -12
- package/dist/effect/Stream.test.js.map +1 -1
- package/dist/effect/Subscribable.d.ts +9 -3
- package/dist/effect/Subscribable.d.ts.map +1 -1
- package/dist/effect/Subscribable.js +20 -35
- package/dist/effect/Subscribable.js.map +1 -1
- package/dist/effect/SubscriptionRef.d.ts +5 -6
- package/dist/effect/SubscriptionRef.d.ts.map +1 -1
- package/dist/effect/SubscriptionRef.js +2 -3
- package/dist/effect/SubscriptionRef.js.map +1 -1
- package/dist/effect/TaskTracing.d.ts +1 -1
- package/dist/effect/TaskTracing.d.ts.map +1 -1
- package/dist/effect/TaskTracing.js +12 -16
- package/dist/effect/TaskTracing.js.map +1 -1
- package/dist/effect/WebChannel/WebChannel.d.ts +11 -30
- package/dist/effect/WebChannel/WebChannel.d.ts.map +1 -1
- package/dist/effect/WebChannel/WebChannel.js +70 -125
- package/dist/effect/WebChannel/WebChannel.js.map +1 -1
- package/dist/effect/WebChannel/WebChannel.test.js +16 -16
- package/dist/effect/WebChannel/WebChannel.test.js.map +1 -1
- package/dist/effect/WebChannel/broadcastChannelWithAck.d.ts.map +1 -1
- package/dist/effect/WebChannel/broadcastChannelWithAck.js +20 -13
- package/dist/effect/WebChannel/broadcastChannelWithAck.js.map +1 -1
- package/dist/effect/WebChannel/common.d.ts +17 -18
- package/dist/effect/WebChannel/common.d.ts.map +1 -1
- package/dist/effect/WebChannel/common.js +7 -7
- package/dist/effect/WebChannel/common.js.map +1 -1
- package/dist/effect/WebSocket.d.ts +4 -6
- package/dist/effect/WebSocket.d.ts.map +1 -1
- package/dist/effect/WebSocket.js +17 -17
- package/dist/effect/WebSocket.js.map +1 -1
- package/dist/effect/WebSocket.test.js +2 -2
- package/dist/effect/WebSocket.test.js.map +1 -1
- package/dist/effect/mod.browser-build.test.d.ts +2 -0
- package/dist/effect/mod.browser-build.test.d.ts.map +1 -0
- package/dist/effect/mod.browser-build.test.js +18 -0
- package/dist/effect/mod.browser-build.test.js.map +1 -0
- package/dist/effect/mod.d.ts +30 -0
- package/dist/effect/mod.d.ts.map +1 -0
- package/dist/effect/mod.js +30 -0
- package/dist/effect/mod.js.map +1 -0
- package/dist/effect/spanEvent.d.ts +12 -0
- package/dist/effect/spanEvent.d.ts.map +1 -0
- package/dist/effect/spanEvent.js +12 -0
- package/dist/effect/spanEvent.js.map +1 -0
- package/dist/effect/spanEvent.test.d.ts +2 -0
- package/dist/effect/spanEvent.test.d.ts.map +1 -0
- package/dist/effect/spanEvent.test.js +79 -0
- package/dist/effect/spanEvent.test.js.map +1 -0
- package/dist/env.d.ts.map +1 -1
- package/dist/env.js +1 -1
- package/dist/env.js.map +1 -1
- package/dist/fast-deep-equal.d.ts.map +1 -1
- package/dist/fast-deep-equal.js +9 -10
- package/dist/fast-deep-equal.js.map +1 -1
- package/dist/global.d.ts +3 -0
- package/dist/global.d.ts.map +1 -1
- package/dist/global.js.map +1 -1
- package/dist/misc.d.ts +9 -1
- package/dist/misc.d.ts.map +1 -1
- package/dist/misc.js +12 -4
- package/dist/misc.js.map +1 -1
- package/dist/mod.d.ts +177 -20
- package/dist/mod.d.ts.map +1 -1
- package/dist/mod.js +159 -22
- package/dist/mod.js.map +1 -1
- package/dist/node/mod.d.ts +45 -6
- package/dist/node/mod.d.ts.map +1 -1
- package/dist/node/mod.js +51 -9
- package/dist/node/mod.js.map +1 -1
- package/dist/object/index.d.ts +0 -9
- package/dist/object/index.d.ts.map +1 -1
- package/dist/object/index.js +0 -9
- package/dist/object/index.js.map +1 -1
- package/dist/object/stringify-object.js +2 -2
- package/dist/object/stringify-object.js.map +1 -1
- package/dist/object/stringify-object.test.js.map +1 -1
- package/dist/qr.d.ts +38 -0
- package/dist/qr.d.ts.map +1 -0
- package/dist/qr.js +109 -0
- package/dist/qr.js.map +1 -0
- package/dist/set.js +1 -1
- package/dist/set.js.map +1 -1
- package/dist/string.d.ts +0 -2
- package/dist/string.d.ts.map +1 -1
- package/dist/string.js +0 -2
- package/dist/string.js.map +1 -1
- package/package.json +61 -55
- package/src/NoopTracer.test.ts +20 -0
- package/src/NoopTracer.ts +28 -37
- package/src/binary.ts +1 -1
- package/src/browser/Opfs/Opfs.ts +463 -0
- package/src/browser/Opfs/debug-utils.ts +154 -0
- package/src/browser/Opfs/mod.ts +3 -0
- package/src/browser/Opfs/utils.ts +297 -0
- package/src/browser/QuotaExceededError.ts +60 -0
- package/src/browser/WebChannelBrowser.ts +145 -0
- package/src/browser/WebError.test.ts +66 -0
- package/src/browser/WebError.ts +587 -0
- package/src/{effect → browser}/WebLock.ts +27 -28
- package/src/{browser.ts → browser/detect.ts} +6 -6
- package/src/browser/mod.ts +8 -0
- package/src/cuid/cuid.browser.ts +1 -1
- package/src/cuid/cuid.node.ts +1 -1
- package/src/effect/Debug.ts +477 -0
- package/src/effect/Effect.test.ts +117 -0
- package/src/effect/Effect.ts +174 -79
- package/src/effect/Error.ts +1 -1
- package/src/effect/Logger.ts +0 -48
- package/src/effect/OtelTracer.ts +2 -3
- package/src/effect/RpcClient.ts +85 -65
- package/src/effect/Schedule.ts +4 -5
- package/src/effect/Schema/debug-diff.test.ts +7 -7
- package/src/effect/Schema/debug-diff.ts +11 -12
- package/src/effect/Schema/index.ts +121 -57
- package/src/effect/Stream.test.ts +14 -13
- package/src/effect/Stream.ts +23 -16
- package/src/effect/Subscribable.ts +31 -42
- package/src/effect/SubscriptionRef.ts +9 -13
- package/src/effect/TaskTracing.ts +13 -16
- package/src/effect/WebChannel/WebChannel.test.ts +24 -24
- package/src/effect/WebChannel/WebChannel.ts +102 -192
- package/src/effect/WebChannel/broadcastChannelWithAck.ts +25 -15
- package/src/effect/WebChannel/common.ts +16 -17
- package/src/effect/WebSocket.test.ts +2 -2
- package/src/effect/WebSocket.ts +20 -16
- package/src/effect/mod.browser-build.test.ts +19 -0
- package/src/effect/{index.ts → mod.ts} +43 -69
- package/src/effect/spanEvent.test.ts +92 -0
- package/src/effect/spanEvent.ts +17 -0
- package/src/env.ts +2 -1
- package/src/fast-deep-equal.ts +9 -10
- package/src/global.ts +4 -2
- package/src/misc.ts +13 -5
- package/src/mod.ts +188 -32
- package/src/node/mod.ts +57 -12
- package/src/object/index.ts +0 -24
- package/src/object/stringify-object.test.ts +1 -0
- package/src/object/stringify-object.ts +2 -2
- package/src/qr.ts +125 -0
- package/src/set.ts +1 -1
- package/src/string.ts +0 -3
- package/dist/.tsbuildinfo.json +0 -1
- package/dist/browser.d.ts.map +0 -1
- package/dist/browser.js.map +0 -1
- package/dist/effect/BucketQueue.d.ts +0 -20
- package/dist/effect/BucketQueue.d.ts.map +0 -1
- package/dist/effect/BucketQueue.js +0 -34
- package/dist/effect/BucketQueue.js.map +0 -1
- package/dist/effect/Scheduler.d.ts +0 -4
- package/dist/effect/Scheduler.d.ts.map +0 -1
- package/dist/effect/Scheduler.js +0 -9
- package/dist/effect/Scheduler.js.map +0 -1
- package/dist/effect/ServiceContext.d.ts +0 -37
- package/dist/effect/ServiceContext.d.ts.map +0 -1
- package/dist/effect/ServiceContext.js +0 -55
- package/dist/effect/ServiceContext.js.map +0 -1
- package/dist/effect/WebLock.d.ts.map +0 -1
- package/dist/effect/WebLock.js.map +0 -1
- package/dist/effect/index.d.ts +0 -32
- package/dist/effect/index.d.ts.map +0 -1
- package/dist/effect/index.js +0 -35
- package/dist/effect/index.js.map +0 -1
- package/dist/guards.d.ts +0 -6
- package/dist/guards.d.ts.map +0 -1
- package/dist/guards.js +0 -6
- package/dist/guards.js.map +0 -1
- package/dist/node/ChildProcessRunner/ChildProcessRunner.d.ts +0 -4
- package/dist/node/ChildProcessRunner/ChildProcessRunner.d.ts.map +0 -1
- package/dist/node/ChildProcessRunner/ChildProcessRunner.js +0 -122
- package/dist/node/ChildProcessRunner/ChildProcessRunner.js.map +0 -1
- package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/ChildProcessRunner.test.d.ts +0 -2
- package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/ChildProcessRunner.test.d.ts.map +0 -1
- package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/ChildProcessRunner.test.js +0 -218
- package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/ChildProcessRunner.test.js.map +0 -1
- package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/schema.d.ts +0 -84
- package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/schema.d.ts.map +0 -1
- package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/schema.js +0 -68
- package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/schema.js.map +0 -1
- package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/serializedWorker.d.ts +0 -2
- package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/serializedWorker.d.ts.map +0 -1
- package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/serializedWorker.js +0 -52
- package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/serializedWorker.js.map +0 -1
- package/dist/node/ChildProcessRunner/ChildProcessWorker.d.ts +0 -23
- package/dist/node/ChildProcessRunner/ChildProcessWorker.d.ts.map +0 -1
- package/dist/node/ChildProcessRunner/ChildProcessWorker.js +0 -138
- package/dist/node/ChildProcessRunner/ChildProcessWorker.js.map +0 -1
- package/dist/object/omit.d.ts +0 -3
- package/dist/object/omit.d.ts.map +0 -1
- package/dist/object/omit.js +0 -14
- package/dist/object/omit.js.map +0 -1
- package/dist/object/pick.d.ts +0 -14
- package/dist/object/pick.d.ts.map +0 -1
- package/dist/object/pick.js +0 -17
- package/dist/object/pick.js.map +0 -1
- package/dist/time.d.ts +0 -12
- package/dist/time.d.ts.map +0 -1
- package/dist/time.js +0 -22
- package/dist/time.js.map +0 -1
- package/src/effect/BucketQueue.ts +0 -49
- package/src/effect/Scheduler.ts +0 -13
- package/src/effect/ServiceContext.ts +0 -108
- package/src/guards.ts +0 -8
- package/src/node/ChildProcessRunner/ChildProcessRunner.ts +0 -135
- package/src/node/ChildProcessRunner/ChildProcessRunnerTest/ChildProcessRunner.test.ts +0 -302
- package/src/node/ChildProcessRunner/ChildProcessRunnerTest/schema.ts +0 -78
- package/src/node/ChildProcessRunner/ChildProcessRunnerTest/serializedWorker.ts +0 -66
- package/src/node/ChildProcessRunner/ChildProcessWorker.ts +0 -168
- package/src/object/omit.ts +0 -17
- package/src/object/pick.ts +0 -27
- package/src/time.ts +0 -25
- /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
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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
|
-
|
|
31
|
-
export type
|
|
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
|
-
//
|
|
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
|
|
97
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
/**
|
|
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
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
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 {
|
|
3
|
+
import { NodeFileSystem } from '@effect/platform-node'
|
|
4
|
+
import { Effect, FileSystem, Layer, Option } from 'effect'
|
|
4
5
|
|
|
5
|
-
import { OtelTracer, UnknownError } from '../effect/
|
|
6
|
+
import { OtelTracer, Tracer, UnknownError } from '../effect/mod.ts'
|
|
6
7
|
import { makeNoopTracer } from '../NoopTracer.ts'
|
|
7
8
|
|
|
8
|
-
export * as Cli from '
|
|
9
|
-
export * as SocketServer from '
|
|
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.
|
|
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 =
|
|
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))
|
package/src/object/index.ts
CHANGED
|
@@ -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
|
|
@@ -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 &&
|
|
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
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
|