@livestore/utils 0.0.54-dev.5 → 0.0.55-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.json +1 -1
- package/dist/Deferred.d.ts.map +1 -1
- package/dist/NoopTracer.d.ts.map +1 -1
- package/dist/effect/Effect.d.ts +12 -5
- package/dist/effect/Effect.d.ts.map +1 -1
- package/dist/effect/Effect.js +59 -18
- package/dist/effect/Effect.js.map +1 -1
- package/dist/effect/Error.d.ts +8 -9
- package/dist/effect/Error.d.ts.map +1 -1
- package/dist/effect/Error.js +5 -8
- package/dist/effect/Error.js.map +1 -1
- 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 +13 -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 +15 -0
- package/dist/effect/Schema/index.d.ts.map +1 -0
- package/dist/effect/Schema/index.js +46 -0
- package/dist/effect/Schema/index.js.map +1 -0
- package/dist/effect/Schema.d.ts.map +1 -1
- package/dist/effect/ServiceContext.d.ts.map +1 -1
- package/dist/effect/Stream.d.ts +2 -0
- package/dist/effect/Stream.d.ts.map +1 -1
- package/dist/effect/Stream.js +11 -1
- package/dist/effect/Stream.js.map +1 -1
- package/dist/effect/SubscriptionRef.d.ts +8 -3
- package/dist/effect/SubscriptionRef.d.ts.map +1 -1
- package/dist/effect/SubscriptionRef.js +2 -2
- package/dist/effect/SubscriptionRef.js.map +1 -1
- package/dist/effect/WebLock.d.ts +5 -1
- package/dist/effect/WebLock.d.ts.map +1 -1
- package/dist/effect/WebLock.js +42 -11
- package/dist/effect/WebLock.js.map +1 -1
- package/dist/effect/browser-worker-tmp/BrowserWorker.d.ts +21 -0
- package/dist/effect/browser-worker-tmp/BrowserWorker.d.ts.map +1 -0
- package/dist/effect/browser-worker-tmp/BrowserWorker.js +17 -0
- package/dist/effect/browser-worker-tmp/BrowserWorker.js.map +1 -0
- package/dist/effect/browser-worker-tmp/BrowserWorkerRunner.d.ts +12 -0
- package/dist/effect/browser-worker-tmp/BrowserWorkerRunner.d.ts.map +1 -0
- package/dist/effect/browser-worker-tmp/BrowserWorkerRunner.js +8 -0
- package/dist/effect/browser-worker-tmp/BrowserWorkerRunner.js.map +1 -0
- package/dist/effect/browser-worker-tmp/internal/worker.d.ts +9 -0
- package/dist/effect/browser-worker-tmp/internal/worker.d.ts.map +1 -0
- package/dist/effect/browser-worker-tmp/internal/worker.js +56 -0
- package/dist/effect/browser-worker-tmp/internal/worker.js.map +1 -0
- package/dist/effect/browser-worker-tmp/internal/workerRunner.d.ts +5 -0
- package/dist/effect/browser-worker-tmp/internal/workerRunner.d.ts.map +1 -0
- package/dist/effect/browser-worker-tmp/internal/workerRunner.js +100 -0
- package/dist/effect/browser-worker-tmp/internal/workerRunner.js.map +1 -0
- package/dist/effect/browser-worker-tmp/port-platform-runner.d.ts +5 -0
- package/dist/effect/browser-worker-tmp/port-platform-runner.d.ts.map +1 -0
- package/dist/effect/browser-worker-tmp/port-platform-runner.js +54 -0
- package/dist/effect/browser-worker-tmp/port-platform-runner.js.map +1 -0
- package/dist/effect/index.d.ts +10 -5
- package/dist/effect/index.d.ts.map +1 -1
- package/dist/effect/index.js +12 -5
- package/dist/effect/index.js.map +1 -1
- package/dist/effect/worker-tmp/Worker.d.ts +290 -0
- package/dist/effect/worker-tmp/Worker.d.ts.map +1 -0
- package/dist/effect/worker-tmp/Worker.js +62 -0
- package/dist/effect/worker-tmp/Worker.js.map +1 -0
- package/dist/effect/worker-tmp/WorkerError.d.ts +63 -0
- package/dist/effect/worker-tmp/WorkerError.d.ts.map +1 -0
- package/dist/effect/worker-tmp/WorkerError.js +52 -0
- package/dist/effect/worker-tmp/WorkerError.js.map +1 -0
- package/dist/effect/worker-tmp/WorkerRunner.d.ts +119 -0
- package/dist/effect/worker-tmp/WorkerRunner.d.ts.map +1 -0
- package/dist/effect/worker-tmp/WorkerRunner.js +32 -0
- package/dist/effect/worker-tmp/WorkerRunner.js.map +1 -0
- package/dist/effect/worker-tmp/internal/worker.d.ts +36 -0
- package/dist/effect/worker-tmp/internal/worker.d.ts.map +1 -0
- package/dist/effect/worker-tmp/internal/worker.js +244 -0
- package/dist/effect/worker-tmp/internal/worker.js.map +1 -0
- package/dist/effect/worker-tmp/internal/workerError.d.ts +4 -0
- package/dist/effect/worker-tmp/internal/workerError.d.ts.map +1 -0
- package/dist/effect/worker-tmp/internal/workerError.js +3 -0
- package/dist/effect/worker-tmp/internal/workerError.js.map +1 -0
- package/dist/effect/worker-tmp/internal/workerRunner.d.ts +21 -0
- package/dist/effect/worker-tmp/internal/workerRunner.d.ts.map +1 -0
- package/dist/effect/worker-tmp/internal/workerRunner.js +137 -0
- package/dist/effect/worker-tmp/internal/workerRunner.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/guards.d.ts.map +1 -1
- package/dist/index.d.ts +5 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +20 -19
- package/dist/index.js.map +1 -1
- package/dist/misc.d.ts +2 -0
- package/dist/misc.d.ts.map +1 -0
- package/dist/misc.js +13 -0
- package/dist/misc.js.map +1 -0
- package/dist/object/index.d.ts.map +1 -1
- package/dist/object/omit.d.ts.map +1 -1
- package/dist/object/pick.d.ts +1 -1
- package/dist/object/pick.d.ts.map +1 -1
- package/dist/promise.d.ts.map +1 -1
- package/dist/set.d.ts.map +1 -1
- package/package.json +18 -17
- package/src/effect/Effect.ts +96 -26
- package/src/effect/Error.ts +5 -14
- package/src/effect/Scheduler.ts +14 -0
- package/src/effect/Schema/debug-diff.test.ts +102 -0
- package/src/effect/Schema/debug-diff.ts +59 -0
- package/src/effect/Schema/index.ts +60 -0
- package/src/effect/Stream.ts +37 -1
- package/src/effect/SubscriptionRef.ts +22 -7
- package/src/effect/WebLock.ts +69 -31
- package/src/effect/browser-worker-tmp/BrowserWorker.ts +26 -0
- package/src/effect/browser-worker-tmp/BrowserWorkerRunner.ts +14 -0
- package/src/effect/browser-worker-tmp/internal/worker.ts +71 -0
- package/src/effect/browser-worker-tmp/internal/workerRunner.ts +119 -0
- package/src/effect/browser-worker-tmp/port-platform-runner.ts +74 -0
- package/src/effect/index.ts +17 -4
- package/src/effect/worker-tmp/Worker.ts +374 -0
- package/src/effect/worker-tmp/WorkerError.ts +79 -0
- package/src/effect/worker-tmp/WorkerRunner.ts +181 -0
- package/src/effect/worker-tmp/internal/worker.ts +417 -0
- package/src/effect/worker-tmp/internal/workerError.ts +6 -0
- package/src/effect/worker-tmp/internal/workerRunner.ts +237 -0
- package/src/fast-deep-equal.ts +72 -0
- package/src/index.ts +21 -22
- package/src/misc.ts +12 -0
- package/src/effect/Schema.ts +0 -18
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { Transferable } from '@effect/platform'
|
|
2
|
+
import { Schema } from '@effect/schema'
|
|
3
|
+
import type { ParseOptions } from '@effect/schema/AST'
|
|
4
|
+
import type { ParseError } from '@effect/schema/ParseResult'
|
|
5
|
+
import { Effect, Hash } from 'effect'
|
|
6
|
+
|
|
7
|
+
import { objectToString } from '../../misc.js'
|
|
8
|
+
|
|
9
|
+
export * from '@effect/schema/Schema'
|
|
10
|
+
export * from './debug-diff.js'
|
|
11
|
+
|
|
12
|
+
// NOTE this is a temporary workaround until Effect schema has a better way to hash schemas
|
|
13
|
+
// https://github.com/Effect-TS/effect/issues/2719
|
|
14
|
+
// TODO remove this once the issue is resolved
|
|
15
|
+
export const hash = (schema: Schema.Schema<any>) => {
|
|
16
|
+
try {
|
|
17
|
+
return Hash.string(JSON.stringify(schema.ast, null, 2))
|
|
18
|
+
} catch {
|
|
19
|
+
console.warn(
|
|
20
|
+
`Schema hashing failed, falling back to hashing the shortend schema AST string. This is less reliable and may cause false positives.`,
|
|
21
|
+
)
|
|
22
|
+
return Hash.hash(schema.ast.toString())
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const errorStructSchema = Schema.Struct({
|
|
27
|
+
message: Schema.String,
|
|
28
|
+
stack: Schema.optional(Schema.String),
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
export class AnyError extends Schema.transform(errorStructSchema, Schema.Any, {
|
|
32
|
+
decode: (errorStruct) => {
|
|
33
|
+
const { message, stack } = errorStruct
|
|
34
|
+
const previousLimit = Error.stackTraceLimit
|
|
35
|
+
Error.stackTraceLimit = 0
|
|
36
|
+
// eslint-disable-next-line unicorn/error-message
|
|
37
|
+
const error = new Error('')
|
|
38
|
+
Error.stackTraceLimit = previousLimit
|
|
39
|
+
error.message = message
|
|
40
|
+
error.stack = stack
|
|
41
|
+
return error
|
|
42
|
+
},
|
|
43
|
+
encode: (anyError) => ({
|
|
44
|
+
message: objectToString(anyError).replace(/^Error: /, ''),
|
|
45
|
+
stack: anyError.stack,
|
|
46
|
+
}),
|
|
47
|
+
}) {}
|
|
48
|
+
|
|
49
|
+
export const encodeWithTransferables =
|
|
50
|
+
<A, I, R>(schema: Schema.Schema<A, I, R>, options?: ParseOptions | undefined) =>
|
|
51
|
+
(a: A, overrideOptions?: ParseOptions | undefined): Effect.Effect<[I, Transferable[]], ParseError, R> =>
|
|
52
|
+
Effect.gen(function* () {
|
|
53
|
+
const collector = yield* Transferable.makeCollector
|
|
54
|
+
|
|
55
|
+
const encoded: I = yield* Schema.encode(schema, options)(a, overrideOptions).pipe(
|
|
56
|
+
Effect.provideService(Transferable.Collector, collector),
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
return [encoded, collector.unsafeRead() as Transferable[]]
|
|
60
|
+
})
|
package/src/effect/Stream.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export * from 'effect/Stream'
|
|
2
2
|
|
|
3
3
|
import type { Chunk } from 'effect'
|
|
4
|
-
import { Effect, pipe, Stream } from 'effect'
|
|
4
|
+
import { Effect, Option, pipe, Ref, Stream } from 'effect'
|
|
5
5
|
|
|
6
6
|
export const tapLog = <R, E, A>(stream: Stream.Stream<A, E, R>): Stream.Stream<A, E, R> =>
|
|
7
7
|
tapChunk<never, never, A, void>(Effect.forEach((_) => Effect.succeed(console.log(_))))(stream)
|
|
@@ -20,3 +20,39 @@ export const tapChunk =
|
|
|
20
20
|
Effect.map(() => chunks),
|
|
21
21
|
),
|
|
22
22
|
)
|
|
23
|
+
|
|
24
|
+
const isIdentity = <A>(a1: A, a2: A): boolean => a1 === a2
|
|
25
|
+
|
|
26
|
+
export const skipRepeated =
|
|
27
|
+
<A>(isEqual: (prevEl: A, newEl: A) => boolean = isIdentity) =>
|
|
28
|
+
<R, E>(stream: Stream.Stream<A, E, R>): Stream.Stream<A, E, R> =>
|
|
29
|
+
skipRepeated_(stream, isEqual)
|
|
30
|
+
|
|
31
|
+
export const skipRepeated_ = <R, E, A>(
|
|
32
|
+
stream: Stream.Stream<A, E, R>,
|
|
33
|
+
isEqual: (prevEl: A, newEl: A) => boolean = isIdentity,
|
|
34
|
+
): Stream.Stream<A, E, R> =>
|
|
35
|
+
pipe(
|
|
36
|
+
Ref.make<Option.Option<A>>(Option.none()),
|
|
37
|
+
Stream.fromEffect,
|
|
38
|
+
Stream.flatMap((ref) =>
|
|
39
|
+
pipe(
|
|
40
|
+
stream,
|
|
41
|
+
Stream.filterEffect((el) =>
|
|
42
|
+
pipe(
|
|
43
|
+
Ref.get(ref),
|
|
44
|
+
Effect.flatMap((prevEl) => {
|
|
45
|
+
if (prevEl._tag === 'None' || isEqual(prevEl.value, el) === false) {
|
|
46
|
+
return pipe(
|
|
47
|
+
Ref.set(ref, Option.some(el)),
|
|
48
|
+
Effect.map(() => true),
|
|
49
|
+
)
|
|
50
|
+
} else {
|
|
51
|
+
return Effect.succeed(false)
|
|
52
|
+
}
|
|
53
|
+
}),
|
|
54
|
+
),
|
|
55
|
+
),
|
|
56
|
+
),
|
|
57
|
+
),
|
|
58
|
+
)
|
|
@@ -1,15 +1,30 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { pipe, Stream, SubscriptionRef } from 'effect'
|
|
1
|
+
import { Chunk, Effect, pipe, Stream, SubscriptionRef } from 'effect'
|
|
3
2
|
import { dual } from 'effect/Function'
|
|
3
|
+
import type { Predicate, Refinement } from 'effect/Predicate'
|
|
4
4
|
|
|
5
5
|
export * from 'effect/SubscriptionRef'
|
|
6
6
|
|
|
7
7
|
export const changeStreamIncludingCurrent = <A>(sref: SubscriptionRef.SubscriptionRef<A>) =>
|
|
8
8
|
pipe(Stream.fromEffect(SubscriptionRef.get(sref)), Stream.concat(sref.changes))
|
|
9
9
|
|
|
10
|
-
export const waitUntil
|
|
11
|
-
<A
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
10
|
+
export const waitUntil: {
|
|
11
|
+
<A, B extends A>(
|
|
12
|
+
refinement: Refinement<NoInfer<A>, B>,
|
|
13
|
+
): (sref: SubscriptionRef.SubscriptionRef<A>) => Effect.Effect<B, never, never>
|
|
14
|
+
<A, B extends A>(
|
|
15
|
+
predicate: Predicate<B>,
|
|
16
|
+
): (sref: SubscriptionRef.SubscriptionRef<A>) => Effect.Effect<A, never, never>
|
|
17
|
+
<A, B extends A>(
|
|
18
|
+
sref: SubscriptionRef.SubscriptionRef<A>,
|
|
19
|
+
refinement: Refinement<NoInfer<A>, B>,
|
|
20
|
+
): Effect.Effect<B, never, never>
|
|
21
|
+
<A, B extends A>(sref: SubscriptionRef.SubscriptionRef<A>, predicate: Predicate<B>): Effect.Effect<A, never, never>
|
|
22
|
+
} = dual(2, <A>(sref: SubscriptionRef.SubscriptionRef<A>, predicate: (a: A) => boolean) =>
|
|
23
|
+
pipe(
|
|
24
|
+
changeStreamIncludingCurrent(sref),
|
|
25
|
+
Stream.filter(predicate),
|
|
26
|
+
Stream.take(1),
|
|
27
|
+
Stream.runCollect,
|
|
28
|
+
Effect.map(Chunk.unsafeHead),
|
|
29
|
+
),
|
|
15
30
|
)
|
package/src/effect/WebLock.ts
CHANGED
|
@@ -3,29 +3,45 @@ import { Deferred, Effect, Runtime } from 'effect'
|
|
|
3
3
|
|
|
4
4
|
// See https://developer.mozilla.org/en-US/docs/Web/API/Web_Locks_API
|
|
5
5
|
export const withLock =
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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
|
+
})
|
|
26
42
|
|
|
27
43
|
if (exit._tag === 'Failure') {
|
|
28
|
-
return yield*
|
|
44
|
+
return yield* Effect.failCause(exit.cause)
|
|
29
45
|
} else {
|
|
30
46
|
return exit.value
|
|
31
47
|
}
|
|
@@ -33,25 +49,47 @@ export const withLock =
|
|
|
33
49
|
|
|
34
50
|
export const waitForDeferredLock = (deferred: Deferred.Deferred<void>, lockName: string) =>
|
|
35
51
|
Effect.async<void>((cb, signal) => {
|
|
36
|
-
|
|
37
|
-
// immediately continuing calling Effect since we have the lock
|
|
38
|
-
cb(Effect.void)
|
|
52
|
+
if (signal.aborted) return
|
|
39
53
|
|
|
40
|
-
|
|
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)
|
|
41
58
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
|
+
})
|
|
45
71
|
})
|
|
46
72
|
|
|
47
73
|
export const tryGetDeferredLock = (deferred: Deferred.Deferred<void>, lockName: string) =>
|
|
48
|
-
Effect.async<boolean>((cb) => {
|
|
49
|
-
navigator.locks.request(lockName, { mode: 'exclusive', ifAvailable: true },
|
|
74
|
+
Effect.async<boolean>((cb, signal) => {
|
|
75
|
+
navigator.locks.request(lockName, { mode: 'exclusive', ifAvailable: true }, (lock) => {
|
|
50
76
|
cb(Effect.succeed(lock !== null))
|
|
51
77
|
|
|
52
78
|
// the code below is still running
|
|
53
79
|
|
|
80
|
+
const abortPromise = new Promise<void>((resolve) => {
|
|
81
|
+
signal.addEventListener('abort', () => {
|
|
82
|
+
resolve()
|
|
83
|
+
})
|
|
84
|
+
})
|
|
85
|
+
|
|
54
86
|
// holding lock until deferred is resolved
|
|
55
|
-
|
|
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
|
+
])
|
|
56
94
|
})
|
|
57
95
|
})
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @since 1.0.0
|
|
3
|
+
*/
|
|
4
|
+
import type * as Layer from 'effect/Layer'
|
|
5
|
+
|
|
6
|
+
import type * as Worker from '../worker-tmp/Worker.js'
|
|
7
|
+
import * as internal from './internal/worker.js'
|
|
8
|
+
/**
|
|
9
|
+
* @since 1.0.0
|
|
10
|
+
* @category layers
|
|
11
|
+
*/
|
|
12
|
+
export const layerManager: Layer.Layer<Worker.WorkerManager> = internal.layerManager
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @since 1.0.0
|
|
16
|
+
* @category layers
|
|
17
|
+
*/
|
|
18
|
+
export const layerWorker: Layer.Layer<Worker.PlatformWorker> = internal.layerWorker
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @since 1.0.0
|
|
22
|
+
* @category layers
|
|
23
|
+
*/
|
|
24
|
+
export const layer: (
|
|
25
|
+
spawn: (id: number) => Worker | SharedWorker | MessagePort,
|
|
26
|
+
) => Layer.Layer<Worker.WorkerManager | Worker.Spawner, never, never> = internal.layer
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @since 1.0.0
|
|
3
|
+
*/
|
|
4
|
+
import type * as Layer from 'effect/Layer'
|
|
5
|
+
|
|
6
|
+
import type * as Runner from '../worker-tmp/WorkerRunner.js'
|
|
7
|
+
import * as internal from './internal/workerRunner.js'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @since 1.0.0
|
|
11
|
+
* @category layers
|
|
12
|
+
*/
|
|
13
|
+
export const layer: Layer.Layer<Runner.PlatformRunner> = internal.layer
|
|
14
|
+
export { layerMessagePort } from './port-platform-runner.js'
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/* eslint-disable prefer-arrow/prefer-arrow-functions */
|
|
2
|
+
import * as Deferred from 'effect/Deferred'
|
|
3
|
+
import * as Effect from 'effect/Effect'
|
|
4
|
+
import * as Layer from 'effect/Layer'
|
|
5
|
+
import * as Queue from 'effect/Queue'
|
|
6
|
+
|
|
7
|
+
import * as Worker from '../../worker-tmp/Worker.js'
|
|
8
|
+
import { WorkerError } from '../../worker-tmp/WorkerError.js'
|
|
9
|
+
|
|
10
|
+
const platformWorkerImpl = Worker.PlatformWorker.of({
|
|
11
|
+
[Worker.PlatformWorkerTypeId]: Worker.PlatformWorkerTypeId,
|
|
12
|
+
spawn<I, O>(worker_: unknown) {
|
|
13
|
+
return Effect.gen(function* (_) {
|
|
14
|
+
const worker = worker_ as globalThis.SharedWorker | globalThis.Worker | MessagePort
|
|
15
|
+
let port: globalThis.Worker | MessagePort
|
|
16
|
+
if ('port' in worker) {
|
|
17
|
+
port = worker.port
|
|
18
|
+
} else {
|
|
19
|
+
port = worker
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
yield* _(Effect.addFinalizer(() => Effect.sync(() => port.postMessage([1]))))
|
|
23
|
+
|
|
24
|
+
const queue = yield* _(Queue.unbounded<Worker.BackingWorker.Message<O>>())
|
|
25
|
+
const latch = yield* Deferred.make<void>()
|
|
26
|
+
|
|
27
|
+
const fiber = yield* _(
|
|
28
|
+
Effect.async<never, WorkerError, never>((resume) => {
|
|
29
|
+
function onMessage(event: MessageEvent) {
|
|
30
|
+
queue.unsafeOffer((event as MessageEvent).data)
|
|
31
|
+
}
|
|
32
|
+
function onError(event: ErrorEvent) {
|
|
33
|
+
resume(new WorkerError({ reason: 'unknown', error: event.error ?? event.message }))
|
|
34
|
+
}
|
|
35
|
+
port.addEventListener('message', onMessage as any)
|
|
36
|
+
port.addEventListener('error', onError as any)
|
|
37
|
+
Deferred.unsafeDone(latch, Effect.void)
|
|
38
|
+
return Effect.sync(() => {
|
|
39
|
+
port.removeEventListener('message', onMessage as any)
|
|
40
|
+
port.removeEventListener('error', onError as any)
|
|
41
|
+
})
|
|
42
|
+
}),
|
|
43
|
+
Effect.interruptible,
|
|
44
|
+
Effect.forkScoped,
|
|
45
|
+
)
|
|
46
|
+
yield* Deferred.await(latch)
|
|
47
|
+
|
|
48
|
+
if ('start' in port) {
|
|
49
|
+
port.start()
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const send = (message: I, transfers?: ReadonlyArray<unknown>) =>
|
|
53
|
+
Effect.try({
|
|
54
|
+
try: () => port.postMessage([0, message], transfers as any),
|
|
55
|
+
catch: (error) => new WorkerError({ reason: 'send', error }),
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
return { fiber, queue, send }
|
|
59
|
+
})
|
|
60
|
+
},
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
/** @internal */
|
|
64
|
+
export const layerWorker = Layer.succeed(Worker.PlatformWorker, platformWorkerImpl)
|
|
65
|
+
|
|
66
|
+
/** @internal */
|
|
67
|
+
export const layerManager = Layer.provide(Worker.layerManager, layerWorker)
|
|
68
|
+
|
|
69
|
+
/** @internal */
|
|
70
|
+
export const layer = (spawn: (id: number) => globalThis.Worker | globalThis.SharedWorker | MessagePort) =>
|
|
71
|
+
Layer.merge(layerManager, Worker.layerSpawner(spawn))
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/// <reference lib="webworker" />
|
|
2
|
+
/* eslint-disable prefer-arrow/prefer-arrow-functions */
|
|
3
|
+
import * as Cause from 'effect/Cause'
|
|
4
|
+
import * as Effect from 'effect/Effect'
|
|
5
|
+
import * as FiberSet from 'effect/FiberSet'
|
|
6
|
+
import { globalValue } from 'effect/GlobalValue'
|
|
7
|
+
import * as Layer from 'effect/Layer'
|
|
8
|
+
import * as Queue from 'effect/Queue'
|
|
9
|
+
import * as Schedule from 'effect/Schedule'
|
|
10
|
+
|
|
11
|
+
import { WorkerError } from '../../worker-tmp/WorkerError.js'
|
|
12
|
+
import * as WorkerRunner from '../../worker-tmp/WorkerRunner.js'
|
|
13
|
+
|
|
14
|
+
const cachedPorts = globalValue('@effect/platform-browser/Worker/cachedPorts', () => new Set<MessagePort>())
|
|
15
|
+
function globalHandleConnect(event: MessageEvent) {
|
|
16
|
+
cachedPorts.add((event as MessageEvent).ports[0]!)
|
|
17
|
+
}
|
|
18
|
+
if (typeof self !== 'undefined' && 'onconnect' in self) {
|
|
19
|
+
// @ts-expect-error TODO
|
|
20
|
+
self.addEventListener('connect', globalHandleConnect)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const platformRunnerImpl = WorkerRunner.PlatformRunner.of({
|
|
24
|
+
[WorkerRunner.PlatformRunnerTypeId]: WorkerRunner.PlatformRunnerTypeId,
|
|
25
|
+
start<I, O>(shutdown: Effect.Effect<void>) {
|
|
26
|
+
return Effect.gen(function* () {
|
|
27
|
+
let currentPortId = 0
|
|
28
|
+
|
|
29
|
+
yield* Effect.addFinalizer(() => Effect.sync(() => self.close()))
|
|
30
|
+
|
|
31
|
+
const queue = yield* Queue.unbounded<readonly [portId: number, message: I]>()
|
|
32
|
+
const runFork = yield* FiberSet.makeRuntime<never>()
|
|
33
|
+
const ports = new Map<number, MessagePort>()
|
|
34
|
+
const send = (portId: number, message: O, transfer?: ReadonlyArray<unknown>) =>
|
|
35
|
+
Effect.sync(() => {
|
|
36
|
+
ports.get(portId)?.postMessage([1, message], {
|
|
37
|
+
transfer: transfer as any,
|
|
38
|
+
})
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
function handlePort(port: MessagePort, sharedWorker: boolean) {
|
|
42
|
+
const portId = currentPortId++
|
|
43
|
+
ports.set(portId, port)
|
|
44
|
+
|
|
45
|
+
Effect.async<never, WorkerError, never>((resume) => {
|
|
46
|
+
function onMessage(event: MessageEvent) {
|
|
47
|
+
const message = (event as MessageEvent).data as WorkerRunner.BackingRunner.Message<I>
|
|
48
|
+
if (message[0] === 0) {
|
|
49
|
+
queue.unsafeOffer([portId, message[1]])
|
|
50
|
+
} else if (sharedWorker && ports.size > 1) {
|
|
51
|
+
resume(Effect.interrupt)
|
|
52
|
+
} else {
|
|
53
|
+
Effect.runFork(shutdown)
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
function onMessageError(error: ErrorEvent) {
|
|
57
|
+
resume(new WorkerError({ reason: 'decode', error: error.error ?? error.message }))
|
|
58
|
+
}
|
|
59
|
+
function onError(error: ErrorEvent) {
|
|
60
|
+
resume(new WorkerError({ reason: 'unknown', error: error.error ?? error.message }))
|
|
61
|
+
}
|
|
62
|
+
port.addEventListener('message', onMessage as any)
|
|
63
|
+
port.addEventListener('messageerror', onMessageError as any)
|
|
64
|
+
port.addEventListener('error', onError as any)
|
|
65
|
+
|
|
66
|
+
// ready
|
|
67
|
+
if ('start' in port) {
|
|
68
|
+
port.start()
|
|
69
|
+
}
|
|
70
|
+
port.postMessage([0])
|
|
71
|
+
|
|
72
|
+
return Effect.sync(() => {
|
|
73
|
+
port.removeEventListener('message', onMessage as any)
|
|
74
|
+
port.removeEventListener('messageerror', onMessageError as any)
|
|
75
|
+
port.removeEventListener('error', onError as any)
|
|
76
|
+
})
|
|
77
|
+
}).pipe(
|
|
78
|
+
Effect.tapErrorCause((cause) => (Cause.isInterruptedOnly(cause) ? Effect.void : Effect.logDebug(cause))),
|
|
79
|
+
Effect.retry(Schedule.forever),
|
|
80
|
+
Effect.annotateLogs({
|
|
81
|
+
package: '@effect/platform-browser',
|
|
82
|
+
module: 'WorkerRunner',
|
|
83
|
+
}),
|
|
84
|
+
Effect.ensuring(
|
|
85
|
+
Effect.sync(() => {
|
|
86
|
+
ports.delete(portId)
|
|
87
|
+
}),
|
|
88
|
+
),
|
|
89
|
+
Effect.interruptible,
|
|
90
|
+
runFork,
|
|
91
|
+
)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if ('onconnect' in self) {
|
|
95
|
+
// @ts-expect-error TODO
|
|
96
|
+
self.addEventListener('connect', (event: MessageEvent) => {
|
|
97
|
+
const port = (event as MessageEvent).ports[0]
|
|
98
|
+
handlePort(port!, true)
|
|
99
|
+
})
|
|
100
|
+
yield* Effect.addFinalizer(() =>
|
|
101
|
+
Effect.sync(() => {
|
|
102
|
+
;(self as any).addEventListener('connect', globalHandleConnect)
|
|
103
|
+
}),
|
|
104
|
+
)
|
|
105
|
+
for (const port of cachedPorts) {
|
|
106
|
+
handlePort(port, true)
|
|
107
|
+
}
|
|
108
|
+
cachedPorts.clear()
|
|
109
|
+
} else {
|
|
110
|
+
handlePort(self as any, false)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return { queue, send }
|
|
114
|
+
})
|
|
115
|
+
},
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
/** @internal */
|
|
119
|
+
export const layer = Layer.succeed(WorkerRunner.PlatformRunner, platformRunnerImpl)
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { Deferred } from 'effect'
|
|
2
|
+
import * as Cause from 'effect/Cause'
|
|
3
|
+
import * as Effect from 'effect/Effect'
|
|
4
|
+
import * as Layer from 'effect/Layer'
|
|
5
|
+
import * as Queue from 'effect/Queue'
|
|
6
|
+
import * as Schedule from 'effect/Schedule'
|
|
7
|
+
|
|
8
|
+
import { WorkerError } from '../worker-tmp/WorkerError.js'
|
|
9
|
+
import * as Runner from '../worker-tmp/WorkerRunner.js'
|
|
10
|
+
|
|
11
|
+
const platformRunnerImpl = (port: MessagePort) =>
|
|
12
|
+
Runner.PlatformRunner.of({
|
|
13
|
+
[Runner.PlatformRunnerTypeId]: Runner.PlatformRunnerTypeId,
|
|
14
|
+
start: <I, O>(shutdown: Effect.Effect<void>) => {
|
|
15
|
+
return Effect.gen(function* () {
|
|
16
|
+
const queue = yield* Queue.unbounded<readonly [portId: number, message: I]>()
|
|
17
|
+
|
|
18
|
+
const latch = yield* Deferred.make<void>()
|
|
19
|
+
|
|
20
|
+
yield* Effect.async<never, WorkerError>((resume) => {
|
|
21
|
+
const onMessage = (msg: MessageEvent<Runner.BackingRunner.Message<I>>) => {
|
|
22
|
+
const message = msg.data
|
|
23
|
+
if (message[0] === 0) {
|
|
24
|
+
queue.unsafeOffer([0, message[1]])
|
|
25
|
+
} else {
|
|
26
|
+
Effect.runFork(shutdown)
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const onError = (error: any) => {
|
|
31
|
+
resume(new WorkerError({ reason: 'decode', error }))
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
port.addEventListener('message', onMessage)
|
|
35
|
+
port.addEventListener('messageerror', onError)
|
|
36
|
+
port.addEventListener('error', onError)
|
|
37
|
+
|
|
38
|
+
Deferred.unsafeDone(latch, Effect.void)
|
|
39
|
+
|
|
40
|
+
return Effect.sync(() => {
|
|
41
|
+
port.removeEventListener('message', onMessage as any)
|
|
42
|
+
port.removeEventListener('error', onError as any)
|
|
43
|
+
})
|
|
44
|
+
}).pipe(
|
|
45
|
+
Effect.tapErrorCause((cause) => (Cause.isInterruptedOnly(cause) ? Effect.void : Effect.logDebug(cause))),
|
|
46
|
+
Effect.retry(Schedule.forever),
|
|
47
|
+
Effect.annotateLogs({
|
|
48
|
+
package: '@livestore/utils/effect',
|
|
49
|
+
module: 'PortPlatformRunner',
|
|
50
|
+
}),
|
|
51
|
+
Effect.interruptible,
|
|
52
|
+
Effect.forkScoped,
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
yield* Deferred.await(latch)
|
|
56
|
+
|
|
57
|
+
port.start()
|
|
58
|
+
|
|
59
|
+
const send = (_portId: number, message: O, transfers?: ReadonlyArray<unknown>) =>
|
|
60
|
+
Effect.try({
|
|
61
|
+
try: () => port.postMessage([1, message], transfers as any),
|
|
62
|
+
catch: (error) => new WorkerError({ reason: 'send', error }),
|
|
63
|
+
}).pipe(Effect.catchTag('WorkerError', Effect.orDie))
|
|
64
|
+
|
|
65
|
+
// ready
|
|
66
|
+
port.postMessage([0])
|
|
67
|
+
|
|
68
|
+
return { queue, send }
|
|
69
|
+
})
|
|
70
|
+
},
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
/** @internal */
|
|
74
|
+
export const layerMessagePort = (port: MessagePort) => Layer.succeed(Runner.PlatformRunner, platformRunnerImpl(port))
|
package/src/effect/index.ts
CHANGED
|
@@ -7,12 +7,15 @@ export {
|
|
|
7
7
|
Queue,
|
|
8
8
|
Fiber,
|
|
9
9
|
FiberId,
|
|
10
|
+
FiberSet,
|
|
11
|
+
FiberMap,
|
|
12
|
+
FiberHandle,
|
|
13
|
+
Inspectable,
|
|
10
14
|
RuntimeFlags,
|
|
11
15
|
PubSub,
|
|
12
16
|
Exit,
|
|
13
17
|
Cause,
|
|
14
18
|
Runtime,
|
|
15
|
-
Scheduler,
|
|
16
19
|
FiberRef,
|
|
17
20
|
FiberRefs,
|
|
18
21
|
FiberRefsPatch,
|
|
@@ -36,6 +39,8 @@ export {
|
|
|
36
39
|
HashSet,
|
|
37
40
|
MutableHashSet,
|
|
38
41
|
Option,
|
|
42
|
+
LogLevel,
|
|
43
|
+
Logger,
|
|
39
44
|
Layer,
|
|
40
45
|
STM,
|
|
41
46
|
TRef,
|
|
@@ -55,18 +60,26 @@ export {
|
|
|
55
60
|
TreeFormatter,
|
|
56
61
|
AST as SchemaAST,
|
|
57
62
|
Pretty as SchemaPretty,
|
|
63
|
+
Equivalence as SchemaEquivalence,
|
|
58
64
|
Serializable,
|
|
59
65
|
JSONSchema,
|
|
60
66
|
ParseResult,
|
|
61
67
|
} from '@effect/schema'
|
|
62
|
-
export * as Schema from './Schema.js'
|
|
68
|
+
export * as Schema from './Schema/index.js'
|
|
63
69
|
export * as OtelTracer from '@effect/opentelemetry/Tracer'
|
|
64
70
|
|
|
65
|
-
export { Transferable, FileSystem, Worker, WorkerError, WorkerRunner, Terminal, HttpServer } from '@effect/platform'
|
|
66
|
-
export { BrowserWorker, BrowserWorkerRunner } from '@effect/platform-browser'
|
|
71
|
+
// export { Transferable, FileSystem, Worker, WorkerError, WorkerRunner, Terminal, HttpServer } from '@effect/platform'
|
|
72
|
+
// export { BrowserWorker, BrowserWorkerRunner } from '@effect/platform-browser'
|
|
73
|
+
export { Transferable, FileSystem, Terminal, HttpServer } from '@effect/platform'
|
|
74
|
+
export * as Worker from './worker-tmp/Worker.js'
|
|
75
|
+
export * as WorkerError from './worker-tmp/WorkerError.js'
|
|
76
|
+
export * as WorkerRunner from './worker-tmp/WorkerRunner.js'
|
|
77
|
+
export * as BrowserWorker from './browser-worker-tmp/BrowserWorker.js'
|
|
78
|
+
export * as BrowserWorkerRunner from './browser-worker-tmp/BrowserWorkerRunner.js'
|
|
67
79
|
|
|
68
80
|
export * as Effect from './Effect.js'
|
|
69
81
|
export * as Schedule from './Schedule.js'
|
|
82
|
+
export * as Scheduler from './Scheduler.js'
|
|
70
83
|
export * from './Error.js'
|
|
71
84
|
export * as ServiceContext from './ServiceContext.js'
|
|
72
85
|
export * as WebLock from './WebLock.js'
|