@effect/platform 0.69.14 → 0.69.16

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/src/Socket.ts CHANGED
@@ -9,14 +9,15 @@ import type { DurationInput } from "effect/Duration"
9
9
  import * as Effect from "effect/Effect"
10
10
  import * as ExecutionStrategy from "effect/ExecutionStrategy"
11
11
  import * as Exit from "effect/Exit"
12
+ import * as Fiber from "effect/Fiber"
12
13
  import * as FiberRef from "effect/FiberRef"
13
14
  import * as FiberSet from "effect/FiberSet"
14
15
  import { dual } from "effect/Function"
15
16
  import { globalValue } from "effect/GlobalValue"
16
17
  import * as Layer from "effect/Layer"
17
18
  import * as Mailbox from "effect/Mailbox"
19
+ import * as Option from "effect/Option"
18
20
  import * as Predicate from "effect/Predicate"
19
- import * as Queue from "effect/Queue"
20
21
  import * as Scope from "effect/Scope"
21
22
  import type * as AsyncProducer from "effect/SingleProducerAsyncInput"
22
23
  import { TypeIdError } from "./Error.js"
@@ -394,126 +395,133 @@ export const fromWebSocket = <RO>(
394
395
  readonly openTimeout?: DurationInput
395
396
  }
396
397
  ): Effect.Effect<Socket, never, Exclude<RO, Scope.Scope>> =>
