@effect/platform-browser 0.37.27 → 0.38.1

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.
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * @since 1.0.0
3
3
  */
4
- import { RefailError } from "@effect/platform/Error"
4
+ import { TypeIdError } from "@effect/platform/Error"
5
5
  import * as Context from "effect/Context"
6
6
  import * as Effect from "effect/Effect"
7
7
  import * as Either from "effect/Either"
@@ -64,9 +64,14 @@ export type ErrorTypeId = typeof ErrorTypeId
64
64
  * @since 1.0.0
65
65
  * @category errors
66
66
  */
67
- export class GeolocationError extends RefailError(ErrorTypeId, "GeolocationError")<{
67
+ export class GeolocationError extends TypeIdError(ErrorTypeId, "GeolocationError")<{
68
68
  readonly reason: "PositionUnavailable" | "PermissionDenied" | "Timeout"
69
- }> {}
69
+ readonly cause: unknown
70
+ }> {
71
+ get message() {
72
+ return this.reason
73
+ }
74
+ }
70
75
 
71
76
  const makeQueue = (
72
77
  options:
@@ -81,11 +86,11 @@ const makeQueue = (
81
86
  Effect.sync(() =>
82
87
  navigator.geolocation.watchPosition(
83
88
  (position) => queue.unsafeOffer(Either.right(position)),
84
- (error) => {
85
- if (error.code === error.PERMISSION_DENIED) {
86
- queue.unsafeOffer(Either.left(new GeolocationError({ reason: "PermissionDenied", error })))
87
- } else if (error.code === error.TIMEOUT) {
88
- queue.unsafeOffer(Either.left(new GeolocationError({ reason: "Timeout", error })))
89
+ (cause) => {
90
+ if (cause.code === cause.PERMISSION_DENIED) {
91
+ queue.unsafeOffer(Either.left(new GeolocationError({ reason: "PermissionDenied", cause })))
92
+ } else if (cause.code === cause.TIMEOUT) {
93
+ queue.unsafeOffer(Either.left(new GeolocationError({ reason: "Timeout", cause })))
89
94
  }
90
95
  },
91
96
  options
@@ -65,7 +65,7 @@ export const makeXMLHttpRequest = Client.makeDefault((request, url, signal, fibe
65
65
  new Error.RequestError({
66
66
  request,
67
67
  reason: "Transport",
68
- error: xhr.statusText
68
+ cause: xhr.statusText
69
69
  })
70
70
  ))
71
71
  }
@@ -98,12 +98,12 @@ const sendBody = (
98
98
  return next
99
99
  }),
100
100
  {
101
- onFailure: (error) =>
101
+ onFailure: (cause) =>
102
102
  Effect.fail(
103
103
  new Error.RequestError({
104
104
  request,
105
105
  reason: "Encode",
106
- error
106
+ cause
107
107
  })
108
108
  ),
109
109
  onSuccess: (body) => Effect.sync(() => xhr.send(body))
@@ -286,12 +286,12 @@ class ClientResponseImpl extends IncomingMessageImpl<Error.ResponseError> implem
286
286
  readonly request: ClientRequest.HttpClientRequest,
287
287
  source: XMLHttpRequest
288
288
  ) {
289
- super(source, (_) =>
289
+ super(source, (cause) =>
290
290
  new Error.ResponseError({
291
291
  request,
292
292
  response: this,
293
293
  reason: "Decode",
294
- error: _
294
+ cause
295
295
  }))
296
296
  this[ClientResponse.TypeId] = ClientResponse.TypeId
297
297
  }
@@ -3,58 +3,43 @@ import { WorkerError } from "@effect/platform/WorkerError"
3
3
  import * as Deferred from "effect/Deferred"
4
4
  import * as Effect from "effect/Effect"
5
5
  import * as Layer from "effect/Layer"
6
- import * as Queue from "effect/Queue"
7
-
8
- const platformWorkerImpl = Worker.PlatformWorker.of({
9
- [Worker.PlatformWorkerTypeId]: Worker.PlatformWorkerTypeId,
10
- spawn<I, O>(worker_: unknown) {
11
- return Effect.gen(function*(_) {
12
- const worker = worker_ as globalThis.SharedWorker | globalThis.Worker | MessagePort
13
- let port: globalThis.Worker | MessagePort
14
- if ("port" in worker) {
15
- port = worker.port
16
- } else {
17
- port = worker
18
- }
19
-
20
- yield* _(Effect.addFinalizer(() => Effect.sync(() => port.postMessage([1]))))
21
-
22
- const queue = yield* _(Queue.unbounded<Worker.BackingWorker.Message<O>>())
23
- const latch = yield* Deferred.make<void>()
24
-
25
- const fiber = yield* _(
26
- Effect.async<never, WorkerError, never>((resume) => {
27
- function onMessage(event: MessageEvent) {
28
- queue.unsafeOffer((event as MessageEvent).data)
29
- }
30
- function onError(event: ErrorEvent) {
31
- resume(new WorkerError({ reason: "unknown", error: event.error ?? event.message }))
32
- }
33
- port.addEventListener("message", onMessage as any)
34
- port.addEventListener("error", onError as any)
35
- Deferred.unsafeDone(latch, Effect.void)
36
- return Effect.sync(() => {
37
- port.removeEventListener("message", onMessage as any)
38
- port.removeEventListener("error", onError as any)
39
- })
40
- }),
41
- Effect.interruptible,
42
- Effect.forkScoped
43
- )
44
- yield* Deferred.await(latch)
45
-
46
- if ("start" in port) {
47
- port.start()
48
- }
49
-
50
- const send = (message: I, transfers?: ReadonlyArray<unknown>) =>
51
- Effect.try({
52
- try: () => port.postMessage([0, message], transfers as any),
53
- catch: (error) => new WorkerError({ reason: "send", error })
6
+ import * as Scope from "effect/Scope"
7
+
8
+ const platformWorkerImpl = Worker.makePlatform<globalThis.SharedWorker | globalThis.Worker | MessagePort>()({
9
+ setup({ scope, worker }) {
10
+ const port = "port" in worker ? worker.port : worker
11
+ return Effect.as(
12
+ Scope.addFinalizer(
13
+ scope,
14
+ Effect.sync(() => {
15
+ port.postMessage([1])
54
16
  })
55
-
56
- return { fiber, queue, send }
57
- })
17
+ ),
18
+ port
19
+ )
20
+ },
21
+ listen({ deferred, emit, port, scope }) {
22
+ function onMessage(event: MessageEvent) {
23
+ emit(event.data)
24
+ }
25
+ function onError(event: ErrorEvent) {
26
+ Deferred.unsafeDone(
27
+ deferred,
28
+ new WorkerError({ reason: "unknown", cause: event.error ?? event.message })
29
+ )
30
+ }
31
+ port.addEventListener("message", onMessage as any)
32
+ port.addEventListener("error", onError as any)
33
+ if ("start" in port) {
34
+ port.start()
35
+ }
36
+ return Scope.addFinalizer(
37
+ scope,
38
+ Effect.sync(() => {
39
+ port.removeEventListener("message", onMessage as any)
40
+ port.removeEventListener("error", onError as any)
41
+ })
42
+ )
58
43
  }
59
44
  })
60
45
 
@@ -1,12 +1,14 @@
1
1
  import { WorkerError } from "@effect/platform/WorkerError"
2
2
  import * as Runner from "@effect/platform/WorkerRunner"
3
- import * as Cause from "effect/Cause"
3
+ import * as Deferred from "effect/Deferred"
4
4
  import * as Effect from "effect/Effect"
5
+ import * as ExecStrategy from "effect/ExecutionStrategy"
6
+ import * as Exit from "effect/Exit"
7
+ import * as FiberId from "effect/FiberId"
5
8
  import * as FiberSet from "effect/FiberSet"
6
9
  import { globalValue } from "effect/GlobalValue"
7
10
  import * as Layer from "effect/Layer"
8
- import * as Queue from "effect/Queue"
9
- import * as Schedule from "effect/Schedule"
11
+ import * as Scope from "effect/Scope"
10
12
 
11
13
  const cachedPorts = globalValue("@effect/platform-browser/Worker/cachedPorts", () => new Set<MessagePort>())
12
14
  function globalHandleConnect(event: MessageEvent) {
@@ -16,97 +18,122 @@ if (typeof self !== "undefined" && "onconnect" in self) {
16
18
  self.onconnect = globalHandleConnect
17
19
  }
18
20
 
19
- const platformRunnerImpl = Runner.PlatformRunner.of({
20
- [Runner.PlatformRunnerTypeId]: Runner.PlatformRunnerTypeId,
21
- start<I, O>(shutdown: Effect.Effect<void>) {
22
- return Effect.gen(function*() {
23
- let currentPortId = 0
24
-
25
- yield* Effect.addFinalizer(() => Effect.sync(() => self.close()))
21
+ /** @internal */
22
+ export const make = (self: MessagePort | Window) =>
23
+ Runner.PlatformRunner.of({
24
+ [Runner.PlatformRunnerTypeId]: Runner.PlatformRunnerTypeId,
25
+ start<I, O>() {
26
+ return Effect.sync(() => {
27
+ let currentPortId = 0
26
28
 
27
- const queue = yield* Queue.unbounded<readonly [portId: number, message: I]>()
28
- const runFork = yield* FiberSet.makeRuntime<never>()
29
- const ports = new Map<number, MessagePort>()
30
- const send = (portId: number, message: O, transfer?: ReadonlyArray<unknown>) =>
31
- Effect.sync(() => {
32
- ports.get(portId)?.postMessage([1, message], {
33
- transfer: transfer as any
29
+ const ports = new Map<number, readonly [MessagePort, Scope.CloseableScope]>()
30
+ const send = (portId: number, message: O, transfer?: ReadonlyArray<unknown>) =>
31
+ Effect.sync(() => {
32
+ ;(ports.get(portId)?.[0] ?? self).postMessage([1, message], {
33
+ transfer: transfer as any
34
+ })
34
35
  })
35
- })
36
-
37
- function handlePort(port: MessagePort, sharedWorker: boolean) {
38
- const portId = currentPortId++
39
- ports.set(portId, port)
40
-
41
- Effect.async<never, WorkerError, never>((resume) => {
42
- function onMessage(event: MessageEvent) {
43
- const message = (event as MessageEvent).data as Runner.BackingRunner.Message<I>
44
- if (message[0] === 0) {
45
- queue.unsafeOffer([portId, message[1]])
46
- } else if (sharedWorker && ports.size > 1) {
47
- resume(Effect.interrupt)
48
- } else {
49
- Effect.runFork(shutdown)
50
- }
51
- }
52
- function onMessageError(error: ErrorEvent) {
53
- resume(new WorkerError({ reason: "decode", error: error.error ?? error.message }))
54
- }
55
- function onError(error: ErrorEvent) {
56
- resume(new WorkerError({ reason: "unknown", error: error.error ?? error.message }))
57
- }
58
- port.addEventListener("message", onMessage as any)
59
- port.addEventListener("messageerror", onMessageError as any)
60
- port.addEventListener("error", onError as any)
61
36
 
62
- // ready
63
- if ("start" in port) {
64
- port.start()
65
- }
66
- port.postMessage([0])
37
+ const run = <A, E, R>(handler: (portId: number, message: I) => Effect.Effect<A, E, R>) =>
38
+ Effect.uninterruptibleMask((restore) =>
39
+ Scope.make().pipe(
40
+ Effect.bindTo("scope"),
41
+ Effect.bind("fiberSet", ({ scope }) => FiberSet.make<any, WorkerError | E>().pipe(Scope.extend(scope))),
42
+ Effect.bind("runFork", ({ fiberSet }) => FiberSet.runtime(fiberSet)<R>()),
43
+ Effect.tap(({ fiberSet, runFork, scope }) => {
44
+ function onMessage(portId: number) {
45
+ return function(event: MessageEvent) {
46
+ const message = (event as MessageEvent).data as Runner.BackingRunner.Message<I>
47
+ if (message[0] === 0) {
48
+ runFork(restore(handler(portId, message[1])))
49
+ } else {
50
+ const port = ports.get(portId)
51
+ if (port) {
52
+ Effect.runFork(Scope.close(port[1], Exit.void))
53
+ }
54
+ ports.delete(portId)
55
+ if (ports.size === 0) {
56
+ Deferred.unsafeDone(fiberSet.deferred, Exit.interrupt(FiberId.none))
57
+ }
58
+ }
59
+ }
60
+ }
61
+ function onMessageError(error: MessageEvent) {
62
+ Deferred.unsafeDone(
63
+ fiberSet.deferred,
64
+ new WorkerError({ reason: "decode", cause: error.data })
65
+ )
66
+ }
67
+ function onError(error: any) {
68
+ Deferred.unsafeDone(
69
+ fiberSet.deferred,
70
+ new WorkerError({ reason: "unknown", cause: error.data })
71
+ )
72
+ }
73
+ function handlePort(port: MessagePort) {
74
+ return Scope.fork(scope, ExecStrategy.sequential).pipe(
75
+ Effect.flatMap((scope) => {
76
+ const portId = currentPortId++
77
+ ports.set(portId, [port, scope])
78
+ const onMsg = onMessage(portId)
79
+ port.addEventListener("message", onMsg)
80
+ port.addEventListener("messageerror", onMessageError)
81
+ if ("start" in port) {
82
+ port.start()
83
+ }
84
+ port.postMessage([0])
85
+ return Scope.addFinalizer(
86
+ scope,
87
+ Effect.sync(() => {
88
+ port.removeEventListener("message", onMsg)
89
+ port.removeEventListener("messageerror", onError)
90
+ })
91
+ )
92
+ }),
93
+ runFork
94
+ )
95
+ }
96
+ self.addEventListener("error", onError)
97
+ let prevOnConnect: unknown | undefined
98
+ if ("onconnect" in self) {
99
+ prevOnConnect = self.onconnect
100
+ self.onconnect = function(event: MessageEvent) {
101
+ const port = (event as MessageEvent).ports[0]
102
+ handlePort(port)
103
+ }
104
+ for (const port of cachedPorts) {
105
+ handlePort(port)
106
+ }
107
+ cachedPorts.clear()
108
+ } else {
109
+ handlePort(self as any)
110
+ }
111
+ return Scope.addFinalizer(
112
+ scope,
113
+ Effect.sync(() => {
114
+ self.removeEventListener("error", onError)
115
+ if ("onconnect" in self) {
116
+ self.onconnect = prevOnConnect
117
+ }
118
+ self.close()
119
+ })
120
+ )
121
+ }),
122
+ Effect.flatMap(({ fiberSet, scope }) =>
123
+ restore(FiberSet.join(fiberSet) as Effect.Effect<never, E | WorkerError>).pipe(
124
+ Effect.ensuring(Scope.close(scope, Exit.void))
125
+ )
126
+ )
127
+ )
128
+ )
67
129
 
68
- return Effect.sync(() => {
69
- port.removeEventListener("message", onMessage as any)
70
- port.removeEventListener("messageerror", onMessageError as any)
71
- port.removeEventListener("error", onError as any)
72
- })
73
- }).pipe(
74
- Effect.tapErrorCause((cause) => Cause.isInterruptedOnly(cause) ? Effect.void : Effect.logDebug(cause)),
75
- Effect.retry(Schedule.forever),
76
- Effect.annotateLogs({
77
- package: "@effect/platform-browser",
78
- module: "WorkerRunner"
79
- }),
80
- Effect.ensuring(Effect.sync(() => {
81
- ports.delete(portId)
82
- })),
83
- Effect.interruptible,
84
- runFork
85
- )
86
- }
130
+ return { run, send }
131
+ })
132
+ }
133
+ })
87
134
 
88
- if ("onconnect" in self) {
89
- self.onconnect = function(event: MessageEvent) {
90
- const port = (event as MessageEvent).ports[0]
91
- handlePort(port, true)
92
- }
93
- yield* Effect.addFinalizer(() =>
94
- Effect.sync(() => {
95
- ;(self as any).onconnect = globalHandleConnect
96
- })
97
- )
98
- for (const port of cachedPorts) {
99
- handlePort(port, true)
100
- }
101
- cachedPorts.clear()
102
- } else {
103
- handlePort(self as any, false)
104
- }
105
-
106
- return { queue, send }
107
- })
108
- }
109
- })
135
+ /** @internal */
136
+ export const layerMessagePort = (port: MessagePort | Window) => Layer.succeed(Runner.PlatformRunner, make(port))
110
137
 
111
138
  /** @internal */
112
- export const layer = Layer.succeed(Runner.PlatformRunner, platformRunnerImpl)
139
+ export const layer = Layer.succeed(Runner.PlatformRunner, make(self))