@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.
- package/dist/.tsbuildinfo.json +1 -0
- package/dist/Deferred.d.ts +10 -0
- package/dist/Deferred.d.ts.map +1 -0
- package/dist/Deferred.js +21 -0
- package/dist/Deferred.js.map +1 -0
- package/dist/NoopTracer.d.ts +10 -0
- package/dist/NoopTracer.d.ts.map +1 -0
- package/dist/NoopTracer.js +52 -0
- package/dist/NoopTracer.js.map +1 -0
- package/dist/base64.d.ts +13 -0
- package/dist/base64.d.ts.map +1 -0
- package/dist/base64.js +117 -0
- package/dist/base64.js.map +1 -0
- package/dist/browser.d.ts +3 -0
- package/dist/browser.d.ts.map +1 -0
- package/dist/browser.js +28 -0
- package/dist/browser.js.map +1 -0
- package/dist/cuid/cuid.browser.d.ts +18 -0
- package/dist/cuid/cuid.browser.d.ts.map +1 -0
- package/dist/cuid/cuid.browser.js +80 -0
- package/dist/cuid/cuid.browser.js.map +1 -0
- package/dist/cuid/cuid.node.d.ts +18 -0
- package/dist/cuid/cuid.node.d.ts.map +1 -0
- package/dist/cuid/cuid.node.js +83 -0
- package/dist/cuid/cuid.node.js.map +1 -0
- package/dist/effect/Effect.d.ts +28 -0
- package/dist/effect/Effect.d.ts.map +1 -0
- package/dist/effect/Effect.js +82 -0
- package/dist/effect/Effect.js.map +1 -0
- package/dist/effect/Error.d.ts +11 -0
- package/dist/effect/Error.d.ts.map +1 -0
- package/dist/effect/Error.js +7 -0
- package/dist/effect/Error.js.map +1 -0
- package/dist/effect/Schedule.d.ts +4 -0
- package/dist/effect/Schedule.d.ts.map +1 -0
- package/dist/effect/Schedule.js +5 -0
- package/dist/effect/Schedule.js.map +1 -0
- package/dist/effect/Scheduler.d.ts +4 -0
- package/dist/effect/Scheduler.d.ts.map +1 -0
- package/dist/effect/Scheduler.js +10 -0
- package/dist/effect/Scheduler.js.map +1 -0
- package/dist/effect/Schema/debug-diff.d.ts +12 -0
- package/dist/effect/Schema/debug-diff.d.ts.map +1 -0
- package/dist/effect/Schema/debug-diff.js +51 -0
- package/dist/effect/Schema/debug-diff.js.map +1 -0
- package/dist/effect/Schema/debug-diff.test.d.ts +2 -0
- package/dist/effect/Schema/debug-diff.test.d.ts.map +1 -0
- package/dist/effect/Schema/debug-diff.test.js +91 -0
- package/dist/effect/Schema/debug-diff.test.js.map +1 -0
- package/dist/effect/Schema/index.d.ts +18 -0
- package/dist/effect/Schema/index.d.ts.map +1 -0
- package/dist/effect/Schema/index.js +29 -0
- package/dist/effect/Schema/index.js.map +1 -0
- package/dist/effect/Schema/msgpack.d.ts +3 -0
- package/dist/effect/Schema/msgpack.d.ts.map +1 -0
- package/dist/effect/Schema/msgpack.js +7 -0
- package/dist/effect/Schema/msgpack.js.map +1 -0
- package/dist/effect/ServiceContext.d.ts +37 -0
- package/dist/effect/ServiceContext.d.ts.map +1 -0
- package/dist/effect/ServiceContext.js +55 -0
- package/dist/effect/ServiceContext.js.map +1 -0
- package/dist/effect/Stream.d.ts +10 -0
- package/dist/effect/Stream.d.ts.map +1 -0
- package/dist/effect/Stream.js +17 -0
- package/dist/effect/Stream.js.map +1 -0
- package/dist/effect/SubscriptionRef.d.ts +11 -0
- package/dist/effect/SubscriptionRef.d.ts.map +1 -0
- package/dist/effect/SubscriptionRef.js +5 -0
- package/dist/effect/SubscriptionRef.js.map +1 -0
- package/dist/effect/WebChannel.d.ts +30 -0
- package/dist/effect/WebChannel.d.ts.map +1 -0
- package/dist/effect/WebChannel.js +44 -0
- package/dist/effect/WebChannel.js.map +1 -0
- package/dist/effect/WebLock.d.ts +9 -0
- package/dist/effect/WebLock.d.ts.map +1 -0
- package/dist/effect/WebLock.js +73 -0
- package/dist/effect/WebLock.js.map +1 -0
- package/dist/effect/index.d.ts +21 -0
- package/dist/effect/index.d.ts.map +1 -0
- package/dist/effect/index.js +20 -0
- package/dist/effect/index.js.map +1 -0
- package/dist/fast-deep-equal.d.ts +2 -0
- package/dist/fast-deep-equal.d.ts.map +1 -0
- package/dist/fast-deep-equal.js +79 -0
- package/dist/fast-deep-equal.js.map +1 -0
- package/dist/global.d.ts +5 -0
- package/dist/global.d.ts.map +1 -0
- package/dist/global.js +2 -0
- package/dist/global.js.map +1 -0
- package/dist/guards.d.ts +6 -0
- package/dist/guards.d.ts.map +1 -0
- package/dist/guards.js +6 -0
- package/dist/guards.js.map +1 -0
- package/dist/index.d.ts +76 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +176 -0
- package/dist/index.js.map +1 -0
- package/dist/misc.d.ts +3 -0
- package/dist/misc.d.ts.map +1 -0
- package/dist/misc.js +24 -0
- package/dist/misc.js.map +1 -0
- package/dist/nanoid/index.d.ts +2 -0
- package/dist/nanoid/index.d.ts.map +1 -0
- package/dist/nanoid/index.js +2 -0
- package/dist/nanoid/index.js.map +1 -0
- package/dist/object/index.d.ts +10 -0
- package/dist/object/index.d.ts.map +1 -0
- package/dist/object/index.js +10 -0
- package/dist/object/index.js.map +1 -0
- package/dist/object/omit.d.ts +3 -0
- package/dist/object/omit.d.ts.map +1 -0
- package/dist/object/omit.js +14 -0
- package/dist/object/omit.js.map +1 -0
- package/dist/object/pick.d.ts +14 -0
- package/dist/object/pick.d.ts.map +1 -0
- package/dist/object/pick.js +17 -0
- package/dist/object/pick.js.map +1 -0
- package/dist/promise.d.ts +6 -0
- package/dist/promise.d.ts.map +1 -0
- package/dist/promise.js +27 -0
- package/dist/promise.js.map +1 -0
- package/dist/set.d.ts +2 -0
- package/dist/set.d.ts.map +1 -0
- package/dist/set.js +10 -0
- package/dist/set.js.map +1 -0
- package/dist/string.d.ts +5 -0
- package/dist/string.d.ts.map +1 -0
- package/dist/string.js +8 -0
- package/dist/string.js.map +1 -0
- package/dist/time.d.ts +12 -0
- package/dist/time.d.ts.map +1 -0
- package/dist/time.js +22 -0
- package/dist/time.js.map +1 -0
- package/package.json +74 -0
- package/src/Deferred.ts +24 -0
- package/src/NoopTracer.ts +75 -0
- package/src/ambient.d.ts +3 -0
- package/src/base64.ts +123 -0
- package/src/browser.ts +32 -0
- package/src/cuid/cuid.browser.ts +95 -0
- package/src/cuid/cuid.node.ts +103 -0
- package/src/effect/Effect.ts +180 -0
- package/src/effect/Error.ts +6 -0
- package/src/effect/Schedule.ts +10 -0
- package/src/effect/Scheduler.ts +14 -0
- package/src/effect/Schema/debug-diff.test.ts +102 -0
- package/src/effect/Schema/debug-diff.ts +58 -0
- package/src/effect/Schema/index.ts +58 -0
- package/src/effect/Schema/msgpack.ts +8 -0
- package/src/effect/ServiceContext.ts +108 -0
- package/src/effect/Stream.ts +63 -0
- package/src/effect/SubscriptionRef.ts +22 -0
- package/src/effect/WebChannel.ts +116 -0
- package/src/effect/WebLock.ts +95 -0
- package/src/effect/index.ts +91 -0
- package/src/fast-deep-equal.ts +72 -0
- package/src/global.ts +5 -0
- package/src/guards.ts +8 -0
- package/src/index.ts +240 -0
- package/src/misc.ts +26 -0
- package/src/nanoid/index.ts +1 -0
- package/src/object/index.ts +24 -0
- package/src/object/omit.ts +17 -0
- package/src/object/pick.ts +27 -0
- package/src/promise.ts +43 -0
- package/src/set.ts +10 -0
- package/src/string.ts +9 -0
- package/src/time.ts +25 -0
- 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,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
|
+
})
|