397
- Effect.withFiberRuntime<Socket, never, Exclude<RO, Scope.Scope>>((fiber) =>
398
- Effect.gen(function*() {
399
- const sendQueue = yield* Queue.dropping<Uint8Array | string | CloseEvent>(
400
- fiber.getFiberRef(currentSendQueueCapacity)
401
- )
402
- const acquireContext = fiber.currentContext as Context.Context<RO>
403
- const closeCodeIsError = options?.closeCodeIsError ?? defaultCloseCodeIsError
404
-
405
- const runRaw = <_, E, R>(handler: (_: string | Uint8Array) => Effect.Effect<_, E, R> | void) =>
406
- Effect.gen(function*() {
407
- const fiberSet = yield* FiberSet.make<any, E | SocketError>()
408
- const ws = yield* acquire
409
- const run = yield* Effect.provideService(FiberSet.runtime(fiberSet)<R>(), WebSocket, ws)
410
- let open = false
411
-
412
- function onMessage(event: MessageEvent) {
413
- if (event.data instanceof Blob) {
414
- return Effect.promise(() => event.data.arrayBuffer() as Promise<ArrayBuffer>).pipe(
415
- Effect.andThen((buffer) => handler(new Uint8Array(buffer))),
416
- run
417
- )
418
- }
419
- const result = handler(event.data)
420
- if (Effect.isEffect(result)) {
421
- run(result)
422
- }
423
- }
424
- function onError(cause: Event) {
425
- ws.removeEventListener("message", onMessage)
426
- ws.removeEventListener("close", onClose)
427
- Deferred.unsafeDone(
428
- fiberSet.deferred,
429
- Effect.fail(new SocketGenericError({ reason: open ? "Read" : "Open", cause }))
430
- )
431
- }
432
- function onClose(event: globalThis.CloseEvent) {
433
- ws.removeEventListener("message", onMessage)
434
- ws.removeEventListener("error", onError)
435
- Deferred.unsafeDone(
436
- fiberSet.deferred,
437
- Effect.fail(
438
- new SocketCloseError({
439
- reason: "Close",
440
- code: event.code,
441
- closeReason: event.reason
442
- })
443
- )
398
+ Effect.gen(function*() {
399
+ const fiber = Option.getOrThrow(Fiber.getCurrentFiber())
400
+ const sendQueue = yield* Mailbox.make<Uint8Array | string, SocketError>({
401
+ capacity: fiber.getFiberRef(currentSendQueueCapacity),
402
+ strategy: "dropping"
403
+ })
404
+ const acquireContext = fiber.currentContext as Context.Context<RO>
405
+ const closeCodeIsError = options?.closeCodeIsError ?? defaultCloseCodeIsError
406
+
407
+ const runRaw = <_, E, R>(handler: (_: string | Uint8Array) => Effect.Effect<_, E, R> | void) =>
408
+ Effect.gen(function*() {
409
+ const fiberSet = yield* FiberSet.make<any, E | SocketError>()
410
+ const ws = yield* acquire
411
+ const run = yield* Effect.provideService(FiberSet.runtime(fiberSet)<R>(), WebSocket, ws)
412
+ let open = false
413
+
414
+ function onMessage(event: MessageEvent) {
415
+ if (event.data instanceof Blob) {
416
+ return Effect.promise(() => event.data.arrayBuffer() as Promise<ArrayBuffer>).pipe(
417
+ Effect.andThen((buffer) => handler(new Uint8Array(buffer))),
418
+ run
444
419
  )
445
420
  }
446
-
447
- ws.addEventListener("close", onClose, { once: true })
448
- ws.addEventListener("error", onError, { once: true })
449
- ws.addEventListener("message", onMessage)
450
-
451
- if (ws.readyState !== 1) {
452
- const openDeferred = Deferred.unsafeMake<void>(fiber.id())
453
- ws.addEventListener("open", () => {
454
- open = true
455
- Deferred.unsafeDone(openDeferred, Effect.void)
456
- }, { once: true })
457
- yield* Deferred.await(openDeferred).pipe(
458
- Effect.timeoutFail({
459
- duration: options?.openTimeout ?? 10000,
460
- onTimeout: () =>
461
- new SocketGenericError({ reason: "OpenTimeout", cause: "timeout waiting for \"open\"" })
462
- }),
463
- Effect.raceFirst(FiberSet.join(fiberSet))
464
- )
421
+ const result = handler(event.data)
422
+ if (Effect.isEffect(result)) {
423
+ run(result)
465
424
  }
466
- open = true
467
- yield* Queue.take(sendQueue).pipe(
468
- Effect.tap((chunk) =>
469
- isCloseEvent(chunk) ?
470
- Effect.failSync(() => {
471
- ws.close(chunk.code, chunk.reason)
472
- return new SocketCloseError({
473
- reason: "Close",
474
- code: chunk.code,
475
- closeReason: chunk.reason
476
- })
477
- }) :
478
- Effect.try({
479
- try: () => ws.send(chunk),
480
- catch: (cause) => new SocketGenericError({ reason: "Write", cause })
481
- })
482
- ),
483
- Effect.forever,
484
- FiberSet.run(fiberSet)
425
+ }
426
+ function onError(cause: Event) {
427
+ ws.removeEventListener("message", onMessage)
428
+ ws.removeEventListener("close", onClose)
429
+ Deferred.unsafeDone(
430
+ fiberSet.deferred,
431
+ Effect.fail(new SocketGenericError({ reason: open ? "Read" : "Open", cause }))
485
432
  )
486
- return yield* FiberSet.join(fiberSet).pipe(
487
- Effect.catchIf(
488
- SocketCloseError.isClean((_) => !closeCodeIsError(_)),
489
- (_) => Effect.void
433
+ }
434
+ function onClose(event: globalThis.CloseEvent) {
435
+ ws.removeEventListener("message", onMessage)
436
+ ws.removeEventListener("error", onError)
437
+ Deferred.unsafeDone(
438
+ fiberSet.deferred,
439
+ Effect.fail(
440
+ new SocketCloseError({
441
+ reason: "Close",
442
+ code: event.code,
443
+ closeReason: event.reason
444
+ })
490
445
  )
491
446
  )
492
- }).pipe(
493
- Effect.mapInputContext((input: Context.Context<R | Scope.Scope>) => Context.merge(acquireContext, input)),
494
- Effect.scoped,
495
- Effect.interruptible
447
+ }
448
+
449
+ ws.addEventListener("close", onClose, { once: true })
450
+ ws.addEventListener("error", onError, { once: true })
451
+ ws.addEventListener("message", onMessage)
452
+
453
+ if (ws.readyState !== 1) {
454
+ const openDeferred = Deferred.unsafeMake<void>(fiber.id())
455
+ ws.addEventListener("open", () => {
456
+ open = true
457
+ Deferred.unsafeDone(openDeferred, Effect.void)
458
+ }, { once: true })
459
+ yield* Deferred.await(openDeferred).pipe(
460
+ Effect.timeoutFail({
461
+ duration: options?.openTimeout ?? 10000,
462
+ onTimeout: () => new SocketGenericError({ reason: "OpenTimeout", cause: "timeout waiting for \"open\"" })
463
+ }),
464
+ Effect.raceFirst(FiberSet.join(fiberSet))
465
+ )
466
+ }
467
+ open = true
468
+ yield* sendQueue.takeAll.pipe(
469
+ Effect.tap(([chunk]) =>
470
+ Effect.try({
471
+ try: () => {
472
+ for (const item of chunk) {
473
+ ws.send(item)
474
+ }
475
+ },
476
+ catch: (cause) => new SocketGenericError({ reason: "Write", cause })
477
+ })
478
+ ),
479
+ Effect.forever,
480
+ Effect.catchIf(SocketCloseError.is, (error) => {
481
+ ws.close(error.code, error.closeReason)
482
+ return Effect.fail(error)
483
+ }),
484
+ FiberSet.run(fiberSet)
496
485
  )
497
-
498
- const encoder = new TextEncoder()
499
- const run = <_, E, R>(handler: (_: Uint8Array) => Effect.Effect<_, E, R> | void) =>
500
- runRaw((data) =>
501
- typeof data === "string"
502
- ? handler(encoder.encode(data))
503
- : handler(data)
486
+ return yield* FiberSet.join(fiberSet).pipe(
487
+ Effect.catchIf(
488
+ SocketCloseError.isClean((_) => !closeCodeIsError(_)),
489
+ (_) => Effect.void
490
+ )
504
491
  )
492
+ }).pipe(
493
+ Effect.mapInputContext((input: Context.Context<R | Scope.Scope>) => Context.merge(acquireContext, input)),
494
+ Effect.scoped,
495
+ Effect.interruptible
496
+ )
505
497
 
506
- const write = (chunk: Uint8Array | string | CloseEvent) => Queue.offer(sendQueue, chunk)
507
- const writer = Effect.succeed(write)
498
+ const encoder = new TextEncoder()
499
+ const run = <_, E, R>(handler: (_: Uint8Array) => Effect.Effect<_, E, R> | void) =>
500
+ runRaw((data) =>
501
+ typeof data === "string"
502
+ ? handler(encoder.encode(data))
503
+ : handler(data)
504
+ )
508
505
 
509
- return Socket.of({
510
- [TypeId]: TypeId,
511
- run,
512
- runRaw,
513
- writer
514
- })
506
+ const write = (chunk: Uint8Array | string | CloseEvent) =>
507
+ isCloseEvent(chunk)
508
+ ? sendQueue.fail(
509
+ new SocketCloseError({
510
+ reason: "Close",
511
+ code: chunk.code,
512
+ closeReason: chunk.reason
513
+ })
514
+ )
515
+ : sendQueue.offer(chunk)
516
+ const writer = Effect.succeed(write)
517
+
518
+ return Socket.of({
519
+ [TypeId]: TypeId,
520
+ run,
521
+ runRaw,
522
+ writer
515
523
  })
516
- )
524
+ })
517
525
 
518
526
  /**
519
527
  * @since 1.0.0
@@ -574,109 +582,106 @@ export interface InputTransformStream {
574
582
  export const fromTransformStream = <R>(acquire: Effect.Effect<InputTransformStream, SocketError, R>, options?: {
575
583
  readonly closeCodeIsError?: (code: number) => boolean
576
584
  }): Effect.Effect<Socket, never, Exclude<R, Scope.Scope>> =>
577
- Effect.withFiberRuntime<Socket, never, Exclude<R, Scope.Scope>>((fiber) =>
578
- Effect.gen(function*() {
579
- const EOF = Symbol()
580
- const sendQueue = yield* Queue.dropping<Uint8Array | string | CloseEvent | typeof EOF>(
581
- fiber.getFiberRef(currentSendQueueCapacity)
582
- )
583
- const acquireContext = fiber.currentContext as Context.Context<R>
584
- const closeCodeIsError = options?.closeCodeIsError ?? defaultCloseCodeIsError
585
- const runRaw = <_, E, R>(handler: (_: string | Uint8Array) => Effect.Effect<_, E, R> | void) =>
586
- Effect.gen(function*() {
587
- const stream = yield* acquire
588
- const reader = yield* Effect.acquireRelease(
589
- Effect.sync(() => stream.readable.getReader()),
590
- (reader) =>
591
- Effect.promise(() => reader.cancel()).pipe(
592
- Effect.tap(() => {
593
- reader.releaseLock()
594
- })
595
- )
596
- )
597
- const writer = yield* Effect.acquireRelease(
598
- Effect.sync(() => stream.writable.getWriter()),
599
- (reader) => Effect.sync(() => reader.releaseLock())
600
- )
601
- const fiberSet = yield* FiberSet.make<any, E | SocketError>()
602
- const encoder = new TextEncoder()
603
- yield* Queue.take(sendQueue).pipe(
604
- Effect.tap((chunk) => {
605
- if (
606
- chunk === EOF ||
607
- isCloseEvent(chunk)
608
- ) {
609
- return Effect.zipRight(
610
- Effect.promise(() => writer.close()),
611
- chunk === EOF ? Effect.interrupt : Effect.fail(
612
- new SocketCloseError({
613
- reason: "Close",
614
- code: chunk.code,
615
- closeReason: chunk.reason
616
- })
617
- )
618
- )
619
- }
620
- return Effect.try({
621
- try: () => {
622
- if (typeof chunk === "string") {
623
- writer.write(encoder.encode(chunk))
585
+ Effect.gen(function*() {
586
+ const fiber = Option.getOrThrow(Fiber.getCurrentFiber())
587
+ const sendQueue = yield* Mailbox.make<Uint8Array | string, SocketError>({
588
+ capacity: fiber.getFiberRef(currentSendQueueCapacity),
589
+ strategy: "dropping"
590
+ })
591
+ const acquireContext = fiber.currentContext as Context.Context<R>
592
+ const closeCodeIsError = options?.closeCodeIsError ?? defaultCloseCodeIsError
593
+ const runRaw = <_, E, R>(handler: (_: string | Uint8Array) => Effect.Effect<_, E, R> | void) =>
594
+ Effect.gen(function*() {
595
+ const stream = yield* acquire
596
+ const reader = yield* Effect.acquireRelease(
597
+ Effect.sync(() => stream.readable.getReader()),
598
+ (reader) =>
599
+ Effect.promise(() => reader.cancel()).pipe(
600
+ Effect.tap(() => {
601
+ reader.releaseLock()
602
+ })
603
+ )
604
+ )
605
+ const writer = yield* Effect.acquireRelease(
606
+ Effect.sync(() => stream.writable.getWriter()),
607
+ (reader) => Effect.sync(() => reader.releaseLock())
608
+ )
609
+ const fiberSet = yield* FiberSet.make<any, E | SocketError>()
610
+ const encoder = new TextEncoder()
611
+ yield* sendQueue.takeAll.pipe(
612
+ Effect.flatMap(([chunk, done]) => {
613
+ const write = Effect.try({
614
+ try: () => {
615
+ for (const item of chunk) {
616
+ if (typeof item === "string") {
617
+ writer.write(encoder.encode(item))
624
618
  } else {
625
- writer.write(chunk)
619
+ writer.write(item)
626
620
  }
627
- },
628
- catch: (cause) => new SocketGenericError({ reason: "Write", cause })
629
- })
630
- }),
631
- Effect.forever,
632
- FiberSet.run(fiberSet)
633
- )
634
-
635
- yield* Effect.tryPromise({
636
- try: () => reader.read(),
637
- catch: (cause) => new SocketGenericError({ reason: "Read", cause })
638
- }).pipe(
639
- Effect.tap((result) => {
640
- if (result.done) {
641
- return Effect.fail(new SocketCloseError({ reason: "Close", code: 1000 }))
642
- }
643
- return handler(result.value)
644
- }),
645
- Effect.forever,
646
- FiberSet.run(fiberSet)
647
- )
621
+ }
622
+ },
623
+ catch: (cause) => new SocketGenericError({ reason: "Write", cause })
624
+ })
625
+ return done ? Effect.zipRight(write, Effect.interrupt) : write
626
+ }),
627
+ Effect.forever,
628
+ Effect.ensuring(Effect.promise(() => writer.close())),
629
+ FiberSet.run(fiberSet)
630
+ )
648
631
 
649
- return yield* FiberSet.join(fiberSet).pipe(
650
- Effect.catchIf(
651
- SocketCloseError.isClean((_) => !closeCodeIsError(_)),
652
- (_) => Effect.void
653
- )
654
- )
632
+ yield* Effect.tryPromise({
633
+ try: () => reader.read(),
634
+ catch: (cause) => new SocketGenericError({ reason: "Read", cause })
655
635
  }).pipe(
656
- Effect.mapInputContext((input: Context.Context<R | Scope.Scope>) => Context.merge(acquireContext, input)),
657
- Effect.scoped,
658
- Effect.interruptible
636
+ Effect.tap((result) => {
637
+ if (result.done) {
638
+ return Effect.fail(new SocketCloseError({ reason: "Close", code: 1000 }))
639
+ }
640
+ return handler(result.value)
641
+ }),
642
+ Effect.forever,
643
+ FiberSet.run(fiberSet)
659
644
  )
660
645
 
661
- const encoder = new TextEncoder()
662
- const run = <_, E, R>(handler: (_: Uint8Array) => Effect.Effect<_, E, R> | void) =>
663
- runRaw((data) =>
664
- typeof data === "string"
665
- ? handler(encoder.encode(data))
666
- : handler(data)
646
+ return yield* FiberSet.join(fiberSet).pipe(
647
+ Effect.catchIf(
648
+ SocketCloseError.isClean((_) => !closeCodeIsError(_)),
649
+ (_) => Effect.void
650
+ )
667
651
  )
652
+ }).pipe(
653
+ Effect.mapInputContext((input: Context.Context<R | Scope.Scope>) => Context.merge(acquireContext, input)),
654
+ Effect.scoped,
655
+ Effect.interruptible
656
+ )
668
657
 
669
- const write = (chunk: Uint8Array | string | CloseEvent) => Queue.offer(sendQueue, chunk)
670
- const writer = Effect.acquireRelease(
671
- Effect.succeed(write),
672
- () => Queue.offer(sendQueue, EOF)
658
+ const encoder = new TextEncoder()
659
+ const run = <_, E, R>(handler: (_: Uint8Array) => Effect.Effect<_, E, R> | void) =>
660
+ runRaw((data) =>
661
+ typeof data === "string"
662
+ ? handler(encoder.encode(data))
663
+ : handler(data)
673
664
  )
674
665
 
675
- return Socket.of({
676
- [TypeId]: TypeId,
677
- run,
678
- runRaw,
679
- writer
680
- })
666
+ const write = (chunk: Uint8Array | string | CloseEvent) =>
667
+ isCloseEvent(chunk) ?
668
+ sendQueue.fail(
669
+ new SocketCloseError({
670
+ reason: "Close",
671
+ code: chunk.code,
672
+ closeReason: chunk.reason
673
+ })
674
+ ) :
675
+ sendQueue.offer(chunk)
676
+ const writer = Effect.acquireRelease(
677
+ Effect.succeed(write),
678
+ () => sendQueue.end
679
+ )
680
+
681
+ return Socket.of({
682
+ [TypeId]: TypeId,
683
+ run,
684
+ runRaw,
685
+ writer
681
686
  })
682
- )
687
+ })