@livestore/utils 0.0.0-snapshot-909cdd1ac2fd591945c2be2b0f53e14d87f3c9d4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (169) hide show
  1. package/dist/.tsbuildinfo.json +1 -0
  2. package/dist/Deferred.d.ts +10 -0
  3. package/dist/Deferred.d.ts.map +1 -0
  4. package/dist/Deferred.js +21 -0
  5. package/dist/Deferred.js.map +1 -0
  6. package/dist/NoopTracer.d.ts +10 -0
  7. package/dist/NoopTracer.d.ts.map +1 -0
  8. package/dist/NoopTracer.js +52 -0
  9. package/dist/NoopTracer.js.map +1 -0
  10. package/dist/base64.d.ts +13 -0
  11. package/dist/base64.d.ts.map +1 -0
  12. package/dist/base64.js +117 -0
  13. package/dist/base64.js.map +1 -0
  14. package/dist/browser.d.ts +3 -0
  15. package/dist/browser.d.ts.map +1 -0
  16. package/dist/browser.js +28 -0
  17. package/dist/browser.js.map +1 -0
  18. package/dist/cuid/cuid.browser.d.ts +18 -0
  19. package/dist/cuid/cuid.browser.d.ts.map +1 -0
  20. package/dist/cuid/cuid.browser.js +80 -0
  21. package/dist/cuid/cuid.browser.js.map +1 -0
  22. package/dist/cuid/cuid.node.d.ts +18 -0
  23. package/dist/cuid/cuid.node.d.ts.map +1 -0
  24. package/dist/cuid/cuid.node.js +83 -0
  25. package/dist/cuid/cuid.node.js.map +1 -0
  26. package/dist/effect/Effect.d.ts +28 -0
  27. package/dist/effect/Effect.d.ts.map +1 -0
  28. package/dist/effect/Effect.js +82 -0
  29. package/dist/effect/Effect.js.map +1 -0
  30. package/dist/effect/Error.d.ts +11 -0
  31. package/dist/effect/Error.d.ts.map +1 -0
  32. package/dist/effect/Error.js +7 -0
  33. package/dist/effect/Error.js.map +1 -0
  34. package/dist/effect/Schedule.d.ts +4 -0
  35. package/dist/effect/Schedule.d.ts.map +1 -0
  36. package/dist/effect/Schedule.js +5 -0
  37. package/dist/effect/Schedule.js.map +1 -0
  38. package/dist/effect/Scheduler.d.ts +4 -0
  39. package/dist/effect/Scheduler.d.ts.map +1 -0
  40. package/dist/effect/Scheduler.js +10 -0
  41. package/dist/effect/Scheduler.js.map +1 -0
  42. package/dist/effect/Schema/debug-diff.d.ts +12 -0
  43. package/dist/effect/Schema/debug-diff.d.ts.map +1 -0
  44. package/dist/effect/Schema/debug-diff.js +51 -0
  45. package/dist/effect/Schema/debug-diff.js.map +1 -0
  46. package/dist/effect/Schema/debug-diff.test.d.ts +2 -0
  47. package/dist/effect/Schema/debug-diff.test.d.ts.map +1 -0
  48. package/dist/effect/Schema/debug-diff.test.js +91 -0
  49. package/dist/effect/Schema/debug-diff.test.js.map +1 -0
  50. package/dist/effect/Schema/index.d.ts +18 -0
  51. package/dist/effect/Schema/index.d.ts.map +1 -0
  52. package/dist/effect/Schema/index.js +29 -0
  53. package/dist/effect/Schema/index.js.map +1 -0
  54. package/dist/effect/Schema/msgpack.d.ts +3 -0
  55. package/dist/effect/Schema/msgpack.d.ts.map +1 -0
  56. package/dist/effect/Schema/msgpack.js +7 -0
  57. package/dist/effect/Schema/msgpack.js.map +1 -0
  58. package/dist/effect/ServiceContext.d.ts +37 -0
  59. package/dist/effect/ServiceContext.d.ts.map +1 -0
  60. package/dist/effect/ServiceContext.js +55 -0
  61. package/dist/effect/ServiceContext.js.map +1 -0
  62. package/dist/effect/Stream.d.ts +10 -0
  63. package/dist/effect/Stream.d.ts.map +1 -0
  64. package/dist/effect/Stream.js +17 -0
  65. package/dist/effect/Stream.js.map +1 -0
  66. package/dist/effect/SubscriptionRef.d.ts +11 -0
  67. package/dist/effect/SubscriptionRef.d.ts.map +1 -0
  68. package/dist/effect/SubscriptionRef.js +5 -0
  69. package/dist/effect/SubscriptionRef.js.map +1 -0
  70. package/dist/effect/WebChannel.d.ts +30 -0
  71. package/dist/effect/WebChannel.d.ts.map +1 -0
  72. package/dist/effect/WebChannel.js +44 -0
  73. package/dist/effect/WebChannel.js.map +1 -0
  74. package/dist/effect/WebLock.d.ts +9 -0
  75. package/dist/effect/WebLock.d.ts.map +1 -0
  76. package/dist/effect/WebLock.js +73 -0
  77. package/dist/effect/WebLock.js.map +1 -0
  78. package/dist/effect/index.d.ts +21 -0
  79. package/dist/effect/index.d.ts.map +1 -0
  80. package/dist/effect/index.js +20 -0
  81. package/dist/effect/index.js.map +1 -0
  82. package/dist/fast-deep-equal.d.ts +2 -0
  83. package/dist/fast-deep-equal.d.ts.map +1 -0
  84. package/dist/fast-deep-equal.js +79 -0
  85. package/dist/fast-deep-equal.js.map +1 -0
  86. package/dist/global.d.ts +5 -0
  87. package/dist/global.d.ts.map +1 -0
  88. package/dist/global.js +2 -0
  89. package/dist/global.js.map +1 -0
  90. package/dist/guards.d.ts +6 -0
  91. package/dist/guards.d.ts.map +1 -0
  92. package/dist/guards.js +6 -0
  93. package/dist/guards.js.map +1 -0
  94. package/dist/index.d.ts +76 -0
  95. package/dist/index.d.ts.map +1 -0
  96. package/dist/index.js +176 -0
  97. package/dist/index.js.map +1 -0
  98. package/dist/misc.d.ts +3 -0
  99. package/dist/misc.d.ts.map +1 -0
  100. package/dist/misc.js +24 -0
  101. package/dist/misc.js.map +1 -0
  102. package/dist/nanoid/index.d.ts +2 -0
  103. package/dist/nanoid/index.d.ts.map +1 -0
  104. package/dist/nanoid/index.js +2 -0
  105. package/dist/nanoid/index.js.map +1 -0
  106. package/dist/object/index.d.ts +10 -0
  107. package/dist/object/index.d.ts.map +1 -0
  108. package/dist/object/index.js +10 -0
  109. package/dist/object/index.js.map +1 -0
  110. package/dist/object/omit.d.ts +3 -0
  111. package/dist/object/omit.d.ts.map +1 -0
  112. package/dist/object/omit.js +14 -0
  113. package/dist/object/omit.js.map +1 -0
  114. package/dist/object/pick.d.ts +14 -0
  115. package/dist/object/pick.d.ts.map +1 -0
  116. package/dist/object/pick.js +17 -0
  117. package/dist/object/pick.js.map +1 -0
  118. package/dist/promise.d.ts +6 -0
  119. package/dist/promise.d.ts.map +1 -0
  120. package/dist/promise.js +27 -0
  121. package/dist/promise.js.map +1 -0
  122. package/dist/set.d.ts +2 -0
  123. package/dist/set.d.ts.map +1 -0
  124. package/dist/set.js +10 -0
  125. package/dist/set.js.map +1 -0
  126. package/dist/string.d.ts +5 -0
  127. package/dist/string.d.ts.map +1 -0
  128. package/dist/string.js +8 -0
  129. package/dist/string.js.map +1 -0
  130. package/dist/time.d.ts +12 -0
  131. package/dist/time.d.ts.map +1 -0
  132. package/dist/time.js +22 -0
  133. package/dist/time.js.map +1 -0
  134. package/package.json +74 -0
  135. package/src/Deferred.ts +24 -0
  136. package/src/NoopTracer.ts +75 -0
  137. package/src/ambient.d.ts +3 -0
  138. package/src/base64.ts +123 -0
  139. package/src/browser.ts +32 -0
  140. package/src/cuid/cuid.browser.ts +95 -0
  141. package/src/cuid/cuid.node.ts +103 -0
  142. package/src/effect/Effect.ts +180 -0
  143. package/src/effect/Error.ts +6 -0
  144. package/src/effect/Schedule.ts +10 -0
  145. package/src/effect/Scheduler.ts +14 -0
  146. package/src/effect/Schema/debug-diff.test.ts +102 -0
  147. package/src/effect/Schema/debug-diff.ts +58 -0
  148. package/src/effect/Schema/index.ts +58 -0
  149. package/src/effect/Schema/msgpack.ts +8 -0
  150. package/src/effect/ServiceContext.ts +108 -0
  151. package/src/effect/Stream.ts +63 -0
  152. package/src/effect/SubscriptionRef.ts +22 -0
  153. package/src/effect/WebChannel.ts +116 -0
  154. package/src/effect/WebLock.ts +95 -0
  155. package/src/effect/index.ts +91 -0
  156. package/src/fast-deep-equal.ts +72 -0
  157. package/src/global.ts +5 -0
  158. package/src/guards.ts +8 -0
  159. package/src/index.ts +240 -0
  160. package/src/misc.ts +26 -0
  161. package/src/nanoid/index.ts +1 -0
  162. package/src/object/index.ts +24 -0
  163. package/src/object/omit.ts +17 -0
  164. package/src/object/pick.ts +27 -0
  165. package/src/promise.ts +43 -0
  166. package/src/set.ts +10 -0
  167. package/src/string.ts +9 -0
  168. package/src/time.ts +25 -0
  169. package/tsconfig.json +10 -0
@@ -0,0 +1,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
@@ -0,0 +1,5 @@
1
+ declare global {
2
+ export type TODO<_Reason extends string = 'unknown'> = any
3
+ }
4
+
5
+ export {}
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