@livestore/utils 0.0.54-dev.27 → 0.0.54-dev.28

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 (119) hide show
  1. package/dist/.tsbuildinfo.json +1 -1
  2. package/dist/Deferred.d.ts.map +1 -1
  3. package/dist/NoopTracer.d.ts.map +1 -1
  4. package/dist/effect/Effect.d.ts.map +1 -1
  5. package/dist/effect/Effect.js +9 -5
  6. package/dist/effect/Effect.js.map +1 -1
  7. package/dist/effect/Scheduler.d.ts +1 -1
  8. package/dist/effect/Scheduler.d.ts.map +1 -1
  9. package/dist/effect/Schema/debug-diff.d.ts +13 -0
  10. package/dist/effect/Schema/debug-diff.d.ts.map +1 -0
  11. package/dist/effect/Schema/debug-diff.js +51 -0
  12. package/dist/effect/Schema/debug-diff.js.map +1 -0
  13. package/dist/effect/Schema/debug-diff.test.d.ts +2 -0
  14. package/dist/effect/Schema/debug-diff.test.d.ts.map +1 -0
  15. package/dist/effect/Schema/debug-diff.test.js +91 -0
  16. package/dist/effect/Schema/debug-diff.test.js.map +1 -0
  17. package/dist/effect/{Schema.d.ts → Schema/index.d.ts} +2 -1
  18. package/dist/effect/Schema/index.d.ts.map +1 -0
  19. package/dist/effect/{Schema.js → Schema/index.js} +3 -2
  20. package/dist/effect/Schema/index.js.map +1 -0
  21. package/dist/effect/ServiceContext.d.ts.map +1 -1
  22. package/dist/effect/Stream.d.ts.map +1 -1
  23. package/dist/effect/SubscriptionRef.d.ts.map +1 -1
  24. package/dist/effect/WebLock.d.ts +1 -1
  25. package/dist/effect/WebLock.d.ts.map +1 -1
  26. package/dist/effect/WebLock.js +4 -8
  27. package/dist/effect/WebLock.js.map +1 -1
  28. package/dist/effect/browser-worker-tmp/BrowserWorker.d.ts +21 -0
  29. package/dist/effect/browser-worker-tmp/BrowserWorker.d.ts.map +1 -0
  30. package/dist/effect/browser-worker-tmp/BrowserWorker.js +17 -0
  31. package/dist/effect/browser-worker-tmp/BrowserWorker.js.map +1 -0
  32. package/dist/effect/browser-worker-tmp/BrowserWorkerRunner.d.ts +12 -0
  33. package/dist/effect/browser-worker-tmp/BrowserWorkerRunner.d.ts.map +1 -0
  34. package/dist/effect/browser-worker-tmp/BrowserWorkerRunner.js +8 -0
  35. package/dist/effect/browser-worker-tmp/BrowserWorkerRunner.js.map +1 -0
  36. package/dist/effect/browser-worker-tmp/internal/worker.d.ts +9 -0
  37. package/dist/effect/browser-worker-tmp/internal/worker.d.ts.map +1 -0
  38. package/dist/effect/browser-worker-tmp/internal/worker.js +56 -0
  39. package/dist/effect/browser-worker-tmp/internal/worker.js.map +1 -0
  40. package/dist/effect/browser-worker-tmp/internal/workerRunner.d.ts +5 -0
  41. package/dist/effect/browser-worker-tmp/internal/workerRunner.d.ts.map +1 -0
  42. package/dist/effect/browser-worker-tmp/internal/workerRunner.js +100 -0
  43. package/dist/effect/browser-worker-tmp/internal/workerRunner.js.map +1 -0
  44. package/dist/effect/browser-worker-tmp/port-platform-runner.d.ts +5 -0
  45. package/dist/effect/browser-worker-tmp/port-platform-runner.d.ts.map +1 -0
  46. package/dist/effect/{port-platform-runner.js → browser-worker-tmp/port-platform-runner.js} +3 -3
  47. package/dist/effect/browser-worker-tmp/port-platform-runner.js.map +1 -0
  48. package/dist/effect/index.d.ts +7 -4
  49. package/dist/effect/index.d.ts.map +1 -1
  50. package/dist/effect/index.js +9 -4
  51. package/dist/effect/index.js.map +1 -1
  52. package/dist/effect/platform-worker.d.ts +2 -0
  53. package/dist/effect/platform-worker.d.ts.map +1 -0
  54. package/dist/effect/platform-worker.js +2 -0
  55. package/dist/effect/platform-worker.js.map +1 -0
  56. package/dist/effect/worker-tmp/Worker.d.ts +290 -0
  57. package/dist/effect/worker-tmp/Worker.d.ts.map +1 -0
  58. package/dist/effect/worker-tmp/Worker.js +62 -0
  59. package/dist/effect/worker-tmp/Worker.js.map +1 -0
  60. package/dist/effect/worker-tmp/WorkerError.d.ts +63 -0
  61. package/dist/effect/worker-tmp/WorkerError.d.ts.map +1 -0
  62. package/dist/effect/worker-tmp/WorkerError.js +54 -0
  63. package/dist/effect/worker-tmp/WorkerError.js.map +1 -0
  64. package/dist/effect/worker-tmp/WorkerRunner.d.ts +119 -0
  65. package/dist/effect/worker-tmp/WorkerRunner.d.ts.map +1 -0
  66. package/dist/effect/worker-tmp/WorkerRunner.js +32 -0
  67. package/dist/effect/worker-tmp/WorkerRunner.js.map +1 -0
  68. package/dist/effect/worker-tmp/internal/worker.d.ts +36 -0
  69. package/dist/effect/worker-tmp/internal/worker.d.ts.map +1 -0
  70. package/dist/effect/worker-tmp/internal/worker.js +242 -0
  71. package/dist/effect/worker-tmp/internal/worker.js.map +1 -0
  72. package/dist/effect/worker-tmp/internal/workerError.d.ts +4 -0
  73. package/dist/effect/worker-tmp/internal/workerError.d.ts.map +1 -0
  74. package/dist/effect/worker-tmp/internal/workerError.js +3 -0
  75. package/dist/effect/worker-tmp/internal/workerError.js.map +1 -0
  76. package/dist/effect/worker-tmp/internal/workerRunner.d.ts +21 -0
  77. package/dist/effect/worker-tmp/internal/workerRunner.d.ts.map +1 -0
  78. package/dist/effect/worker-tmp/internal/workerRunner.js +136 -0
  79. package/dist/effect/worker-tmp/internal/workerRunner.js.map +1 -0
  80. package/dist/fast-deep-equal.d.ts +2 -0
  81. package/dist/fast-deep-equal.d.ts.map +1 -0
  82. package/dist/fast-deep-equal.js +79 -0
  83. package/dist/fast-deep-equal.js.map +1 -0
  84. package/dist/guards.d.ts.map +1 -1
  85. package/dist/index.d.ts +2 -1
  86. package/dist/index.d.ts.map +1 -1
  87. package/dist/index.js +13 -1
  88. package/dist/index.js.map +1 -1
  89. package/dist/object/index.d.ts.map +1 -1
  90. package/dist/object/omit.d.ts.map +1 -1
  91. package/dist/object/pick.d.ts +1 -1
  92. package/dist/object/pick.d.ts.map +1 -1
  93. package/dist/promise.d.ts.map +1 -1
  94. package/dist/set.d.ts.map +1 -1
  95. package/package.json +17 -16
  96. package/src/effect/Effect.ts +6 -3
  97. package/src/effect/Schema/debug-diff.test.ts +102 -0
  98. package/src/effect/Schema/debug-diff.ts +59 -0
  99. package/src/effect/{Schema.ts → Schema/index.ts} +2 -1
  100. package/src/effect/WebLock.ts +4 -10
  101. package/src/effect/browser-worker-tmp/BrowserWorker.ts +26 -0
  102. package/src/effect/browser-worker-tmp/BrowserWorkerRunner.ts +14 -0
  103. package/src/effect/browser-worker-tmp/internal/worker.ts +71 -0
  104. package/src/effect/browser-worker-tmp/internal/workerRunner.ts +119 -0
  105. package/src/effect/{port-platform-runner.ts → browser-worker-tmp/port-platform-runner.ts} +4 -3
  106. package/src/effect/index.ts +9 -4
  107. package/src/effect/worker-tmp/Worker.ts +374 -0
  108. package/src/effect/worker-tmp/WorkerError.ts +80 -0
  109. package/src/effect/worker-tmp/WorkerRunner.ts +181 -0
  110. package/src/effect/worker-tmp/internal/worker.ts +415 -0
  111. package/src/effect/worker-tmp/internal/workerError.ts +6 -0
  112. package/src/effect/worker-tmp/internal/workerRunner.ts +236 -0
  113. package/src/fast-deep-equal.ts +72 -0
  114. package/src/index.ts +12 -1
  115. package/dist/effect/Schema.d.ts.map +0 -1
  116. package/dist/effect/Schema.js.map +0 -1
  117. package/dist/effect/port-platform-runner.d.ts +0 -5
  118. package/dist/effect/port-platform-runner.d.ts.map +0 -1
  119. package/dist/effect/port-platform-runner.js.map +0 -1
