@livestore/utils 0.0.0-snapshot-909cdd1ac2fd591945c2be2b0f53e14d87f3c9d4

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 (169) hide show
  1. package/dist/.tsbuildinfo.json +1 -0
  2. package/dist/Deferred.d.ts +10 -0
  3. package/dist/Deferred.d.ts.map +1 -0
  4. package/dist/Deferred.js +21 -0
  5. package/dist/Deferred.js.map +1 -0
  6. package/dist/NoopTracer.d.ts +10 -0
  7. package/dist/NoopTracer.d.ts.map +1 -0
  8. package/dist/NoopTracer.js +52 -0
  9. package/dist/NoopTracer.js.map +1 -0
  10. package/dist/base64.d.ts +13 -0
  11. package/dist/base64.d.ts.map +1 -0
  12. package/dist/base64.js +117 -0
  13. package/dist/base64.js.map +1 -0
  14. package/dist/browser.d.ts +3 -0
  15. package/dist/browser.d.ts.map +1 -0
  16. package/dist/browser.js +28 -0
  17. package/dist/browser.js.map +1 -0
  18. package/dist/cuid/cuid.browser.d.ts +18 -0
  19. package/dist/cuid/cuid.browser.d.ts.map +1 -0
  20. package/dist/cuid/cuid.browser.js +80 -0
  21. package/dist/cuid/cuid.browser.js.map +1 -0
  22. package/dist/cuid/cuid.node.d.ts +18 -0
  23. package/dist/cuid/cuid.node.d.ts.map +1 -0
  24. package/dist/cuid/cuid.node.js +83 -0
  25. package/dist/cuid/cuid.node.js.map +1 -0
  26. package/dist/effect/Effect.d.ts +28 -0
  27. package/dist/effect/Effect.d.ts.map +1 -0
  28. package/dist/effect/Effect.js +82 -0
  29. package/dist/effect/Effect.js.map +1 -0
  30. package/dist/effect/Error.d.ts +11 -0
  31. package/dist/effect/Error.d.ts.map +1 -0
  32. package/dist/effect/Error.js +7 -0
  33. package/dist/effect/Error.js.map +1 -0
  34. package/dist/effect/Schedule.d.ts +4 -0
  35. package/dist/effect/Schedule.d.ts.map +1 -0
  36. package/dist/effect/Schedule.js +5 -0
  37. package/dist/effect/Schedule.js.map +1 -0
  38. package/dist/effect/Scheduler.d.ts +4 -0
  39. package/dist/effect/Scheduler.d.ts.map +1 -0
  40. package/dist/effect/Scheduler.js +10 -0
  41. package/dist/effect/Scheduler.js.map +1 -0
  42. package/dist/effect/Schema/debug-diff.d.ts +12 -0
  43. package/dist/effect/Schema/debug-diff.d.ts.map +1 -0
  44. package/dist/effect/Schema/debug-diff.js +51 -0
  45. package/dist/effect/Schema/debug-diff.js.map +1 -0
  46. package/dist/effect/Schema/debug-diff.test.d.ts +2 -0
  47. package/dist/effect/Schema/debug-diff.test.d.ts.map +1 -0
  48. package/dist/effect/Schema/debug-diff.test.js +91 -0
  49. package/dist/effect/Schema/debug-diff.test.js.map +1 -0
  50. package/dist/effect/Schema/index.d.ts +18 -0
  51. package/dist/effect/Schema/index.d.ts.map +1 -0
  52. package/dist/effect/Schema/index.js +29 -0
  53. package/dist/effect/Schema/index.js.map +1 -0
  54. package/dist/effect/Schema/msgpack.d.ts +3 -0
  55. package/dist/effect/Schema/msgpack.d.ts.map +1 -0
  56. package/dist/effect/Schema/msgpack.js +7 -0
  57. package/dist/effect/Schema/msgpack.js.map +1 -0
  58. package/dist/effect/ServiceContext.d.ts +37 -0
  59. package/dist/effect/ServiceContext.d.ts.map +1 -0
  60. package/dist/effect/ServiceContext.js +55 -0
  61. package/dist/effect/ServiceContext.js.map +1 -0
  62. package/dist/effect/Stream.d.ts +10 -0
  63. package/dist/effect/Stream.d.ts.map +1 -0
  64. package/dist/effect/Stream.js +17 -0
  65. package/dist/effect/Stream.js.map +1 -0
  66. package/dist/effect/SubscriptionRef.d.ts +11 -0
  67. package/dist/effect/SubscriptionRef.d.ts.map +1 -0
  68. package/dist/effect/SubscriptionRef.js +5 -0
  69. package/dist/effect/SubscriptionRef.js.map +1 -0
  70. package/dist/effect/WebChannel.d.ts +30 -0
  71. package/dist/effect/WebChannel.d.ts.map +1 -0
  72. package/dist/effect/WebChannel.js +44 -0
  73. package/dist/effect/WebChannel.js.map +1 -0
  74. package/dist/effect/WebLock.d.ts +9 -0
  75. package/dist/effect/WebLock.d.ts.map +1 -0
  76. package/dist/effect/WebLock.js +73 -0
  77. package/dist/effect/WebLock.js.map +1 -0
  78. package/dist/effect/index.d.ts +21 -0
  79. package/dist/effect/index.d.ts.map +1 -0
  80. package/dist/effect/index.js +20 -0
  81. package/dist/effect/index.js.map +1 -0
  82. package/dist/fast-deep-equal.d.ts +2 -0
  83. package/dist/fast-deep-equal.d.ts.map +1 -0
  84. package/dist/fast-deep-equal.js +79 -0
  85. package/dist/fast-deep-equal.js.map +1 -0
  86. package/dist/global.d.ts +5 -0
  87. package/dist/global.d.ts.map +1 -0
  88. package/dist/global.js +2 -0
  89. package/dist/global.js.map +1 -0
  90. package/dist/guards.d.ts +6 -0
  91. package/dist/guards.d.ts.map +1 -0
  92. package/dist/guards.js +6 -0
  93. package/dist/guards.js.map +1 -0
  94. package/dist/index.d.ts +76 -0
  95. package/dist/index.d.ts.map +1 -0
  96. package/dist/index.js +176 -0
  97. package/dist/index.js.map +1 -0
  98. package/dist/misc.d.ts +3 -0
  99. package/dist/misc.d.ts.map +1 -0
  100. package/dist/misc.js +24 -0
  101. package/dist/misc.js.map +1 -0
  102. package/dist/nanoid/index.d.ts +2 -0
  103. package/dist/nanoid/index.d.ts.map +1 -0
  104. package/dist/nanoid/index.js +2 -0
  105. package/dist/nanoid/index.js.map +1 -0
  106. package/dist/object/index.d.ts +10 -0
  107. package/dist/object/index.d.ts.map +1 -0
  108. package/dist/object/index.js +10 -0
  109. package/dist/object/index.js.map +1 -0
  110. package/dist/object/omit.d.ts +3 -0
  111. package/dist/object/omit.d.ts.map +1 -0
  112. package/dist/object/omit.js +14 -0
  113. package/dist/object/omit.js.map +1 -0
  114. package/dist/object/pick.d.ts +14 -0
  115. package/dist/object/pick.d.ts.map +1 -0
  116. package/dist/object/pick.js +17 -0
  117. package/dist/object/pick.js.map +1 -0
  118. package/dist/promise.d.ts +6 -0
  119. package/dist/promise.d.ts.map +1 -0
  120. package/dist/promise.js +27 -0
  121. package/dist/promise.js.map +1 -0
  122. package/dist/set.d.ts +2 -0
  123. package/dist/set.d.ts.map +1 -0
  124. package/dist/set.js +10 -0
  125. package/dist/set.js.map +1 -0
  126. package/dist/string.d.ts +5 -0
  127. package/dist/string.d.ts.map +1 -0
  128. package/dist/string.js +8 -0
  129. package/dist/string.js.map +1 -0
  130. package/dist/time.d.ts +12 -0
  131. package/dist/time.d.ts.map +1 -0
  132. package/dist/time.js +22 -0
  133. package/dist/time.js.map +1 -0
  134. package/package.json +74 -0
  135. package/src/Deferred.ts +24 -0
  136. package/src/NoopTracer.ts +75 -0
  137. package/src/ambient.d.ts +3 -0
  138. package/src/base64.ts +123 -0
  139. package/src/browser.ts +32 -0
  140. package/src/cuid/cuid.browser.ts +95 -0
  141. package/src/cuid/cuid.node.ts +103 -0
  142. package/src/effect/Effect.ts +180 -0
  143. package/src/effect/Error.ts +6 -0
  144. package/src/effect/Schedule.ts +10 -0
  145. package/src/effect/Scheduler.ts +14 -0
  146. package/src/effect/Schema/debug-diff.test.ts +102 -0
  147. package/src/effect/Schema/debug-diff.ts +58 -0
  148. package/src/effect/Schema/index.ts +58 -0
  149. package/src/effect/Schema/msgpack.ts +8 -0
  150. package/src/effect/ServiceContext.ts +108 -0
  151. package/src/effect/Stream.ts +63 -0
  152. package/src/effect/SubscriptionRef.ts +22 -0
  153. package/src/effect/WebChannel.ts +116 -0
  154. package/src/effect/WebLock.ts +95 -0
  155. package/src/effect/index.ts +91 -0
  156. package/src/fast-deep-equal.ts +72 -0
  157. package/src/global.ts +5 -0
  158. package/src/guards.ts +8 -0
  159. package/src/index.ts +240 -0
  160. package/src/misc.ts +26 -0
  161. package/src/nanoid/index.ts +1 -0
  162. package/src/object/index.ts +24 -0
  163. package/src/object/omit.ts +17 -0
  164. package/src/object/pick.ts +27 -0
  165. package/src/promise.ts +43 -0
  166. package/src/set.ts +10 -0
  167. package/src/string.ts +9 -0
  168. package/src/time.ts +25 -0
  169. package/tsconfig.json +10 -0
