@effect/platform 0.37.0 → 0.37.2

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.
@@ -12,11 +12,13 @@ import { identity, pipe } from "effect/Function"
12
12
  import * as Layer from "effect/Layer"
13
13
  import * as Pool from "effect/Pool"
14
14
  import * as Queue from "effect/Queue"
15
+ import * as ReadonlyArray from "effect/ReadonlyArray"
16
+ import * as Schedule from "effect/Schedule"
15
17
  import type * as Scope from "effect/Scope"
16
18
  import * as Stream from "effect/Stream"
17
19
  import * as Transferable from "../Transferable.js"
18
20
  import type * as Worker from "../Worker.js"
19
- import type { WorkerError } from "../WorkerError.js"
21
+ import { WorkerError } from "../WorkerError.js"
20
22
 
21
23
  /** @internal */
22
24
  export const defaultQueue = <I>() =>
@@ -55,22 +57,71 @@ export const makeManager = Effect.gen(function*(_) {
55
57
  let idCounter = 0
56
58
  return WorkerManager.of({
57
59
  [WorkerManagerTypeId]: WorkerManagerTypeId,
58
- spawn<I, E, O>({ encode, permits = 1, queue, spawn, transfers = (_) => [] }: Worker.Worker.Options<I>) {
60
+ spawn<I, E, O>({
61
+ encode,
62
+ initialMessage,
63
+ permits = 1,
64
+ queue,
65
+ spawn,
66
+ transfers = (_) => []
67
+ }: Worker.Worker.Options<I>) {
59
68
  return Effect.gen(function*(_) {
60
69
  const id = idCounter++
61
70
  let requestIdCounter = 0
62
- const readyLatch = yield* _(Deferred.make<never, void>())
63
71
  const semaphore = yield* _(Effect.makeSemaphore(permits))
64
72
  const requestMap = new Map<
65
73
  number,
66
- readonly [Queue.Queue<Exit.Exit<E | WorkerError, O>>, Deferred.Deferred<never, void>]
74
+ readonly [Queue.Queue<Exit.Exit<E | WorkerError, ReadonlyArray<O>>>, Deferred.Deferred<never, void>]
67
75
  >()
76
+ const sendQueue = yield* _(Effect.acquireRelease(
77
+ Queue.unbounded<readonly [message: Worker.Worker.Request, transfers?: ReadonlyArray<unknown>]>(),
78
+ Queue.shutdown
79
+ ))
68
80
 
69
81
  const outbound = queue ?? (yield* _(defaultQueue<I>()))
70
82
  yield* _(Effect.addFinalizer(() => outbound.shutdown))
71
83
 
72
- const backing = yield* _(
73
- platform.spawn<Worker.Worker.Request, Worker.Worker.Response<E, O>>(spawn(id))
84
+ yield* _(
85
+ Effect.gen(function*(_) {
86
+ const readyLatch = yield* _(Deferred.make<never, void>())
87
+ const backing = yield* _(
88
+ platform.spawn<Worker.Worker.Request, Worker.Worker.Response<E, O>>(spawn(id))
89
+ )
90
+ const send = pipe(
91
+ sendQueue.take,
92
+ Effect.flatMap(([message, transfers]) => backing.send(message, transfers)),
93
+ Effect.forever
94
+ )
95
+ const take = pipe(
96
+ Queue.take(backing.queue),
97
+ Effect.flatMap((msg) => {
98
+ if (msg[0] === 0) {
99
+ return Deferred.complete(readyLatch, Effect.unit)
100
+ }
101
+ return handleMessage(msg[1])
102
+ }),
103
+ Effect.forever
104
+ )
105
+ return yield* _(Effect.all([
106
+ Fiber.join(backing.fiber),
107
+ Effect.zipRight(Deferred.await(readyLatch), send),
108
+ take
109
+ ], { concurrency: "unbounded" }))
110
+ }),
111
+ Effect.scoped,
112
+ Effect.onError((cause) =>
113
+ Effect.forEach(requestMap.values(), ([queue]) => Queue.offer(queue, Exit.failCause(cause)))
114
+ ),
115
+ Effect.retry(
116
+ Schedule.exponential("250 millis").pipe(
117
+ Schedule.union(Schedule.spaced("30 seconds"))
118
+ )
119
+ ),
120
+ Effect.annotateLogs({
121
+ package: "@effect/platform",
122
+ module: "Worker"
123
+ }),
124
+ Effect.forkScoped
74
125
  )
75
126
 
76
127
  yield* _(Effect.addFinalizer(() =>
@@ -82,42 +133,34 @@ export const makeManager = Effect.gen(function*(_) {
82
133
  )
83
134
  ))
84
135
 
85
- const handleMessage = (msg: Worker.BackingWorker.Message<Worker.Worker.Response<E, O>>) =>
136
+ const handleMessage = (response: Worker.Worker.Response<E, O>) =>
86
137
  Effect.suspend(() => {
87
- switch (msg[0]) {
138
+ const queue = requestMap.get(response[0])
139
+ if (!queue) return Effect.unit
140
+
141
+ switch (response[1]) {
142
+ // data
88
143
  case 0: {
89
- return Deferred.complete(readyLatch, Effect.unit)
144
+ return Queue.offer(queue[0], Exit.succeed(response[2]))
90
145
  }
146
+ // end
91
147
  case 1: {
92
- const response = msg[1]
93
- const queue = requestMap.get(response[0])
94
- if (!queue) return Effect.unit
95
-
96
- switch (response[1]) {
97
- // data
98
- case 0: {
99
- return Queue.offer(queue[0], Exit.succeed(response[2]))
100
- }
101
- // end
102
- case 1: {
103
- return response.length === 2 ?
104
- Queue.offer(queue[0], Exit.failCause(Cause.empty)) :
105
- Effect.zipRight(
106
- Queue.offer(queue[0], Exit.succeed(response[2])),
107
- Queue.offer(queue[0], Exit.failCause(Cause.empty))
108
- )
109
- }
110
- // error / defect
111
- case 2:
112
- case 3: {
113
- return Queue.offer(
114
- queue[0],
115
- response[1] === 2
116
- ? Exit.fail(response[2])
117
- : Exit.die(response[2])
118
- )
119
- }
120
- }
148
+ return response.length === 2 ?
149
+ Queue.offer(queue[0], Exit.failCause(Cause.empty)) :
150
+ Effect.zipRight(
151
+ Queue.offer(queue[0], Exit.succeed(response[2])),
152
+ Queue.offer(queue[0], Exit.failCause(Cause.empty))
153
+ )
154
+ }
155
+ // error / defect
156
+ case 2:
157
+ case 3: {
158
+ return Queue.offer(
159
+ queue[0],
160
+ response[1] === 2
161
+ ? Exit.fail(response[2])
162
+ : Exit.die(response[2])
163
+ )
121
164
  }
122
165
  }
123
166
  })
@@ -126,7 +169,7 @@ export const makeManager = Effect.gen(function*(_) {
126
169
  Effect.tap(
127
170
  Effect.all([
128
171
  Effect.sync(() => requestIdCounter++),
129
- Queue.unbounded<Exit.Exit<E | WorkerError, O>>(),
172
+ Queue.unbounded<Exit.Exit<E | WorkerError, ReadonlyArray<O>>>(),
130
173
  Deferred.make<never, void>()
131
174
  ]),
132
175
  ([id, queue, deferred]) =>
@@ -137,7 +180,11 @@ export const makeManager = Effect.gen(function*(_) {
137
180
  )
138
181
 
139
182
  const executeRelease = (
140
- [id, , deferred]: [number, Queue.Queue<Exit.Exit<E | WorkerError, O>>, Deferred.Deferred<never, void>],
183
+ [id, , deferred]: [
184
+ number,
185
+ Queue.Queue<Exit.Exit<E | WorkerError, ReadonlyArray<O>>>,
186
+ Deferred.Deferred<never, void>
187
+ ],
141
188
  exit: Exit.Exit<unknown, unknown>
142
189
  ) => {
143
190
  const release = Effect.zipRight(
@@ -145,10 +192,7 @@ export const makeManager = Effect.gen(function*(_) {
145
192
  Effect.sync(() => requestMap.delete(id))
146
193
  )
147
194
  return Exit.isInterrupted(exit) ?
148
- Effect.zipRight(
149
- Effect.ignore(backing.send([id, 1])),
150
- release
151
- ) :
195
+ Effect.zipRight(sendQueue.offer([[id, 1]]), release) :
152
196
  release
153
197
  }
154
198
 
@@ -164,7 +208,7 @@ export const makeManager = Effect.gen(function*(_) {
164
208
  Queue.take(queue),
165
209
  Exit.match({
166
210
  onFailure: (cause) => Cause.isEmpty(cause) ? Channel.unit : Channel.failCause(cause),
167
- onSuccess: (value) => Channel.flatMap(Channel.write(Chunk.of(value)), () => loop)
211
+ onSuccess: (value) => Channel.flatMap(Channel.write(Chunk.unsafeFromArray(value)), () => loop)
168
212
  })
169
213
  )
170
214
  return Stream.fromChannel(loop)
@@ -174,18 +218,11 @@ export const makeManager = Effect.gen(function*(_) {
174
218
  const executeEffect = (request: I) =>
175
219
  Effect.acquireUseRelease(
176
220
  executeAcquire(request),
177
- ([, queue]) => Effect.flatten(Queue.take(queue)),
221
+ ([, queue]) => Effect.flatMap(Queue.take(queue), Exit.map(ReadonlyArray.unsafeGet(0))),
178
222
  executeRelease
179
223
  )
180
224
 
181
- const handleMessages = yield* _(
182
- Queue.take(backing.queue),
183
- Effect.flatMap(handleMessage),
184
- Effect.forever,
185
- Effect.forkScoped
186
- )
187
-
188
- const postMessages = pipe(
225
+ yield* _(
189
226
  semaphore.take(1),
190
227
  Effect.zipRight(outbound.take),
191
228
  Effect.flatMap(([id, request]) =>
@@ -194,35 +231,32 @@ export const makeManager = Effect.gen(function*(_) {
194
231
  const result = requestMap.get(id)
195
232
  if (!result) return Effect.unit
196
233
  const transferables = transfers(request)
197
- const payload = encode ? encode(request) : request
198
- return Effect.zipRight(
199
- Effect.catchAllCause(
200
- backing.send([id, 0, payload], transferables),
201
- (cause) => Queue.offer(result[0], Exit.failCause(cause))
234
+ return pipe(
235
+ Effect.flatMap(
236
+ encode ? encode(request) : Effect.succeed(request),
237
+ (payload) => sendQueue.offer([[id, 0, payload], transferables])
202
238
  ),
203
- Deferred.await(result[1])
239
+ Effect.catchAllCause((cause) => Queue.offer(result[0], Exit.failCause(cause))),
240
+ Effect.zipRight(Deferred.await(result[1]))
204
241
  )
205
242
  }),
206
243
  Effect.ensuring(semaphore.release(1)),
207
244
  Effect.fork
208
245
  )
209
246
  ),
210
- Effect.forever
211
- )
212
-
213
- const postMessagesFiber = yield* _(
214
- Deferred.await(readyLatch),
215
- Effect.zipRight(postMessages),
247
+ Effect.forever,
216
248
  Effect.forkScoped
217
249
  )
218
250
 
219
- const join = Fiber.joinAll([backing.fiber, handleMessages, postMessagesFiber]) as Effect.Effect<
220
- never,
221
- WorkerError,
222
- never
223
- >
251
+ if (initialMessage) {
252
+ yield* _(
253
+ Effect.sync(initialMessage),
254
+ Effect.flatMap(executeEffect),
255
+ Effect.mapError((error) => WorkerError("spawn", error))
256
+ )
257
+ }
224
258
 
225
- return { id, join, execute, executeEffect }
259
+ return { id, execute, executeEffect }
226
260
  }).pipe(Effect.parallelFinalizers)
227
261
  }
228
262
  })
@@ -303,6 +337,9 @@ export const makeSerialized = <
303
337
  ...options,
304
338
  transfers(message) {
305
339
  return Transferable.get(message)
340
+ },
341
+ encode(message) {
342
+ return Effect.mapError(Serializable.serialize(message), (error) => WorkerError("encode", error))
306
343
  }
307
344
  })
308
345
  )
@@ -310,8 +347,7 @@ export const makeSerialized = <
310
347
  const parseSuccess = Schema.decode(Serializable.successSchema(message as any))
311
348
  const parseFailure = Schema.decode(Serializable.failureSchema(message as any))
312
349
  return pipe(
313
- Serializable.serialize(message),
314
- Stream.flatMap((message) => backing.execute(message)),
350
+ backing.execute(message),
315
351
  Stream.catchAll((error) => Effect.flatMap(parseFailure(error), Effect.fail)),
316
352
  Stream.mapEffect(parseSuccess)
317
353
  )
@@ -319,18 +355,13 @@ export const makeSerialized = <
319
355
  const executeEffect = <Req extends I>(message: Req) => {
320
356
  const parseSuccess = Schema.decode(Serializable.successSchema(message as any))
321
357
  const parseFailure = Schema.decode(Serializable.failureSchema(message as any))
322
- return pipe(
323
- Serializable.serialize(message),
324
- Effect.flatMap((message) => backing.executeEffect(message)),
325
- Effect.matchEffect({
326
- onFailure: (error) => Effect.flatMap(parseFailure(error), Effect.fail),
327
- onSuccess: parseSuccess
328
- })
329
- )
358
+ return Effect.matchEffect(backing.executeEffect(message), {
359
+ onFailure: (error) => Effect.flatMap(parseFailure(error), Effect.fail),
360
+ onSuccess: parseSuccess
361
+ })
330
362
  }
331
363
  return identity<Worker.SerializedWorker<I>>({
332
364
  id: backing.id,
333
- join: backing.join,
334
365
  execute: execute as any,
335
366
  executeEffect: executeEffect as any
336
367
  })
@@ -1,11 +1,14 @@
1
1
  import * as Schema from "@effect/schema/Schema"
2
2
  import * as Serializable from "@effect/schema/Serializable"
3
3
  import * as Cause from "effect/Cause"
4
+ import * as Chunk from "effect/Chunk"
4
5
  import * as Context from "effect/Context"
5
6
  import * as Effect from "effect/Effect"
6
7
  import * as Either from "effect/Either"
7
8
  import * as Fiber from "effect/Fiber"
8
9
  import { pipe } from "effect/Function"
10
+ import * as Layer from "effect/Layer"
11
+ import * as Option from "effect/Option"
9
12
  import * as Predicate from "effect/Predicate"
10
13
  import * as Queue from "effect/Queue"
11
14
  import type * as Scope from "effect/Scope"
@@ -28,14 +31,15 @@ export const PlatformRunner = Context.Tag<WorkerRunner.PlatformRunner>(
28
31
  /** @internal */
29
32
  export const make = <I, R, E, O>(
30
33
  process: (request: I) => Stream.Stream<R, E, O> | Effect.Effect<R, E, O>,
31
- options?: WorkerRunner.Runner.Options<O>
34
+ options?: WorkerRunner.Runner.Options<E, O>
32
35
  ) =>
33
36
  Effect.gen(function*(_) {
34
37
  const platform = yield* _(PlatformRunner)
35
38
  const backing = yield* _(platform.start<Worker.Worker.Request<I>, Worker.Worker.Response<E>>())
36
39
  const fiberMap = new Map<number, Fiber.Fiber<never, void>>()
40
+ const parentFiber = Option.getOrThrow(Fiber.getCurrentFiber())
37
41
 
38
- const handleRequests = pipe(
42
+ yield* _(
39
43
  Queue.take(backing.queue),
40
44
  Effect.tap((req) => {
41
45
  const id = req[0]
@@ -51,27 +55,58 @@ export const make = <I, R, E, O>(
51
55
  Effect.matchCauseEffect(stream, {
52
56
  onFailure: (cause) =>
53
57
  Either.match(Cause.failureOrCause(cause), {
54
- onLeft: (error) => backing.send([id, 2, error]),
58
+ onLeft: (error) => {
59
+ const transfers = options?.transfers ? options.transfers(error) : undefined
60
+ return pipe(
61
+ options?.encodeError ? options.encodeError(error) : Effect.succeed(error),
62
+ Effect.flatMap((payload) => backing.send([id, 2, payload as any], transfers)),
63
+ Effect.catchAllCause((cause) => backing.send([id, 3, Cause.squash(cause)]))
64
+ )
65
+ },
55
66
  onRight: (cause) => backing.send([id, 3, Cause.squash(cause)])
56
67
  }),
57
68
  onSuccess: (data) => {
58
69
  const transfers = options?.transfers ? options.transfers(data) : undefined
59
- const payload = options?.encode ? options.encode(data) : data
60
- return backing.send([id, 1, payload], transfers)
70
+ return pipe(
71
+ options?.encodeOutput ? options.encodeOutput(data) : Effect.succeed(data),
72
+ Effect.flatMap((payload) => backing.send([id, 0, [payload]], transfers)),
73
+ Effect.catchAllCause((cause) => backing.send([id, 3, Cause.squash(cause)]))
74
+ )
61
75
  }
62
76
  }) :
63
77
  pipe(
64
78
  stream,
65
- Stream.tap((item) => {
66
- const transfers = options?.transfers ? options.transfers(item) : undefined
67
- const payload = options?.encode ? options.encode(item) : item
68
- return backing.send([id, 0, payload], transfers)
79
+ Stream.chunks,
80
+ Stream.tap((data) => {
81
+ if (options?.encodeOutput === undefined) {
82
+ const payload = Chunk.toReadonlyArray(data)
83
+ const transfers = options?.transfers ? payload.flatMap(options.transfers) : undefined
84
+ return backing.send([id, 0, payload], transfers)
85
+ }
86
+
87
+ const transfers: Array<unknown> = []
88
+ return Effect.flatMap(
89
+ Effect.forEach(data, (data) => {
90
+ if (options?.transfers) {
91
+ transfers.push(...options.transfers(data))
92
+ }
93
+ return Effect.orDie(options.encodeOutput!(data))
94
+ }),
95
+ (payload) => backing.send([id, 0, payload], transfers)
96
+ )
69
97
  }),
70
98
  Stream.runDrain,
71
99
  Effect.matchCauseEffect({
72
100
  onFailure: (cause) =>
73
101
  Either.match(Cause.failureOrCause(cause), {
74
- onLeft: (error) => backing.send([id, 2, error]),
102
+ onLeft: (error) => {
103
+ const transfers = options?.transfers ? options.transfers(error) : undefined
104
+ return pipe(
105
+ options?.encodeError ? options.encodeError(error) : Effect.succeed(error),
106
+ Effect.flatMap((payload) => backing.send([id, 2, payload as any], transfers)),
107
+ Effect.catchAllCause((cause) => backing.send([id, 3, Cause.squash(cause)]))
108
+ )
109
+ },
75
110
  onRight: (cause) => backing.send([id, 3, Cause.squash(cause)])
76
111
  }),
77
112
  onSuccess: () => backing.send([id, 1])
@@ -85,17 +120,19 @@ export const make = <I, R, E, O>(
85
120
  Effect.tap((fiber) => Effect.sync(() => fiberMap.set(id, fiber)))
86
121
  )
87
122
  }),
88
- Effect.forever
89
- )
90
-
91
- return yield* _(
92
- Effect.all([
93
- handleRequests,
94
- Fiber.join(backing.fiber)
95
- ], { concurrency: "unbounded", discard: true }) as Effect.Effect<R, WorkerError.WorkerError, never>
123
+ Effect.forever,
124
+ Effect.onInterrupt(() => Fiber.interrupt(parentFiber)),
125
+ Effect.forkScoped
96
126
  )
97
127
  })
98
128
 
129
+ /** @internal */
130
+ export const layer = <I, R, E, O>(
131
+ process: (request: I) => Stream.Stream<R, E, O> | Effect.Effect<R, E, O>,
132
+ options?: WorkerRunner.Runner.Options<E, O>
133
+ ): Layer.Layer<WorkerRunner.PlatformRunner | R, WorkerError.WorkerError, never> =>
134
+ Layer.scopedDiscard(make(process, options))
135
+
99
136
  /** @internal */
100
137
  export const makeSerialized = <
101
138
  I,
@@ -114,7 +151,7 @@ export const makeSerialized = <
114
151
  | Scope.Scope
115
152
  | (ReturnType<Handlers[keyof Handlers]> extends Stream.Stream<infer R, infer _E, infer _A> ? R : never),
116
153
  WorkerError.WorkerError,
117
- never
154
+ void
118
155
  > => {
119
156
  const parseRequest = Schema.decode(schema)
120
157
  const effectTags = new Set<string>()
@@ -159,3 +196,23 @@ export const makeSerialized = <
159
196
  }
160
197
  })
161
198
  }
199
+
200
+ /** @internal */
201
+ export const layerSerialized = <
202
+ I,
203
+ A extends Schema.TaggedRequest.Any,
204
+ const Handlers extends {
205
+ readonly [K in A["_tag"]]: Extract<A, { readonly _tag: K }> extends
206
+ Serializable.SerializableWithResult<infer _IS, infer S, infer _IE, infer E, infer _IO, infer O>
207
+ ? (_: S) => Stream.Stream<any, E, O> | Effect.Effect<any, E, O> :
208
+ never
209
+ }
210
+ >(
211
+ schema: Schema.Schema<I, A>,
212
+ handlers: Handlers
213
+ ): Layer.Layer<
214
+ | WorkerRunner.PlatformRunner
215
+ | (ReturnType<Handlers[keyof Handlers]> extends Stream.Stream<infer R, infer _E, infer _A> ? R : never),
216
+ WorkerError.WorkerError,
217
+ never
218
+ > => Layer.scopedDiscard(makeSerialized(schema, handlers))