@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,108 @@
|
|
|
1
|
+
import type { Runtime } from 'effect'
|
|
2
|
+
import { Cause, Effect, Exit, Fiber, Layer, pipe, Scope } from 'effect'
|
|
3
|
+
|
|
4
|
+
export interface MainLayer<Ctx> {
|
|
5
|
+
layer: Layer.Layer<Ctx>
|
|
6
|
+
close: Effect.Effect<void>
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export const unsafeMainLayer = <Ctx>(original: Layer.Layer<Ctx>): MainLayer<Ctx> => {
|
|
10
|
+
const scope = Effect.runSync(Scope.make())
|
|
11
|
+
const layer = pipe(
|
|
12
|
+
original,
|
|
13
|
+
Layer.memoize,
|
|
14
|
+
Effect.parallelFinalizers, // NOTE this runs the layer teardown in parallel
|
|
15
|
+
Effect.provideService(Scope.Scope, scope),
|
|
16
|
+
Effect.runSync,
|
|
17
|
+
)
|
|
18
|
+
return { layer, close: Scope.close(scope, Exit.void) }
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export const make = <TStaticData, Ctx>(
|
|
22
|
+
staticData: TStaticData,
|
|
23
|
+
runtime: Runtime.Runtime<Ctx>,
|
|
24
|
+
close: Effect.Effect<void> = Effect.dieMessage('close not implemented'),
|
|
25
|
+
): ServiceContext<Ctx, TStaticData> => {
|
|
26
|
+
return {
|
|
27
|
+
provide: (self) => Effect.provide(runtime)(self),
|
|
28
|
+
runWithErrorLog: <E, A>(self: Effect.Effect<A, E, Ctx>) => runWithErrorLog(Effect.provide(runtime)(self)),
|
|
29
|
+
runSync: <E, A>(self: Effect.Effect<A, E, Ctx>) => Effect.runSync(Effect.provide(runtime)(self)),
|
|
30
|
+
runPromiseWithErrorLog: <E, A>(self: Effect.Effect<A, E, Ctx>) =>
|
|
31
|
+
runPromiseWithErrorLog(Effect.provide(runtime)(self)),
|
|
32
|
+
runPromiseExit: <E, A>(self: Effect.Effect<A, E, Ctx>) => Effect.runPromiseExit(Effect.provide(runtime)(self)),
|
|
33
|
+
runPromise: <E, A>(self: Effect.Effect<A, E, Ctx>) => Effect.runPromise(Effect.provide(runtime)(self)),
|
|
34
|
+
withRuntime: (fn) => fn(runtime),
|
|
35
|
+
close: close,
|
|
36
|
+
closePromise: () => Effect.runPromise(close),
|
|
37
|
+
staticData,
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface ServiceContext<Ctx, TStaticData> {
|
|
42
|
+
readonly provide: <E, A>(self: Effect.Effect<A, E, Ctx>) => Effect.Effect<A, E>
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Fire and Forget. Errors are logged however.
|
|
46
|
+
*/
|
|
47
|
+
readonly runWithErrorLog: <E, A>(self: Effect.Effect<A, E, Ctx>) => AbortCallback
|
|
48
|
+
|
|
49
|
+
readonly runSync: <E, A>(self: Effect.Effect<A, E, Ctx>) => A
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Fire and Forget. A promise that never fails nor returns any value.
|
|
53
|
+
* Errors are logged however.
|
|
54
|
+
*/
|
|
55
|
+
readonly runPromiseWithErrorLog: <E, A>(self: Effect.Effect<A, E, Ctx>) => Promise<A | undefined>
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* A Promise that never fails, the Resolved value is an Exit result that can be either Success or Failed
|
|
59
|
+
*/
|
|
60
|
+
readonly runPromiseExit: <E, A>(self: Effect.Effect<A, E, Ctx>) => Promise<Exit.Exit<A, E>>
|
|
61
|
+
readonly runPromise: <E, A>(self: Effect.Effect<A, E, Ctx>) => Promise<A>
|
|
62
|
+
|
|
63
|
+
readonly withRuntime: (fn: (runtime: Runtime.Runtime<Ctx>) => void) => void
|
|
64
|
+
|
|
65
|
+
/** Closes the ServiceContext and closing all its layers */
|
|
66
|
+
readonly close: Effect.Effect<void>
|
|
67
|
+
readonly closePromise: () => Promise<void>
|
|
68
|
+
readonly staticData: TStaticData
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export type AbortCallback = () => void
|
|
72
|
+
|
|
73
|
+
export const runWithErrorLog = <E, A>(self: Effect.Effect<A, E>) => {
|
|
74
|
+
const fiber = Effect.runFork(self)
|
|
75
|
+
fiber.addObserver((ex) => {
|
|
76
|
+
if (ex._tag === 'Failure' && Cause.isInterruptedOnly(ex.cause) === false) {
|
|
77
|
+
console.error(Cause.pretty(ex.cause))
|
|
78
|
+
}
|
|
79
|
+
})
|
|
80
|
+
return () => {
|
|
81
|
+
Effect.runFork(Fiber.interrupt(fiber))
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export const runPromiseWithErrorLog = <E, A>(self: Effect.Effect<A, E>) =>
|
|
86
|
+
Effect.runPromiseExit(self).then((ex) => {
|
|
87
|
+
if (ex._tag === 'Failure') {
|
|
88
|
+
console.error(Cause.pretty(ex.cause))
|
|
89
|
+
return undefined
|
|
90
|
+
} else {
|
|
91
|
+
return ex.value
|
|
92
|
+
}
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
export const MissingContext = Effect.die('service context not provided, wrap your app in LiveServiceContext')
|
|
96
|
+
|
|
97
|
+
export const empty = <Ctx, TStaticData>(): ServiceContext<Ctx, TStaticData> => ({
|
|
98
|
+
provide: () => MissingContext,
|
|
99
|
+
runWithErrorLog: () => runWithErrorLog(MissingContext),
|
|
100
|
+
runSync: () => Effect.runSync(MissingContext),
|
|
101
|
+
runPromiseWithErrorLog: () => runPromiseWithErrorLog(MissingContext),
|
|
102
|
+
runPromiseExit: () => Effect.runPromiseExit(MissingContext),
|
|
103
|
+
runPromise: () => Effect.runPromise(MissingContext),
|
|
104
|
+
withRuntime: () => Effect.runSync(MissingContext),
|
|
105
|
+
close: Effect.dieMessage('Empty ServiceContext cannot be closed'),
|
|
106
|
+
closePromise: () => Promise.reject('Empty ServiceContext cannot be closed'),
|
|
107
|
+
staticData: {} as TStaticData,
|
|
108
|
+
})
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
export * from 'effect/Stream'
|
|
2
|
+
|
|
3
|
+
import type { Chunk } from 'effect'
|
|
4
|
+
import { Effect, Option, pipe, Ref, Stream } from 'effect'
|
|
5
|
+
|
|
6
|
+
export const tapLog = <R, E, A>(stream: Stream.Stream<A, E, R>): Stream.Stream<A, E, R> =>
|
|
7
|
+
tapChunk<never, never, A, void>(Effect.forEach((_) => Effect.succeed(console.log(_))))(stream)
|
|
8
|
+
|
|
9
|
+
export const tapSync =
|
|
10
|
+
<A>(tapFn: (a: A) => unknown) =>
|
|
11
|
+
<R, E>(stream: Stream.Stream<A, E, R>): Stream.Stream<A, E, R> =>
|
|
12
|
+
Stream.tap(stream, (a) => Effect.sync(() => tapFn(a)))
|
|
13
|
+
|
|
14
|
+
export const tapLogWithLabel =
|
|
15
|
+
(label: string) =>
|
|
16
|
+
<R, E, A>(stream: Stream.Stream<A, E, R>): Stream.Stream<A, E, R> =>
|
|
17
|
+
tapChunk<never, never, A, void>(Effect.forEach((_) => Effect.succeed(console.log(label, _))))(stream)
|
|
18
|
+
|
|
19
|
+
export const tapChunk =
|
|
20
|
+
<R1, E1, A, Z>(f: (a: Chunk.Chunk<A>) => Effect.Effect<Z, E1, R1>) =>
|
|
21
|
+
<R, E>(self: Stream.Stream<A, E, R>): Stream.Stream<A, E1 | E, R1 | R> =>
|
|
22
|
+
Stream.mapChunksEffect(self, (chunks) =>
|
|
23
|
+
pipe(
|
|
24
|
+
f(chunks),
|
|
25
|
+
Effect.map(() => chunks),
|
|
26
|
+
),
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
const isIdentity = <A>(a1: A, a2: A): boolean => a1 === a2
|
|
30
|
+
|
|
31
|
+
export const skipRepeated =
|
|
32
|
+
<A>(isEqual: (prevEl: A, newEl: A) => boolean = isIdentity) =>
|
|
33
|
+
<R, E>(stream: Stream.Stream<A, E, R>): Stream.Stream<A, E, R> =>
|
|
34
|
+
skipRepeated_(stream, isEqual)
|
|
35
|
+
|
|
36
|
+
export const skipRepeated_ = <R, E, A>(
|
|
37
|
+
stream: Stream.Stream<A, E, R>,
|
|
38
|
+
isEqual: (prevEl: A, newEl: A) => boolean = isIdentity,
|
|
39
|
+
): Stream.Stream<A, E, R> =>
|
|
40
|
+
pipe(
|
|
41
|
+
Ref.make<Option.Option<A>>(Option.none()),
|
|
42
|
+
Stream.fromEffect,
|
|
43
|
+
Stream.flatMap((ref) =>
|
|
44
|
+
pipe(
|
|
45
|
+
stream,
|
|
46
|
+
Stream.filterEffect((el) =>
|
|
47
|
+
pipe(
|
|
48
|
+
Ref.get(ref),
|
|
49
|
+
Effect.flatMap((prevEl) => {
|
|
50
|
+
if (prevEl._tag === 'None' || isEqual(prevEl.value, el) === false) {
|
|
51
|
+
return pipe(
|
|
52
|
+
Ref.set(ref, Option.some(el)),
|
|
53
|
+
Effect.map(() => true),
|
|
54
|
+
)
|
|
55
|
+
} else {
|
|
56
|
+
return Effect.succeed(false)
|
|
57
|
+
}
|
|
58
|
+
}),
|
|
59
|
+
),
|
|
60
|
+
),
|
|
61
|
+
),
|
|
62
|
+
),
|
|
63
|
+
)
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { SubscriptionRef } from 'effect'
|
|
2
|
+
import { Chunk, Effect, pipe, Stream } from 'effect'
|
|
3
|
+
import { dual } from 'effect/Function'
|
|
4
|
+
import type { Predicate, Refinement } from 'effect/Predicate'
|
|
5
|
+
|
|
6
|
+
export * from 'effect/SubscriptionRef'
|
|
7
|
+
|
|
8
|
+
export const waitUntil: {
|
|
9
|
+
<A, B extends A>(
|
|
10
|
+
refinement: Refinement<NoInfer<A>, B>,
|
|
11
|
+
): (sref: SubscriptionRef.SubscriptionRef<A>) => Effect.Effect<B, never, never>
|
|
12
|
+
<A, B extends A>(
|
|
13
|
+
predicate: Predicate<B>,
|
|
14
|
+
): (sref: SubscriptionRef.SubscriptionRef<A>) => Effect.Effect<A, never, never>
|
|
15
|
+
<A, B extends A>(
|
|
16
|
+
sref: SubscriptionRef.SubscriptionRef<A>,
|
|
17
|
+
refinement: Refinement<NoInfer<A>, B>,
|
|
18
|
+
): Effect.Effect<B, never, never>
|
|
19
|
+
<A, B extends A>(sref: SubscriptionRef.SubscriptionRef<A>, predicate: Predicate<B>): Effect.Effect<A, never, never>
|
|
20
|
+
} = dual(2, <A>(sref: SubscriptionRef.SubscriptionRef<A>, predicate: (a: A) => boolean) =>
|
|
21
|
+
pipe(sref.changes, Stream.filter(predicate), Stream.take(1), Stream.runCollect, Effect.map(Chunk.unsafeHead)),
|
|
22
|
+
)
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import type { ParseResult, Scope } from 'effect'
|
|
2
|
+
import { Deferred, Effect, Either, Queue, Stream } from 'effect'
|
|
3
|
+
|
|
4
|
+
import * as Schema from './Schema/index.js'
|
|
5
|
+
|
|
6
|
+
export type WebChannel<MsgIn, MsgOut, E = never> = {
|
|
7
|
+
send: (a: MsgOut) => Effect.Effect<void, ParseResult.ParseError | E>
|
|
8
|
+
listen: Stream.Stream<Either.Either<MsgIn, ParseResult.ParseError>, E>
|
|
9
|
+
closedDeferred: Deferred.Deferred<void>
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const broadcastChannel = <MsgIn, MsgOut, MsgInEncoded, MsgOutEncoded>({
|
|
13
|
+
channelName,
|
|
14
|
+
listenSchema,
|
|
15
|
+
sendSchema,
|
|
16
|
+
}: {
|
|
17
|
+
channelName: string
|
|
18
|
+
listenSchema: Schema.Schema<MsgIn, MsgInEncoded>
|
|
19
|
+
sendSchema: Schema.Schema<MsgOut, MsgOutEncoded>
|
|
20
|
+
}): Effect.Effect<WebChannel<MsgIn, MsgOut>, never, Scope.Scope> =>
|
|
21
|
+
Effect.gen(function* () {
|
|
22
|
+
const channel = new BroadcastChannel(channelName)
|
|
23
|
+
|
|
24
|
+
yield* Effect.addFinalizer(() => Effect.try(() => channel.close()).pipe(Effect.ignoreLogged))
|
|
25
|
+
|
|
26
|
+
const send = (message: MsgOut) =>
|
|
27
|
+
Effect.gen(function* () {
|
|
28
|
+
const messageEncoded = yield* Schema.encode(sendSchema)(message)
|
|
29
|
+
channel.postMessage(messageEncoded)
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
// TODO also listen to `messageerror` in parallel
|
|
33
|
+
const listen = Stream.fromEventListener<MessageEvent>(channel, 'message').pipe(
|
|
34
|
+
Stream.map((_) => Schema.decodeEither(listenSchema)(_.data)),
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
const closedDeferred = yield* Deferred.make<void>()
|
|
38
|
+
|
|
39
|
+
return { send, listen, closedDeferred }
|
|
40
|
+
}).pipe(Effect.withSpan(`WebChannel:broadcastChannel(${channelName})`))
|
|
41
|
+
|
|
42
|
+
export const windowChannel = <MsgIn, MsgOut, MsgInEncoded, MsgOutEncoded>({
|
|
43
|
+
window,
|
|
44
|
+
targetOrigin = '*',
|
|
45
|
+
listenSchema,
|
|
46
|
+
sendSchema,
|
|
47
|
+
}: {
|
|
48
|
+
window: Window
|
|
49
|
+
targetOrigin?: string
|
|
50
|
+
listenSchema: Schema.Schema<MsgIn, MsgInEncoded>
|
|
51
|
+
sendSchema: Schema.Schema<MsgOut, MsgOutEncoded>
|
|
52
|
+
}): Effect.Effect<WebChannel<MsgIn, MsgOut>, never, Scope.Scope> =>
|
|
53
|
+
Effect.gen(function* () {
|
|
54
|
+
const send = (message: MsgOut) =>
|
|
55
|
+
Effect.gen(function* () {
|
|
56
|
+
const [messageEncoded, transferables] = yield* Schema.encodeWithTransferables(sendSchema)(message)
|
|
57
|
+
window.postMessage(messageEncoded, targetOrigin, transferables)
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
const listen = Stream.fromEventListener<MessageEvent>(window, 'message').pipe(
|
|
61
|
+
Stream.map((_) => Schema.decodeEither(listenSchema)(_.data)),
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
const closedDeferred = yield* Deferred.make<void>()
|
|
65
|
+
|
|
66
|
+
return { send, listen, closedDeferred }
|
|
67
|
+
}).pipe(Effect.withSpan(`WebChannel:windowChannel`))
|
|
68
|
+
|
|
69
|
+
export const messagePortChannel = <MsgIn, MsgOut, MsgInEncoded, MsgOutEncoded>({
|
|
70
|
+
port,
|
|
71
|
+
listenSchema,
|
|
72
|
+
sendSchema,
|
|
73
|
+
}: {
|
|
74
|
+
port: MessagePort
|
|
75
|
+
listenSchema: Schema.Schema<MsgIn, MsgInEncoded>
|
|
76
|
+
sendSchema: Schema.Schema<MsgOut, MsgOutEncoded>
|
|
77
|
+
}): Effect.Effect<WebChannel<MsgIn, MsgOut>, never, Scope.Scope> =>
|
|
78
|
+
Effect.gen(function* () {
|
|
79
|
+
const send = (message: MsgOut) =>
|
|
80
|
+
Effect.gen(function* () {
|
|
81
|
+
const [messageEncoded, transferables] = yield* Schema.encodeWithTransferables(sendSchema)(message)
|
|
82
|
+
port.postMessage(messageEncoded, transferables)
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
const listen = Stream.fromEventListener<MessageEvent>(port, 'message').pipe(
|
|
86
|
+
Stream.map((_) => Schema.decodeEither(listenSchema)(_.data)),
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
port.start()
|
|
90
|
+
|
|
91
|
+
const closedDeferred = yield* Deferred.make<void>()
|
|
92
|
+
|
|
93
|
+
yield* Effect.addFinalizer(() => Effect.try(() => port.close()).pipe(Effect.ignoreLogged))
|
|
94
|
+
|
|
95
|
+
return { send, listen, closedDeferred }
|
|
96
|
+
}).pipe(Effect.withSpan(`WebChannel:messagePortChannel`))
|
|
97
|
+
|
|
98
|
+
export const queueChannelProxy = <MsgIn, MsgOut>(): Effect.Effect<
|
|
99
|
+
{ webChannel: WebChannel<MsgIn, MsgOut>; sendQueue: Queue.Queue<MsgOut>; listenQueue: Queue.Queue<MsgIn> },
|
|
100
|
+
never,
|
|
101
|
+
Scope.Scope
|
|
102
|
+
> =>
|
|
103
|
+
Effect.gen(function* () {
|
|
104
|
+
const sendQueue = yield* Queue.unbounded<MsgOut>().pipe(Effect.acquireRelease(Queue.shutdown))
|
|
105
|
+
const listenQueue = yield* Queue.unbounded<MsgIn>().pipe(Effect.acquireRelease(Queue.shutdown))
|
|
106
|
+
|
|
107
|
+
const send = (message: MsgOut) => Queue.offer(sendQueue, message)
|
|
108
|
+
|
|
109
|
+
const listen = Stream.fromQueue(listenQueue).pipe(Stream.map(Either.right))
|
|
110
|
+
|
|
111
|
+
const closedDeferred = yield* Deferred.make<void>()
|
|
112
|
+
|
|
113
|
+
const webChannel = { send, listen, closedDeferred }
|
|
114
|
+
|
|
115
|
+
return { webChannel, sendQueue, listenQueue }
|
|
116
|
+
})
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import type { Exit } from 'effect'
|
|
2
|
+
import { Deferred, Effect, Runtime } from 'effect'
|
|
3
|
+
|
|
4
|
+
// See https://developer.mozilla.org/en-US/docs/Web/API/Web_Locks_API
|
|
5
|
+
export const withLock =
|
|
6
|
+
<E2>({
|
|
7
|
+
lockName,
|
|
8
|
+
onTaken,
|
|
9
|
+
options,
|
|
10
|
+
}: {
|
|
11
|
+
lockName: string
|
|
12
|
+
onTaken?: Effect.Effect<void, E2>
|
|
13
|
+
options?: Omit<LockOptions, 'signal'>
|
|
14
|
+
}) =>
|
|
15
|
+
<Ctx, E, A>(eff: Effect.Effect<A, E, Ctx>): Effect.Effect<A | void, E | E2, Ctx> =>
|
|
16
|
+
Effect.gen(function* () {
|
|
17
|
+
const runtime = yield* Effect.runtime<Ctx>()
|
|
18
|
+
|
|
19
|
+
const exit = yield* Effect.tryPromise<Exit.Exit<A, E>, E | E2>({
|
|
20
|
+
try: (signal) => {
|
|
21
|
+
if (signal.aborted) return 'aborted' as never
|
|
22
|
+
|
|
23
|
+
// NOTE The 'signal' and 'ifAvailable' options cannot be used together.
|
|
24
|
+
const requestOptions = options?.ifAvailable === true ? options : { ...options, signal }
|
|
25
|
+
return navigator.locks.request(lockName, requestOptions, async (lock) => {
|
|
26
|
+
if (lock === null) {
|
|
27
|
+
if (onTaken) {
|
|
28
|
+
const exit = await Runtime.runPromiseExit(runtime)(onTaken)
|
|
29
|
+
if (exit._tag === 'Failure') {
|
|
30
|
+
return exit
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// TODO also propagate Effect interruption to the execution
|
|
37
|
+
return Runtime.runPromiseExit(runtime)(eff)
|
|
38
|
+
})
|
|
39
|
+
},
|
|
40
|
+
catch: (err) => err as any as E,
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
if (exit._tag === 'Failure') {
|
|
44
|
+
return yield* Effect.failCause(exit.cause)
|
|
45
|
+
} else {
|
|
46
|
+
return exit.value
|
|
47
|
+
}
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
export const waitForDeferredLock = (deferred: Deferred.Deferred<void>, lockName: string) =>
|
|
51
|
+
Effect.async<void>((cb, signal) => {
|
|
52
|
+
if (signal.aborted) return
|
|
53
|
+
|
|
54
|
+
navigator.locks
|
|
55
|
+
.request(lockName, { signal, mode: 'exclusive', ifAvailable: false }, (_lock) => {
|
|
56
|
+
// immediately continuing calling Effect since we have the lock
|
|
57
|
+
cb(Effect.void)
|
|
58
|
+
|
|
59
|
+
// the code below is still running
|
|
60
|
+
|
|
61
|
+
// holding lock until deferred is resolved
|
|
62
|
+
return Effect.runPromise(Deferred.await(deferred))
|
|
63
|
+
})
|
|
64
|
+
.catch((error) => {
|
|
65
|
+
if (error.code === 20 && error.message === 'signal is aborted without reason') {
|
|
66
|
+
// Given signal interruption is handled via Effect, we can ignore this case
|
|
67
|
+
} else {
|
|
68
|
+
throw error
|
|
69
|
+
}
|
|
70
|
+
})
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
export const tryGetDeferredLock = (deferred: Deferred.Deferred<void>, lockName: string) =>
|
|
74
|
+
Effect.async<boolean>((cb, signal) => {
|
|
75
|
+
navigator.locks.request(lockName, { mode: 'exclusive', ifAvailable: true }, (lock) => {
|
|
76
|
+
cb(Effect.succeed(lock !== null))
|
|
77
|
+
|
|
78
|
+
// the code below is still running
|
|
79
|
+
|
|
80
|
+
const abortPromise = new Promise<void>((resolve) => {
|
|
81
|
+
signal.addEventListener('abort', () => {
|
|
82
|
+
resolve()
|
|
83
|
+
})
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
// holding lock until deferred is resolved
|
|
87
|
+
return Promise.race([
|
|
88
|
+
Effect.runPromise(Deferred.await(deferred)),
|
|
89
|
+
// .finally(() =>
|
|
90
|
+
// console.log('[@livestore/utils:WebLock] tryGetDeferredLock. finally', lockName),
|
|
91
|
+
// ),
|
|
92
|
+
abortPromise,
|
|
93
|
+
])
|
|
94
|
+
})
|
|
95
|
+
})
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import '../global.js'
|
|
2
|
+
|
|
3
|
+
export {
|
|
4
|
+
Scope,
|
|
5
|
+
Ref,
|
|
6
|
+
SynchronizedRef,
|
|
7
|
+
Queue,
|
|
8
|
+
Fiber,
|
|
9
|
+
FiberId,
|
|
10
|
+
FiberSet,
|
|
11
|
+
FiberMap,
|
|
12
|
+
FiberHandle,
|
|
13
|
+
Inspectable,
|
|
14
|
+
RuntimeFlags,
|
|
15
|
+
PubSub,
|
|
16
|
+
Exit,
|
|
17
|
+
Cause,
|
|
18
|
+
Runtime,
|
|
19
|
+
FiberRef,
|
|
20
|
+
FiberRefs,
|
|
21
|
+
FiberRefsPatch,
|
|
22
|
+
Deferred,
|
|
23
|
+
Metric,
|
|
24
|
+
MetricState,
|
|
25
|
+
Request,
|
|
26
|
+
Tracer,
|
|
27
|
+
Context,
|
|
28
|
+
Data,
|
|
29
|
+
Either,
|
|
30
|
+
Brand,
|
|
31
|
+
Hash,
|
|
32
|
+
Equal,
|
|
33
|
+
Chunk,
|
|
34
|
+
Duration,
|
|
35
|
+
Array as ReadonlyArray,
|
|
36
|
+
Record as ReadonlyRecord,
|
|
37
|
+
SortedMap,
|
|
38
|
+
HashMap,
|
|
39
|
+
HashSet,
|
|
40
|
+
MutableHashSet,
|
|
41
|
+
MutableHashMap,
|
|
42
|
+
Option,
|
|
43
|
+
LogLevel,
|
|
44
|
+
Logger,
|
|
45
|
+
Layer,
|
|
46
|
+
STM,
|
|
47
|
+
TRef,
|
|
48
|
+
Channel,
|
|
49
|
+
pipe,
|
|
50
|
+
identity,
|
|
51
|
+
GlobalValue,
|
|
52
|
+
Match,
|
|
53
|
+
} from 'effect'
|
|
54
|
+
|
|
55
|
+
export { dual } from 'effect/Function'
|
|
56
|
+
|
|
57
|
+
export * as Stream from './Stream.js'
|
|
58
|
+
|
|
59
|
+
export * as SubscriptionRef from './SubscriptionRef.js'
|
|
60
|
+
|
|
61
|
+
export * as WebChannel from './WebChannel.js'
|
|
62
|
+
|
|
63
|
+
export * as SchemaAST from 'effect/SchemaAST'
|
|
64
|
+
export { TreeFormatter } from 'effect/ParseResult'
|
|
65
|
+
export { ParseResult, Pretty } from 'effect'
|
|
66
|
+
export type { Serializable, SerializableWithResult } from 'effect/Schema'
|
|
67
|
+
export * as Schema from './Schema/index.js'
|
|
68
|
+
export * as OtelTracer from '@effect/opentelemetry/Tracer'
|
|
69
|
+
|
|
70
|
+
export {
|
|
71
|
+
Transferable,
|
|
72
|
+
FileSystem,
|
|
73
|
+
Worker,
|
|
74
|
+
WorkerError,
|
|
75
|
+
WorkerRunner,
|
|
76
|
+
Terminal,
|
|
77
|
+
HttpServer,
|
|
78
|
+
HttpClient,
|
|
79
|
+
HttpClientError,
|
|
80
|
+
HttpClientRequest,
|
|
81
|
+
HttpClientResponse,
|
|
82
|
+
FetchHttpClient,
|
|
83
|
+
} from '@effect/platform'
|
|
84
|
+
export { BrowserWorker, BrowserWorkerRunner } from '@effect/platform-browser'
|
|
85
|
+
|
|
86
|
+
export * as Effect from './Effect.js'
|
|
87
|
+
export * as Schedule from './Schedule.js'
|
|
88
|
+
export * as Scheduler from './Scheduler.js'
|
|
89
|
+
export * from './Error.js'
|
|
90
|
+
export * as ServiceContext from './ServiceContext.js'
|
|
91
|
+
export * as WebLock from './WebLock.js'
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/* eslint-disable no-var */
|
|
2
|
+
|
|
3
|
+
// Copied from fast-deep-equal
|
|
4
|
+
// MIT License
|
|
5
|
+
|
|
6
|
+
export const deepEqual = <T>(a: T, b: T): boolean => {
|
|
7
|
+
if (a === b) return true
|
|
8
|
+
|
|
9
|
+
if (a && b && typeof a == 'object' && typeof b == 'object') {
|
|
10
|
+
if (a.constructor !== b.constructor) return false
|
|
11
|
+
|
|
12
|
+
var length, i, keys
|
|
13
|
+
if (Array.isArray(a)) {
|
|
14
|
+
length = a.length
|
|
15
|
+
// @ts-expect-error ...
|
|
16
|
+
if (length != b.length) return false
|
|
17
|
+
for (i = length; i-- !== 0; )
|
|
18
|
+
// @ts-expect-error ...
|
|
19
|
+
if (!deepEqual(a[i], b[i])) return false
|
|
20
|
+
return true
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (a instanceof Map && b instanceof Map) {
|
|
24
|
+
if (a.size !== b.size) return false
|
|
25
|
+
for (i of a.entries()) if (!b.has(i[0])) return false
|
|
26
|
+
for (i of a.entries()) if (!deepEqual(i[1], b.get(i[0]))) return false
|
|
27
|
+
return true
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (a instanceof Set && b instanceof Set) {
|
|
31
|
+
if (a.size !== b.size) return false
|
|
32
|
+
for (i of a.entries()) if (!b.has(i[0])) return false
|
|
33
|
+
return true
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (ArrayBuffer.isView(a) && ArrayBuffer.isView(b)) {
|
|
37
|
+
// @ts-expect-error ...
|
|
38
|
+
length = a.length
|
|
39
|
+
// @ts-expect-error ...
|
|
40
|
+
if (length != b.length) return false
|
|
41
|
+
for (i = length; i-- !== 0; )
|
|
42
|
+
// @ts-expect-error ...
|
|
43
|
+
if (a[i] !== b[i]) return false
|
|
44
|
+
return true
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// @ts-expect-error ...
|
|
48
|
+
if (a.constructor === RegExp) return a.source === b.source && a.flags === b.flags
|
|
49
|
+
if (a.valueOf !== undefined && a.valueOf !== Object.prototype.valueOf) return a.valueOf() === b.valueOf()
|
|
50
|
+
if (a.toString !== undefined && a.toString !== Object.prototype.toString) return a.toString() === b.toString()
|
|
51
|
+
|
|
52
|
+
keys = Object.keys(a)
|
|
53
|
+
length = keys.length
|
|
54
|
+
if (length !== Object.keys(b).length) return false
|
|
55
|
+
|
|
56
|
+
for (i = length; i-- !== 0; )
|
|
57
|
+
// @ts-expect-error ...
|
|
58
|
+
if (!Object.prototype.hasOwnProperty.call(b, keys[i])) return false
|
|
59
|
+
|
|
60
|
+
for (i = length; i-- !== 0; ) {
|
|
61
|
+
var key = keys[i]
|
|
62
|
+
|
|
63
|
+
// @ts-expect-error ...
|
|
64
|
+
if (!deepEqual(a[key], b[key])) return false
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return true
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// true if both NaN, false otherwise
|
|
71
|
+
return a !== a && b !== b
|
|
72
|
+
}
|
package/src/global.ts
ADDED
package/src/guards.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export const isNotUndefined = <T>(_: T | undefined): _ is T => _ !== undefined
|
|
2
|
+
|
|
3
|
+
export const isNotNull = <T>(_: T | null): _ is T => _ !== null
|
|
4
|
+
export const isUndefined = <T>(_: T | undefined): _ is undefined => _ === undefined
|
|
5
|
+
|
|
6
|
+
export const isNil = (val: any): val is null | undefined => val === null || val === undefined
|
|
7
|
+
|
|
8
|
+
export const isNotNil = <T>(val: T | undefined | null): val is T => val !== null && val !== undefined
|