@@ -0,0 +1,415 @@
1
+ /* eslint-disable prefer-arrow/prefer-arrow-functions */
2
+ import { Transferable } from '@effect/platform'
3
+ import * as Schema from '@effect/schema/Schema'
4
+ import * as Serializable from '@effect/schema/Serializable'
5
+ import * as Arr from 'effect/Array'
6
+ import * as Cause from 'effect/Cause'
7
+ import * as Channel from 'effect/Channel'
8
+ import * as Chunk from 'effect/Chunk'
9
+ import * as Context from 'effect/Context'
10
+ import * as Deferred from 'effect/Deferred'
11
+ import * as Effect from 'effect/Effect'
12
+ import * as Exit from 'effect/Exit'
13
+ import * as Fiber from 'effect/Fiber'
14
+ import { identity, pipe } from 'effect/Function'
15
+ import * as Layer from 'effect/Layer'
16
+ import * as Option from 'effect/Option'
17
+ import * as Pool from 'effect/Pool'
18
+ import * as Queue from 'effect/Queue'
19
+ import * as Schedule from 'effect/Schedule'
20
+ import type * as Scope from 'effect/Scope'
21
+ import * as Stream from 'effect/Stream'
22
+ import * as Tracer from 'effect/Tracer'
23
+
24
+ import type * as Worker from '../Worker.js'
25
+ import { WorkerError } from '../WorkerError.js'
26
+
27
+ /** @internal */
28
+ export const defaultQueue = <I>() =>
29
+ Effect.map(
30
+ Queue.unbounded<readonly [id: number, item: I, span: Option.Option<Tracer.Span>]>(),
31
+ (queue): Worker.WorkerQueue<I> => ({
32
+ offer: (id, item, span) => Queue.offer(queue, [id, item, span]),
33
+ take: Queue.take(queue),
34
+ shutdown: Queue.shutdown(queue),
35
+ }),
36
+ )
37
+
38
+ /** @internal */
39
+ export const PlatformWorkerTypeId: Worker.PlatformWorkerTypeId = Symbol.for(
40
+ '@effect/platform/Worker/PlatformWorker',
41
+ ) as Worker.PlatformWorkerTypeId
42
+
43
+ /** @internal */
44
+ export const PlatformWorker = Context.GenericTag<Worker.PlatformWorker>('@effect/platform/Worker/PlatformWorker')
45
+
46
+ /** @internal */
47
+ export const WorkerManagerTypeId: Worker.WorkerManagerTypeId = Symbol.for(
48
+ '@effect/platform/Worker/WorkerManager',
49
+ ) as Worker.WorkerManagerTypeId
50
+
51
+ /** @internal */
52
+ export const WorkerManager = Context.GenericTag<Worker.WorkerManager>('@effect/platform/Worker/WorkerManager')
53
+
54
+ /** @internal */
55
+ export const Spawner = Context.GenericTag<Worker.Spawner, Worker.SpawnerFn>('@effect/platform/Worker/Spawner')
56
+
57
+ /** @internal */
58
+ export const makeManager = Effect.gen(function* () {
59
+ const platform = yield* PlatformWorker
60
+ let idCounter = 0
61
+ return WorkerManager.of({
62
+ [WorkerManagerTypeId]: WorkerManagerTypeId,
63
+ spawn<I, O, E>({ encode, initialMessage, queue, transfers = (_) => [] }: Worker.Worker.Options<I>) {
64
+ return Effect.gen(function* (_) {
65
+ const spawn = yield* _(Spawner)
66
+ const id = idCounter++
67
+ let requestIdCounter = 0
68
+ const requestMap = new Map<
69
+ number,
70
+ readonly [Queue.Queue<Exit.Exit<ReadonlyArray<O>, E | WorkerError>>, Deferred.Deferred<void>]
71
+ >()
72
+ const sendQueue = yield* Effect.acquireRelease(
73
+ Queue.unbounded<readonly [message: Worker.Worker.Request, transfers?: ReadonlyArray<unknown>]>(),
74
+ Queue.shutdown,
75
+ )
76
+
77
+ const collector = Transferable.unsafeMakeCollector()
78
+ const wrappedEncode = encode
79
+ ? (message: I) =>
80
+ Effect.zipRight(
81
+ collector.clear,
82
+ Effect.provideService(encode(message), Transferable.Collector, collector),
83
+ )
84
+ : Effect.succeed
85
+
86
+ const outbound = queue ?? (yield* defaultQueue<I>())
87
+ yield* Effect.addFinalizer(() => outbound.shutdown)
88
+
89
+ yield* Effect.gen(function* () {
90
+ const readyLatch = yield* Deferred.make<void>()
91
+ const backing = yield* platform.spawn<Worker.Worker.Request, Worker.Worker.Response<E, O>>(spawn(id))
92
+ const send = pipe(
93
+ sendQueue.take,
94
+ Effect.flatMap(([message, transfers]) => backing.send(message, transfers)),
95
+ Effect.forever,
96
+ )
97
+ const take = pipe(
98
+ Queue.take(backing.queue),
99
+ Effect.flatMap((msg) => {
100
+ if (msg[0] === 0) {
101
+ return Deferred.complete(readyLatch, Effect.void)
102
+ }
103
+ return handleMessage(msg[1])
104
+ }),
105
+ Effect.forever,
106
+ )
107
+ return yield* Effect.all(
108
+ [Fiber.join(backing.fiber), Effect.zipRight(Deferred.await(readyLatch), send), take],
109
+ { concurrency: 'unbounded' },
110
+ )
111
+ }).pipe(
112
+ Effect.scoped,
113
+ Effect.onError((cause) =>
114
+ Effect.forEach(requestMap.values(), ([queue]) => Queue.offer(queue, Exit.failCause(cause))),
115
+ ),
116
+ Effect.retry(Schedule.spaced(1000)),
117
+ Effect.annotateLogs({
118
+ package: '@effect/platform',
119
+ module: 'Worker',
120
+ }),
121
+ Effect.interruptible,
122
+ Effect.forkScoped,
123
+ )
124
+
125
+ yield* Effect.addFinalizer(() =>
126
+ Effect.zipRight(
127
+ Effect.forEach(requestMap.values(), ([queue]) => Queue.offer(queue, Exit.failCause(Cause.empty)), {
128
+ discard: true,
129
+ }),
130
+ Effect.sync(() => requestMap.clear()),
131
+ ),
132
+ )
133
+
134
+ const handleMessage = (response: Worker.Worker.Response<E, O>) =>
135
+ Effect.suspend(() => {
136
+ const queue = requestMap.get(response[0])
137
+ if (!queue) return Effect.void
138
+
139
+ switch (response[1]) {
140
+ // data
141
+ case 0: {
142
+ return Queue.offer(queue[0], Exit.succeed(response[2]))
143
+ }
144
+ // end
145
+ case 1: {
146
+ return response.length === 2
147
+ ? Queue.offer(queue[0], Exit.failCause(Cause.empty))
148
+ : Effect.zipRight(
149
+ Queue.offer(queue[0], Exit.succeed(response[2])),
150
+ Queue.offer(queue[0], Exit.failCause(Cause.empty)),
151
+ )
152
+ }
153
+ // error / defect
154
+ case 2:
155
+ case 3: {
156
+ return Queue.offer(
157
+ queue[0],
158
+ response[1] === 2 ? Exit.fail(response[2]) : Exit.failCause(WorkerError.decodeCause(response[2])),
159
+ )
160
+ }
161
+ }
162
+ })
163
+
164
+ const executeAcquire = (request: I) =>
165
+ Effect.tap(
166
+ Effect.all([
167
+ Effect.sync(() => requestIdCounter++),
168
+ Queue.unbounded<Exit.Exit<ReadonlyArray<O>, E | WorkerError>>(),
169
+ Deferred.make<void>(),
170
+ Effect.map(
171
+ Effect.serviceOption(Tracer.ParentSpan),
172
+ Option.filter((span): span is Tracer.Span => span._tag === 'Span'),
173
+ ),
174
+ ]),
175
+ ([id, queue, deferred, span]) =>
176
+ Effect.suspend(() => {
177
+ requestMap.set(id, [queue, deferred])
178
+ return outbound.offer(id, request, span)
179
+ }),
180
+ )
181
+
182
+ const executeRelease = (
183
+ [id, , deferred]: [
184
+ number,
185
+ Queue.Queue<Exit.Exit<ReadonlyArray<O>, E | WorkerError>>,
186
+ Deferred.Deferred<void>,
187
+ Option.Option<Tracer.Span>,
188
+ ],
189
+ exit: Exit.Exit<unknown, unknown>,
190
+ ) => {
191
+ const release = Effect.zipRight(
192
+ Deferred.complete(deferred, Effect.void),
193
+ Effect.sync(() => requestMap.delete(id)),
194
+ )
195
+ return Exit.isFailure(exit) ? Effect.zipRight(sendQueue.offer([[id, 1]]), release) : release
196
+ }
197
+
198
+ const execute = (request: I) =>
199
+ Stream.flatMap(Stream.acquireRelease(executeAcquire(request), executeRelease), ([, queue]) => {
200
+ const loop: Channel.Channel<
201
+ Chunk.Chunk<O>,
202
+ unknown,
203
+ E | WorkerError,
204
+ unknown,
205
+ void,
206
+ unknown
207
+ > = Channel.flatMap(
208
+ Queue.take(queue),
209
+ Exit.match({
210
+ onFailure: (cause) => (Cause.isEmpty(cause) ? Channel.void : Channel.failCause(cause)),
211
+ onSuccess: (value) => Channel.flatMap(Channel.write(Chunk.unsafeFromArray(value)), () => loop),
212
+ }),
213
+ )
214
+ return Stream.fromChannel(loop)
215
+ })
216
+
217
+ const executeEffect = (request: I) =>
218
+ Effect.acquireUseRelease(
219
+ executeAcquire(request),
220
+ ([, queue]) => Effect.flatMap(Queue.take(queue), Exit.map(Arr.unsafeGet(0))),
221
+ executeRelease,
222
+ )
223
+
224
+ yield* outbound.take.pipe(
225
+ Effect.flatMap(([id, request, span]) =>
226
+ Effect.fork(
227
+ Effect.suspend(() => {
228
+ const result = requestMap.get(id)
229
+ if (!result) return Effect.void
230
+ const transferables = transfers(request)
231
+ const spanTuple = Option.getOrUndefined(
232
+ Option.map(span, (span) => [span.traceId, span.spanId, span.sampled] as const),
233
+ )
234
+ return pipe(
235
+ Effect.flatMap(wrappedEncode(request), (payload) =>
236
+ sendQueue.offer([
237
+ [id, 0, payload, spanTuple],
238
+ [...transferables, ...collector.unsafeRead()],
239
+ ]),
240
+ ),
241
+ Effect.catchAllCause((cause) => Queue.offer(result[0], Exit.failCause(cause))),
242
+ Effect.zipRight(Deferred.await(result[1])),
243
+ )
244
+ }),
245
+ ),
246
+ ),
247
+ Effect.forever,
248
+ Effect.forkScoped,
249
+ Effect.interruptible,
250
+ )
251
+
252
+ if (initialMessage) {
253
+ yield* Effect.sync(initialMessage).pipe(
254
+ Effect.flatMap(executeEffect),
255
+ Effect.mapError((error) => new WorkerError({ reason: 'spawn', error })),
256
+ )
257
+ }
258
+
259
+ return { id, execute, executeEffect }
260
+ }).pipe(Effect.parallelFinalizers)
261
+ },
262
+ })
263
+ })
264
+
265
+ /** @internal */
266
+ export const layerManager = Layer.effect(WorkerManager, makeManager)
267
+
268
+ /** @internal */
269
+ export const makePool = <I, O, E>(options: Worker.WorkerPool.Options<I>) =>
270
+ Effect.gen(function* () {
271
+ const manager = yield* WorkerManager
272
+ const workers = new Set<Worker.Worker<I, O, E>>()
273
+ const acquire = pipe(
274
+ manager.spawn<I, O, E>(options),
275
+ Effect.tap((worker) => Effect.sync(() => workers.add(worker))),
276
+ Effect.tap((worker) => Effect.addFinalizer(() => Effect.sync(() => workers.delete(worker)))),
277
+ options.onCreate ? Effect.tap(options.onCreate) : identity,
278
+ )
279
+ const backing =
280
+ 'minSize' in options
281
+ ? yield* Pool.makeWithTTL({
282
+ acquire,
283
+ min: options.minSize,
284
+ max: options.maxSize,
285
+ concurrency: options.concurrency,
286
+ targetUtilization: options.targetUtilization,
287
+ timeToLive: options.timeToLive,
288
+ })
289
+ : yield* Pool.make({
290
+ acquire,
291
+ size: options.size,
292
+ concurrency: options.concurrency,
293
+ targetUtilization: options.targetUtilization,
294
+ })
295
+ const pool: Worker.WorkerPool<I, O, E> = {
296
+ backing,
297
+ broadcast: (message: I) =>
298
+ Effect.forEach(workers, (worker) => worker.executeEffect(message), {
299
+ concurrency: 'unbounded',
300
+ discard: true,
301
+ }),
302
+ execute: (message: I) => Stream.unwrapScoped(Effect.map(backing.get, (worker) => worker.execute(message))),
303
+ executeEffect: (message: I) =>
304
+ Effect.scoped(Effect.flatMap(backing.get, (worker) => worker.executeEffect(message))),
305
+ }
306
+
307
+ // report any spawn errors
308
+ yield* Effect.scoped(backing.get)
309
+
310
+ return pool
311
+ })
312
+
313
+ /** @internal */
314
+ export const makePoolLayer = <Tag, I, O, E>(
315
+ tag: Context.Tag<Tag, Worker.WorkerPool<I, O, E>>,
316
+ options: Worker.WorkerPool.Options<I>,
317
+ ) => Layer.scoped(tag, makePool(options))
318
+
319
+ /** @internal */
320
+ export const makeSerialized = <I extends Schema.TaggedRequest.Any>(
321
+ options: Worker.SerializedWorker.Options<I>,
322
+ ): Effect.Effect<Worker.SerializedWorker<I>, WorkerError, Worker.WorkerManager | Worker.Spawner | Scope.Scope> =>
323
+ Effect.gen(function* () {
324
+ const manager = yield* WorkerManager
325
+ const backing = yield* manager.spawn({
326
+ ...(options as any),
327
+ encode(message) {
328
+ return Effect.mapError(
329
+ Serializable.serialize(message as any),
330
+ (error) => new WorkerError({ reason: 'encode', error }),
331
+ )
332
+ },
333
+ })
334
+ const execute = <Req extends I>(message: Req) => {
335
+ const parseSuccess = Schema.decode(Serializable.successSchema(message as any))
336
+ const parseFailure = Schema.decode(Serializable.failureSchema(message as any))
337
+ return pipe(
338
+ backing.execute(message),
339
+ Stream.catchAll((error) => Effect.flatMap(parseFailure(error), Effect.fail)),
340
+ Stream.mapEffect(parseSuccess),
341
+ )
342
+ }
343
+ const executeEffect = <Req extends I>(message: Req) => {
344
+ const parseSuccess = Schema.decode(Serializable.successSchema(message as any))
345
+ const parseFailure = Schema.decode(Serializable.failureSchema(message as any))
346
+ return Effect.matchEffect(backing.executeEffect(message), {
347
+ onFailure: (error) => Effect.flatMap(parseFailure(error), Effect.fail),
348
+ onSuccess: parseSuccess,
349
+ })
350
+ }
351
+ return identity<Worker.SerializedWorker<I>>({
352
+ id: backing.id,
353
+ execute: execute as any,
354
+ executeEffect: executeEffect as any,
355
+ })
356
+ })
357
+
358
+ /** @internal */
359
+ export const makePoolSerialized = <I extends Schema.TaggedRequest.Any>(
360
+ options: Worker.SerializedWorkerPool.Options<I>,
361
+ ) =>
362
+ Effect.gen(function* () {
363
+ const manager = yield* WorkerManager
364
+ const workers = new Set<Worker.SerializedWorker<I>>()
365
+ const acquire = pipe(
366
+ makeSerialized<I>(options),
367
+ Effect.tap((worker) => Effect.sync(() => workers.add(worker))),
368
+ Effect.tap((worker) => Effect.addFinalizer(() => Effect.sync(() => workers.delete(worker)))),
369
+ options.onCreate
370
+ ? Effect.tap(options.onCreate as (worker: Worker.SerializedWorker<I>) => Effect.Effect<void, WorkerError>)
371
+ : identity,
372
+ Effect.provideService(WorkerManager, manager),
373
+ )
374
+ const backing = yield* 'timeToLive' in options
375
+ ? Pool.makeWithTTL({
376
+ acquire,
377
+ min: options.minSize,
378
+ max: options.maxSize,
379
+ concurrency: options.concurrency,
380
+ targetUtilization: options.targetUtilization,
381
+ timeToLive: options.timeToLive,
382
+ })
383
+ : Pool.make({
384
+ acquire,
385
+ size: options.size,
386
+ concurrency: options.concurrency,
387
+ targetUtilization: options.targetUtilization,
388
+ })
389
+ const pool: Worker.SerializedWorkerPool<I> = {
390
+ backing,
391
+ broadcast: <Req extends I>(message: Req) =>
392
+ Effect.forEach(workers, (worker) => worker.executeEffect(message), {
393
+ concurrency: 'unbounded',
394
+ discard: true,
395
+ }) as any,
396
+ execute: <Req extends I>(message: Req) =>
397
+ Stream.unwrapScoped(Effect.map(backing.get, (worker) => worker.execute(message))) as any,
398
+ executeEffect: <Req extends I>(message: Req) =>
399
+ Effect.scoped(Effect.flatMap(backing.get, (worker) => worker.executeEffect(message))) as any,
400
+ }
401
+
402
+ // report any spawn errors
403
+ yield* Effect.scoped(backing.get)
404
+
405
+ return pool
406
+ })
407
+
408
+ /** @internal */
409
+ export const makePoolSerializedLayer = <Tag, I extends Schema.TaggedRequest.Any>(
410
+ tag: Context.Tag<Tag, Worker.SerializedWorkerPool<I>>,
411
+ options: Worker.SerializedWorkerPool.Options<I>,
412
+ ) => Layer.scoped(tag, makePoolSerialized(options))
413
+
414
+ /** @internal */
415
+ export const layerSpawner = <W = unknown>(spawner: Worker.SpawnerFn<W>) => Layer.succeed(Spawner, spawner)
@@ -0,0 +1,6 @@
1
+ import type * as WorkerError_ from "../WorkerError.js"
2
+
3
+ /** @internal */
4
+ export const WorkerErrorTypeId: WorkerError_.WorkerErrorTypeId = Symbol.for(
5
+ "@effect/platform/WorkerError"
6
+ ) as WorkerError_.WorkerErrorTypeId
@@ -0,0 +1,236 @@
1
+ import { Transferable } from '@effect/platform'
2
+ import * as Schema from '@effect/schema/Schema'
3
+ import * as Serializable from '@effect/schema/Serializable'
4
+ import * as Cause from 'effect/Cause'
5
+ import * as Chunk from 'effect/Chunk'
6
+ import * as Context from 'effect/Context'
7
+ import * as Effect from 'effect/Effect'
8
+ import * as Either from 'effect/Either'
9
+ import * as ExecutionStrategy from 'effect/ExecutionStrategy'
10
+ import * as Exit from 'effect/Exit'
11
+ import * as Fiber from 'effect/Fiber'
12
+ import { identity, pipe } from 'effect/Function'
13
+ import * as Layer from 'effect/Layer'
14
+ import * as Option from 'effect/Option'
15
+ import * as Queue from 'effect/Queue'
16
+ import * as Scope from 'effect/Scope'
17
+ import * as Stream from 'effect/Stream'
18
+
19
+ import type * as Worker from '../Worker.js'
20
+ import { isWorkerError, WorkerError } from '../WorkerError.js'
21
+ import type * as WorkerRunner from '../WorkerRunner.js'
22
+
23
+ /** @internal */
24
+ export const PlatformRunnerTypeId: WorkerRunner.PlatformRunnerTypeId = Symbol.for(
25
+ '@effect/platform/Runner/PlatformRunner',
26
+ ) as WorkerRunner.PlatformRunnerTypeId
27
+
28
+ /** @internal */
29
+ export const PlatformRunner = Context.GenericTag<WorkerRunner.PlatformRunner>('@effect/platform/Runner/PlatformRunner')
30
+
31
+ /** @internal */
32
+ export const make = <I, E, R, O>(
33
+ process: (request: I) => Stream.Stream<O, E, R> | Effect.Effect<O, E, R>,
34
+ options?: WorkerRunner.Runner.Options<I, O, E>,
35
+ ) =>
36
+ Effect.gen(function* (_) {
37
+ const scope = yield* _(Scope.fork(yield* _(Effect.scope), ExecutionStrategy.parallel))
38
+ const fiber = Option.getOrThrow(Fiber.getCurrentFiber())
39
+ const shutdown = Effect.zipRight(Scope.close(scope, Exit.void), Fiber.interruptFork(fiber))
40
+ const platform = yield* _(PlatformRunner)
41
+ const backing = yield* _(
42
+ platform.start<Worker.Worker.Request<I>, Worker.Worker.Response<E>>(shutdown),
43
+ Scope.extend(scope),
44
+ )
45
+ const fiberMap = new Map<number, Fiber.Fiber<void, unknown>>()
46
+
47
+ yield* _(
48
+ Queue.take(backing.queue),
49
+ options?.decode
50
+ ? Effect.flatMap((msg): Effect.Effect<readonly [portId: number, Worker.Worker.Request<I>], WorkerError> => {
51
+ const req = msg[1]
52
+ if (req[1] === 1) {
53
+ return Effect.succeed(msg)
54
+ }
55
+
56
+ return Effect.map(options.decode!(req[2]), (data) => [msg[0], [req[0], req[1], data, req[3]]])
57
+ })
58
+ : identity,
59
+ Effect.tap(([portId, req]) => {
60
+ const id = req[0]
61
+ if (req[1] === 1) {
62
+ const fiber = fiberMap.get(id)
63
+ if (!fiber) return Effect.void
64
+ return Fiber.interrupt(fiber)
65
+ }
66
+
67
+ const collector = Transferable.unsafeMakeCollector()
68
+ return pipe(
69
+ Effect.sync(() => process(req[2])),
70
+ Effect.flatMap((stream) => {
71
+ let effect = Effect.isEffect(stream)
72
+ ? Effect.flatMap(stream, (data) => {
73
+ const transfers = options?.transfers ? options.transfers(data) : []
74
+ return pipe(
75
+ options?.encodeOutput
76
+ ? Effect.provideService(options.encodeOutput(req[2], data), Transferable.Collector, collector)
77
+ : Effect.succeed(data),
78
+ Effect.flatMap((payload) =>
79
+ backing.send(portId, [id, 0, [payload]], [...transfers, ...collector.unsafeRead()]),
80
+ ),
81
+ )
82
+ })
83
+ : pipe(
84
+ stream,
85
+ Stream.chunks,
86
+ Stream.tap((data) => {
87
+ if (options?.encodeOutput === undefined) {
88
+ const payload = Chunk.toReadonlyArray(data)
89
+ const transfers = options?.transfers ? payload.flatMap(options.transfers) : undefined
90
+ return backing.send(portId, [id, 0, payload], transfers)
91
+ }
92
+
93
+ const transfers: unknown[] = []
94
+ collector.unsafeClear()
95
+ return pipe(
96
+ Effect.forEach(data, (data) => {
97
+ if (options?.transfers) {
98
+ for (const option of options.transfers(data)) {
99
+ transfers.push(option)
100
+ }
101
+ }
102
+ return Effect.orDie(options.encodeOutput!(req[2], data))
103
+ }),
104
+ Effect.provideService(Transferable.Collector, collector),
105
+ Effect.flatMap((payload) => {
106
+ collector.unsafeRead().forEach((transfer) => transfers.push(transfer))
107
+ return backing.send(portId, [id, 0, payload], transfers)
108
+ }),
109
+ )
110
+ }),
111
+ Stream.runDrain,
112
+ Effect.andThen(backing.send(portId, [id, 1])),
113
+ )
114
+
115
+ if (req[3]) {
116
+ const [traceId, spanId, sampled] = req[3]
117
+ effect = Effect.withParentSpan(effect, {
118
+ _tag: 'ExternalSpan',
119
+ traceId,
120
+ spanId,
121
+ sampled,
122
+ context: Context.empty(),
123
+ })
124
+ }
125
+
126
+ return effect
127
+ }),
128
+ Effect.catchIf(isWorkerError, (error) =>
129
+ backing.send(portId, [id, 3, WorkerError.encodeCause(Cause.fail(error))]),
130
+ ),
131
+ Effect.onExit((exit) => {
132
+ if (exit._tag === 'Success') {
133
+ return Effect.void
134
+ }
135
+ return Either.match(Cause.failureOrCause(exit.cause), {
136
+ onLeft: (error) => {
137
+ const transfers = options?.transfers ? options.transfers(error) : []
138
+ collector.unsafeClear()
139
+ return pipe(
140
+ options?.encodeError
141
+ ? Effect.provideService(options.encodeError(req[2], error), Transferable.Collector, collector)
142
+ : Effect.succeed(error),
143
+ Effect.flatMap((payload) =>
144
+ backing.send(portId, [id, 2, payload as any], [...transfers, ...collector.unsafeRead()]),
145
+ ),
146
+ Effect.catchAllCause((cause) => backing.send(portId, [id, 3, WorkerError.encodeCause(cause)])),
147
+ )
148
+ },
149
+ onRight: (cause) => backing.send(portId, [id, 3, WorkerError.encodeCause(cause)]),
150
+ })
151
+ }),
152
+ Effect.ensuring(Effect.sync(() => fiberMap.delete(id))),
153
+ Effect.fork,
154
+ Effect.tap((fiber) => Effect.sync(() => fiberMap.set(id, fiber))),
155
+ )
156
+ }),
157
+ Effect.forever,
158
+ Effect.forkIn(scope),
159
+ )
160
+ })
161
+
162
+ /** @internal */
163
+ export const layer = <I, E, R, O>(
164
+ process: (request: I) => Stream.Stream<O, E, R> | Effect.Effect<O, E, R>,
165
+ options?: WorkerRunner.Runner.Options<I, O, E>,
166
+ ): Layer.Layer<never, WorkerError, WorkerRunner.PlatformRunner | R> => Layer.scopedDiscard(make(process, options))
167
+
168
+ /** @internal */
169
+ export const makeSerialized = <
170
+ R,
171
+ I,
172
+ A extends Schema.TaggedRequest.Any,
173
+ const Handlers extends WorkerRunner.SerializedRunner.Handlers<A>,
174
+ >(
175
+ schema: Schema.Schema<A, I, R>,
176
+ handlers: Handlers,
177
+ ): Effect.Effect<
178
+ void,
179
+ WorkerError,
180
+ R | WorkerRunner.PlatformRunner | Scope.Scope | WorkerRunner.SerializedRunner.HandlersContext<Handlers>
181
+ > =>
182
+ Effect.gen(function* (_) {
183
+ const scope = yield* _(Effect.scope)
184
+ let context = Context.empty() as Context.Context<any>
185
+ const parseRequest = Schema.decodeUnknown(schema) as (_: unknown) => Effect.Effect<A>
186
+
187
+ return yield* _(
188
+ make(
189
+ (request: A) => {
190
+ const result = (handlers as any)[request._tag](request)
191
+ if (Layer.isLayer(result)) {
192
+ return Effect.flatMap(Layer.buildWithScope(result, scope), (_) =>
193
+ Effect.sync(() => {
194
+ context = Context.merge(context, _)
195
+ }),
196
+ )
197
+ } else if (Effect.isEffect(result)) {
198
+ return Effect.provide(result, context)
199
+ }
200
+ return Stream.provideContext(result as any, context)
201
+ },
202
+ {
203
+ decode(message) {
204
+ return Effect.mapError(parseRequest(message), (error) => new WorkerError({ reason: 'decode', error }))
205
+ },
206
+ encodeError(request, message) {
207
+ return Effect.mapError(
208
+ Serializable.serializeFailure(request as any, message),
209
+ (error) => new WorkerError({ reason: 'encode', error }),
210
+ )
211
+ },
212
+ encodeOutput(request, message) {
213
+ return Effect.catchAllCause(
214
+ Serializable.serializeSuccess(request as any, message),
215
+ (error) => new WorkerError({ reason: 'encode', error }),
216
+ )
217
+ },
218
+ },
219
+ ),
220
+ )
221
+ }) as any
222
+
223
+ /** @internal */
224
+ export const layerSerialized = <
225
+ R,
226
+ I,
227
+ A extends Schema.TaggedRequest.Any,
228
+ const Handlers extends WorkerRunner.SerializedRunner.Handlers<A>,
229
+ >(
230
+ schema: Schema.Schema<A, I, R>,
231
+ handlers: Handlers,
232
+ ): Layer.Layer<
233
+ never,
234
+ WorkerError,
235
+ R | WorkerRunner.PlatformRunner | WorkerRunner.SerializedRunner.HandlersContext<Handlers>
236
+ > => Layer.scopedDiscard(makeSerialized(schema, handlers))