@@ -0,0 +1,95 @@
1
+ /* eslint-disable unicorn/prefer-global-this */
2
+ /**
3
+ * Based on:
4
+ *
5
+ * cuid.js
6
+ * Collision-resistant UID generator for browsers and node.
7
+ * Sequential for fast db lookups and recency sorting.
8
+ * Safe for element IDs and server-side lookups.
9
+ *
10
+ * Extracted from CLCTR
11
+ *
12
+ * Copyright (c) Eric Elliott 2012
13
+ * MIT License
14
+ */
15
+
16
+ const lim = Math.pow(2, 32) - 1
17
+
18
+ const crypto = globalThis.crypto
19
+
20
+ const getRandomValue = () => {
21
+ return Math.abs(crypto.getRandomValues(new Uint32Array(1))[0]! / lim)
22
+ }
23
+
24
+ const pad = (num: number | string, size: number) => {
25
+ const s = '000000000' + num
26
+ return s.slice(s.length - size)
27
+ }
28
+
29
+ const env = typeof window === 'object' ? window : self
30
+ const globalCount = Object.keys(env).length
31
+
32
+ // To make it work in React Native https://github.com/paralleldrive/cuid/issues/54#issuecomment-222957293
33
+ const clientId =
34
+ navigator.product === 'ReactNative'
35
+ ? 'rn'
36
+ : pad(navigator.userAgent.length.toString(36) + globalCount.toString(36), 4)
37
+
38
+ const fingerprint = () => clientId
39
+
40
+ let c = 0
41
+ const blockSize = 4
42
+ const base = 36
43
+ const discreteValues = Math.pow(base, blockSize)
44
+
45
+ const randomBlock = () => {
46
+ return pad(Math.trunc(getRandomValue() * discreteValues).toString(base), blockSize)
47
+ }
48
+
49
+ const safeCounter = () => {
50
+ c = c < discreteValues ? c : 0
51
+ c++ // this is not subliminal
52
+ return c - 1
53
+ }
54
+
55
+ export const cuid = () => {
56
+ // Starting with a lowercase letter makes
57
+ // it HTML element ID friendly.
58
+ const letter = 'c', // hard-coded allows for sequential access
59
+ // timestamp
60
+ // warning: this exposes the exact date and time
61
+ // that the uid was created.
62
+ timestamp = Date.now().toString(base),
63
+ // Prevent same-machine collisions.
64
+ counter = pad(safeCounter().toString(base), blockSize),
65
+ // A few chars to generate distinct ids for different
66
+ // clients (so different computers are far less
67
+ // likely to generate the same id)
68
+ print = fingerprint(),
69
+ // Grab some more chars from Math.random()
70
+ random = randomBlock() + randomBlock()
71
+
72
+ return letter + timestamp + counter + print + random
73
+ }
74
+
75
+ export const slug = () => {
76
+ const date = Date.now().toString(36),
77
+ counter = safeCounter().toString(36).slice(-4),
78
+ print = fingerprint().slice(0, 1) + fingerprint().slice(-1),
79
+ random = randomBlock().slice(-2)
80
+
81
+ return date.slice(-2) + counter + print + random
82
+ }
83
+
84
+ export const isCuid = (stringToCheck: string) => {
85
+ if (typeof stringToCheck !== 'string') return false
86
+ if (stringToCheck.startsWith('c')) return true
87
+ return false
88
+ }
89
+
90
+ export const isSlug = (stringToCheck: string) => {
91
+ if (typeof stringToCheck !== 'string') return false
92
+ const stringLength = stringToCheck.length
93
+ if (stringLength >= 7 && stringLength <= 10) return true
94
+ return false
95
+ }
@@ -0,0 +1,103 @@
1
+ /**
2
+ * Based on:
3
+ *
4
+ * cuid.js
5
+ * Collision-resistant UID generator for browsers and node.
6
+ * Sequential for fast db lookups and recency sorting.
7
+ * Safe for element IDs and server-side lookups.
8
+ *
9
+ * Extracted from CLCTR
10
+ *
11
+ * Copyright (c) Eric Elliott 2012
12
+ * MIT License
13
+ */
14
+
15
+ import crypto from 'node:crypto'
16
+ import os from 'node:os'
17
+
18
+ const lim = Math.pow(2, 32) - 1
19
+
20
+ const getRandomValue = () => {
21
+ return Math.abs(crypto.randomBytes(4).readInt32BE() / lim)
22
+ }
23
+
24
+ const pad = (num: number | string, size: number) => {
25
+ const s = '000000000' + num
26
+ return s.slice(s.length - size)
27
+ }
28
+
29
+ const fingerprint = () => {
30
+ const padding = 2,
31
+ pid = pad(process.pid.toString(36), padding),
32
+ hostname = os.hostname(),
33
+ length = hostname.length,
34
+ hostId = pad(
35
+ hostname
36
+ .split('')
37
+ .reduce((prev, char) => {
38
+ // eslint-disable-next-line unicorn/prefer-code-point
39
+ return +prev + char.charCodeAt(0)
40
+ }, +length + 36)
41
+ .toString(36),
42
+ padding,
43
+ )
44
+
45
+ return pid + hostId
46
+ }
47
+
48
+ let c = 0
49
+ const blockSize = 4
50
+ const base = 36
51
+ const discreteValues = Math.pow(base, blockSize)
52
+
53
+ const randomBlock = () => {
54
+ return pad(Math.trunc(getRandomValue() * discreteValues).toString(base), blockSize)
55
+ }
56
+
57
+ const safeCounter = () => {
58
+ c = c < discreteValues ? c : 0
59
+ c++ // this is not subliminal
60
+ return c - 1
61
+ }
62
+
63
+ export const cuid = () => {
64
+ // Starting with a lowercase letter makes
65
+ // it HTML element ID friendly.
66
+ const letter = 'c', // hard-coded allows for sequential access
67
+ // timestamp
68
+ // warning: this exposes the exact date and time
69
+ // that the uid was created.
70
+ timestamp = Date.now().toString(base),
71
+ // Prevent same-machine collisions.
72
+ counter = pad(safeCounter().toString(base), blockSize),
73
+ // A few chars to generate distinct ids for different
74
+ // clients (so different computers are far less
75
+ // likely to generate the same id)
76
+ print = fingerprint(),
77
+ // Grab some more chars from Math.random()
78
+ random = randomBlock() + randomBlock()
79
+
80
+ return letter + timestamp + counter + print + random
81
+ }
82
+
83
+ export const slug = () => {
84
+ const date = Date.now().toString(36),
85
+ counter = safeCounter().toString(36).slice(-4),
86
+ print = fingerprint().slice(0, 1) + fingerprint().slice(-1),
87
+ random = randomBlock().slice(-2)
88
+
89
+ return date.slice(-2) + counter + print + random
90
+ }
91
+
92
+ export const isCuid = (stringToCheck: string) => {
93
+ if (typeof stringToCheck !== 'string') return false
94
+ if (stringToCheck.startsWith('c')) return true
95
+ return false
96
+ }
97
+
98
+ export const isSlug = (stringToCheck: string) => {
99
+ if (typeof stringToCheck !== 'string') return false
100
+ const stringLength = stringToCheck.length
101
+ if (stringLength >= 7 && stringLength <= 10) return true
102
+ return false
103
+ }
@@ -0,0 +1,180 @@
1
+ import * as OtelTracer from '@effect/opentelemetry/Tracer'
2
+ import type { Context, Duration, Scope } from 'effect'
3
+ import { Cause, Deferred, Effect, Fiber, pipe } from 'effect'
4
+ import type { UnknownException } from 'effect/Cause'
5
+ import { log } from 'effect/Console'
6
+ import type { LazyArg } from 'effect/Function'
7
+
8
+ import { isNonEmptyString, isPromise } from '../index.js'
9
+ import { UnknownError } from './Error.js'
10
+
11
+ export * from 'effect/Effect'
12
+
13
+ // export const log = <A>(message: A, ...rest: any[]): Effect.Effect<void> =>
14
+ // Effect.sync(() => {
15
+ // console.log(message, ...rest)
16
+ // })
17
+
18
+ // export const logWarn = <A>(message: A, ...rest: any[]): Effect.Effect<void> =>
19
+ // Effect.sync(() => {
20
+ // console.warn(message, ...rest)
21
+ // })
22
+
23
+ // export const logError = <A>(message: A, ...rest: any[]): Effect.Effect<void> =>
24
+ // Effect.sync(() => {
25
+ // console.error(message, ...rest)
26
+ // })
27
+
28
+ export const tryAll = <Res>(
29
+ fn: () => Res,
30
+ ): Res extends Effect.Effect<infer A, infer E, never>
31
+ ? Effect.Effect<A, E | UnknownException, never>
32
+ : Res extends Promise<infer A>
33
+ ? Effect.Effect<A, UnknownException, never>
34
+ : Effect.Effect<Res, UnknownException, never> =>
35
+ Effect.try(() => fn()).pipe(
36
+ Effect.andThen((fnRes) =>
37
+ Effect.isEffect(fnRes)
38
+ ? (fnRes as any as Effect.Effect<any>)
39
+ : isPromise(fnRes)
40
+ ? Effect.promise(() => fnRes)
41
+ : Effect.succeed(fnRes),
42
+ ),
43
+ ) as any
44
+
45
+ const getThreadName = () => {
46
+ // @ts-expect-error TODO fix types
47
+ const globalName = globalThis.name
48
+ return isNonEmptyString(globalName)
49
+ ? globalName
50
+ : // eslint-disable-next-line unicorn/prefer-global-this
51
+ typeof window === 'object'
52
+ ? 'Browser Main Thread'
53
+ : 'unknown-thread'
54
+ }
55
+
56
+ export const acquireReleaseLog = (label: string) =>
57
+ Effect.acquireRelease(Effect.log(`${label} acquire`), (_, ex) => Effect.log(`${label} release`, ex))
58
+
59
+ export const logBefore =
60
+ (...msgs: any[]) =>
61
+ <A, E, R>(eff: Effect.Effect<A, E, R>): Effect.Effect<A, E, R> =>
62
+ Effect.andThen(Effect.log(...msgs), eff)
63
+
64
+ /** Logs both on errors and defects */
65
+ export const tapCauseLogPretty = <R, E, A>(eff: Effect.Effect<A, E, R>): Effect.Effect<A, E, R> =>
66
+ Effect.tapErrorCause(eff, (cause) =>
67
+ Effect.gen(function* () {
68
+ if (Cause.isInterruptedOnly(cause)) {
69
+ // console.log('interrupted', Cause.pretty(err), err)
70
+ return
71
+ }
72
+
73
+ const span = yield* OtelTracer.currentOtelSpan.pipe(
74
+ Effect.catchTag('NoSuchElementException', (_) => Effect.succeed(undefined)),
75
+ )
76
+
77
+ const threadName = getThreadName()
78
+ const firstErrLine = cause.toString().split('\n')[0]
79
+ yield* Effect.logError(`Error on ${threadName}: ${firstErrLine}`, cause).pipe((_) =>
80
+ span === undefined
81
+ ? _
82
+ : Effect.annotateLogs({ spanId: span.spanContext().spanId, traceId: span.spanContext().traceId })(_),
83
+ )
84
+ }),
85
+ )
86
+
87
+ export const logWarnIfTakesLongerThan =
88
+ ({ label, duration }: { label: string; duration: Duration.DurationInput }) =>
89
+ <R, E, A>(eff: Effect.Effect<A, E, R>): Effect.Effect<A, E, R> =>
90
+ Effect.gen(function* () {
91
+ const runtime = yield* Effect.runtime<never>()
92
+
93
+ let timedOut = false
94
+
95
+ const timeoutFiber = Effect.sleep(duration).pipe(
96
+ Effect.tap(() => {
97
+ timedOut = true
98
+ // TODO include span info
99
+ return Effect.logWarning(`${label}: Took longer than ${duration}ms`)
100
+ }),
101
+ Effect.provide(runtime),
102
+ Effect.runFork,
103
+ )
104
+
105
+ const start = Date.now()
106
+ const res = yield* eff
107
+
108
+ if (timedOut) {
109
+ const end = Date.now()
110
+ yield* Effect.logWarning(`${label}: Actual duration: ${end - start}ms`)
111
+ }
112
+
113
+ yield* Fiber.interrupt(timeoutFiber)
114
+
115
+ return res
116
+ })
117
+
118
+ export const logDuration =
119
+ (label: string) =>
120
+ <R, E, A>(eff: Effect.Effect<A, E, R>): Effect.Effect<A, E, R> =>
121
+ Effect.gen(function* () {
122
+ const start = Date.now()
123
+ const res = yield* eff
124
+ const end = Date.now()
125
+ yield* Effect.log(`${label}: ${end - start}ms`)
126
+ return res
127
+ })
128
+
129
+ export const tapSync =
130
+ <A>(tapFn: (a: A) => unknown) =>
131
+ <R, E>(eff: Effect.Effect<A, E, R>): Effect.Effect<A, E, R> =>
132
+ Effect.tap(eff, (a) => Effect.sync(() => tapFn(a)))
133
+
134
+ export const debugLogEnv = (msg?: string): Effect.Effect<Context.Context<never>> =>
135
+ pipe(
136
+ Effect.context<never>(),
137
+ Effect.tap((env) => log(msg ?? 'debugLogEnv', env)),
138
+ )
139
+
140
+ export const timeoutDie =
141
+ <E1>(options: { onTimeout: LazyArg<E1>; duration: Duration.DurationInput }) =>
142
+ <R, E, A>(self: Effect.Effect<A, E, R>): Effect.Effect<A, E, R> =>
143
+ Effect.orDie(Effect.timeoutFail(options)(self))
144
+
145
+ export const timeoutDieMsg =
146
+ (options: { error: string; duration: Duration.DurationInput }) =>
147
+ <R, E, A>(self: Effect.Effect<A, E, R>): Effect.Effect<A, E, R> =>
148
+ Effect.orDie(
149
+ Effect.timeoutFail({ onTimeout: () => new UnknownError({ cause: options.error }), duration: options.duration })(
150
+ self,
151
+ ),
152
+ )
153
+
154
+ export const toForkedDeferred = <R, E, A>(
155
+ eff: Effect.Effect<A, E, R>,
156
+ ): Effect.Effect<Deferred.Deferred<A, E>, never, R | Scope.Scope> =>
157
+ pipe(
158
+ Deferred.make<A, E>(),
159
+ Effect.tap((deferred) =>
160
+ pipe(
161
+ Effect.exit(eff),
162
+ Effect.flatMap((ex) => Deferred.done(deferred, ex)),
163
+ tapCauseLogPretty,
164
+ Effect.forkScoped,
165
+ ),
166
+ ),
167
+ )
168
+
169
+ export const withPerformanceMeasure =
170
+ (meaureLabel: string) =>
171
+ <R, E, A>(eff: Effect.Effect<A, E, R>): Effect.Effect<A, E, R> =>
172
+ Effect.acquireUseRelease(
173
+ Effect.sync(() => performance.mark(`${meaureLabel}:start`)),
174
+ () => eff,
175
+ () =>
176
+ Effect.sync(() => {
177
+ performance.mark(`${meaureLabel}:end`)
178
+ performance.measure(meaureLabel, `${meaureLabel}:start`, `${meaureLabel}:end`)
179
+ }),
180
+ )
@@ -0,0 +1,6 @@
1
+ import { Schema } from 'effect'
2
+
3
+ export class UnknownError extends Schema.TaggedError<'UnknownError'>()('UnknownError', {
4
+ cause: Schema.Any,
5
+ payload: Schema.optional(Schema.Any),
6
+ }) {}
@@ -0,0 +1,10 @@
1
+ import { Duration, pipe, Schedule } from 'effect'
2
+
3
+ export * from 'effect/Schedule'
4
+
5
+ export const exponentialBackoff10Sec: Schedule.Schedule<Duration.DurationInput> = pipe(
6
+ Schedule.exponential(Duration.millis(10), 4), // 10ms, 40ms, 160ms, 640ms, 2560ms, ...
7
+ Schedule.andThenEither(Schedule.spaced(Duration.seconds(1))),
8
+ Schedule.compose(Schedule.elapsed),
9
+ Schedule.whileOutput(Duration.lessThanOrEqualTo(Duration.seconds(10))), // max 10 seconds
10
+ )
@@ -0,0 +1,14 @@
1
+ export * from 'effect/Scheduler'
2
+
3
+ import { Scheduler } from 'effect'
4
+
5
+ // Based on https://github.com/astoilkov/main-thread-scheduling/blob/4b99c26ab96781bc35a331f5c225ad9c8a62cb95/src/utils/waitNextTask.ts#L25
6
+ export const messageChannel = (shouldYield: Scheduler.Scheduler['shouldYield'] = Scheduler.defaultShouldYield) =>
7
+ Scheduler.makeBatched((task) => {
8
+ const messageChannel = new MessageChannel()
9
+
10
+ messageChannel.port1.postMessage(undefined)
11
+
12
+ // eslint-disable-next-line unicorn/prefer-add-event-listener
13
+ messageChannel.port2.onmessage = task
14
+ }, shouldYield)
@@ -0,0 +1,102 @@
1
+ import { Schema } from 'effect'
2
+ import { describe, expect, test } from 'vitest'
3
+
4
+ import type { DiffItem } from './debug-diff.js'
5
+ import { debugDiff } from './debug-diff.js'
6
+
7
+ describe('debug-diff', () => {
8
+ test('simple object', () => {
9
+ const schema = Schema.Struct({
10
+ a: Schema.String,
11
+ b: Schema.Number,
12
+ })
13
+
14
+ const a = { a: 'hello', b: 1 }
15
+ const b = { a: 'world', b: 2 }
16
+
17
+ const diff = debugDiff(schema)(a, b)
18
+ expect(trimAst(diff)).toMatchInlineSnapshot(`
19
+ [
20
+ {
21
+ "a": "hello",
22
+ "b": "world",
23
+ "path": ".a",
24
+ },
25
+ {
26
+ "a": 1,
27
+ "b": 2,
28
+ "path": ".b",
29
+ },
30
+ ]
31
+ `)
32
+ })
33
+
34
+ test('simple object with nested object', () => {
35
+ const schema = Schema.Struct({
36
+ a: Schema.String,
37
+ b: Schema.Struct({
38
+ c: Schema.Number,
39
+ }),
40
+ })
41
+ const a = { a: 'hello', b: { c: 1 } }
42
+ const b = { a: 'world', b: { c: 2 } }
43
+ const diff = debugDiff(schema)(a, b)
44
+ expect(trimAst(diff)).toMatchInlineSnapshot(`
45
+ [
46
+ {
47
+ "a": "hello",
48
+ "b": "world",
49
+ "path": ".a",
50
+ },
51
+ {
52
+ "a": 1,
53
+ "b": 2,
54
+ "path": ".b.c",
55
+ },
56
+ ]
57
+ `)
58
+ })
59
+
60
+ test('union', () => {
61
+ const schema = Schema.Union(Schema.String, Schema.Number)
62
+ const a = 'hello'
63
+ const b = 1
64
+ const diff = debugDiff(schema)(a, b)
65
+ expect(trimAst(diff)).toMatchInlineSnapshot(`
66
+ [
67
+ {
68
+ "a": "hello",
69
+ "b": 1,
70
+ "path": "",
71
+ },
72
+ ]
73
+ `)
74
+ })
75
+
76
+ test('tagged union', () => {
77
+ const schema = Schema.Union(
78
+ Schema.Struct({ _tag: Schema.Literal('a'), a: Schema.String }),
79
+ Schema.Struct({ _tag: Schema.Literal('b'), b: Schema.Number }),
80
+ )
81
+ const a = { _tag: 'a', a: 'hello' } as const
82
+ const b = { _tag: 'b', b: 1 } as const
83
+ const diff = debugDiff(schema)(a, b)
84
+ expect(trimAst(diff)).toMatchInlineSnapshot(`
85
+ [
86
+ {
87
+ "a": {
88
+ "_tag": "a",
89
+ "a": "hello",
90
+ },
91
+ "b": {
92
+ "_tag": "b",
93
+ "b": 1,
94
+ },
95
+ "path": "",
96
+ },
97
+ ]
98
+ `)
99
+ })
100
+ })
101
+
102
+ const trimAst = (diffItems: DiffItem[]) => diffItems.map(({ ast: _ast, ...rest }) => rest)
@@ -0,0 +1,58 @@
1
+ import { Schema, SchemaAST } from 'effect'
2
+
3
+ export type DiffItem = {
4
+ path: string
5
+ a: any
6
+ b: any
7
+ ast: SchemaAST.AST
8
+ }
9
+
10
+ /**
11
+ * Diffs two values for a given schema and traverses downwards and returns a list of differences.
12
+ */
13
+ export const debugDiff =
14
+ <A, I, R>(base: Schema.Schema<A, I, R>) =>
15
+ (a: A, b: A): DiffItem[] => {
16
+ const bag = [] as DiffItem[]
17
+ debugDiffImpl(base.ast, a, b, '', bag)
18
+ return bag
19
+ }
20
+
21
+ const debugDiffImpl = (ast: SchemaAST.AST, a: any, b: any, path: string, bag: DiffItem[]) => {
22
+ const eq = Schema.equivalence({ ast } as any)
23
+ if (eq(a, b) === false) {
24
+ // bag.push({ path, a, b, ast })
25
+
26
+ if (SchemaAST.isUnion(ast)) {
27
+ if (isTaggedUnion(ast)) {
28
+ bag.push({ path, a, b, ast })
29
+ return
30
+ } else {
31
+ for (const type of ast.types) {
32
+ try {
33
+ debugDiffImpl(type, a, b, path, bag)
34
+ return
35
+ } catch {}
36
+ }
37
+ }
38
+ } else if (SchemaAST.isTypeLiteral(ast)) {
39
+ const props = SchemaAST.getPropertySignatures(ast)
40
+ for (const prop of props) {
41
+ debugDiffImpl(prop.type, a[prop.name], b[prop.name], `${path}.${prop.name.toString()}`, bag)
42
+ }
43
+ } else {
44
+ // debugger
45
+ bag.push({ path, a, b, ast })
46
+ }
47
+ }
48
+ }
49
+
50
+ const isTaggedUnion = (ast: SchemaAST.AST) => {
51
+ if (SchemaAST.isUnion(ast)) {
52
+ return ast.types.every((type) => {
53
+ if (SchemaAST.isTypeLiteral(type) === false) return false
54
+ const props = SchemaAST.getPropertySignatures(type)
55
+ return props.some((prop) => prop.name.toString() === '_tag')
56
+ })
57
+ }
58
+ }
@@ -0,0 +1,58 @@
1
+ import { Transferable } from '@effect/platform'
2
+ import { Effect, Hash, ParseResult, Schema } from 'effect'
3
+ import type { ParseError } from 'effect/ParseResult'
4
+ import type { ParseOptions } from 'effect/SchemaAST'
5
+
6
+ export * from 'effect/Schema'
7
+ export * from './debug-diff.js'
8
+ export * from './msgpack.js'
9
+
10
+ // NOTE this is a temporary workaround until Effect schema has a better way to hash schemas
11
+ // https://github.com/Effect-TS/effect/issues/2719
12
+ // TODO remove this once the issue is resolved
13
+ export const hash = (schema: Schema.Schema<any>) => {
14
+ try {
15
+ return Hash.string(JSON.stringify(schema.ast, null, 2))
16
+ } catch {
17
+ console.warn(
18
+ `Schema hashing failed, falling back to hashing the shortend schema AST string. This is less reliable and may cause false positives.`,
19
+ )
20
+ return Hash.hash(schema.ast.toString())
21
+ }
22
+ }
23
+
24
+ export const encodeWithTransferables =
25
+ <A, I, R>(schema: Schema.Schema<A, I, R>, options?: ParseOptions | undefined) =>
26
+ (a: A, overrideOptions?: ParseOptions | undefined): Effect.Effect<[I, Transferable[]], ParseError, R> =>
27
+ Effect.gen(function* () {
28
+ const collector = yield* Transferable.makeCollector
29
+
30
+ const encoded: I = yield* Schema.encode(schema, options)(a, overrideOptions).pipe(
31
+ Effect.provideService(Transferable.Collector, collector),
32
+ )
33
+
34
+ return [encoded, collector.unsafeRead() as Transferable[]]
35
+ })
36
+
37
+ export const swap = <A, I, R>(schema: Schema.Schema<A, I, R>): Schema.Schema<I, A, R> =>
38
+ Schema.transformOrFail(Schema.typeSchema(schema), Schema.encodedSchema(schema), {
39
+ decode: ParseResult.encode(schema),
40
+ encode: ParseResult.decode(schema),
41
+ })
42
+
43
+ export const Base64FromUint8Array: Schema.Schema<string, Uint8Array> = swap(Schema.Uint8ArrayFromBase64)
44
+
45
+ export interface JsonArray extends ReadonlyArray<JsonValue> {}
46
+ export interface JsonObject {
47
+ [key: string]: JsonValue
48
+ }
49
+ export type JsonValue = string | number | boolean | null | JsonObject | JsonArray
50
+
51
+ export const JsonValue: Schema.Schema<JsonValue> = Schema.Union(
52
+ Schema.String,
53
+ Schema.Number,
54
+ Schema.Boolean,
55
+ Schema.Null,
56
+ Schema.Array(Schema.suspend(() => JsonValue)),
57
+ Schema.Record({ key: Schema.String, value: Schema.suspend(() => JsonValue) }),
58
+ )
@@ -0,0 +1,8 @@
1
+ import { Schema } from 'effect'
2
+ import * as msgpack from 'msgpackr'
3
+
4
+ export const MsgPack = <A, I>(schema: Schema.Schema<A, I>) =>
5
+ Schema.transform(Schema.Uint8ArrayFromSelf, schema, {
6
+ encode: (decoded) => msgpack.pack(decoded),
7
+ decode: (encodedBytes) => msgpack.unpack(encodedBytes),
8
+ })