@effect/platform 0.69.15 → 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/dist/cjs/HttpApiClient.js +18 -8
- package/dist/cjs/HttpApiClient.js.map +1 -1
- package/dist/cjs/Socket.js +47 -35
- package/dist/cjs/Socket.js.map +1 -1
- package/dist/dts/HttpApiClient.d.ts.map +1 -1
- package/dist/dts/Socket.d.ts.map +1 -1
- package/dist/esm/HttpApiClient.js +18 -8
- package/dist/esm/HttpApiClient.js.map +1 -1
- package/dist/esm/Socket.js +47 -35
- package/dist/esm/Socket.js.map +1 -1
- package/package.json +2 -2
- package/src/HttpApiClient.ts +34 -44
- package/src/Socket.ts +210 -205
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.
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
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
|
-
|
|
448
|
-
|
|
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
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
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
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
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
|
-
}
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
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
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
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
|
-
|
|
507
|
-
|
|
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
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
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.
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
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(
|
|
619
|
+
writer.write(item)
|
|
626
620
|
}
|
|
627
|
-
}
|
|
628
|
-
|
|
629
|
-
})
|
|
630
|
-
})
|
|
631
|
-
Effect.
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
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
|
-
|
|
650
|
-
|
|
651
|
-
|
|
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.
|
|
657
|
-
|
|
658
|
-
|
|
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
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
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
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
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
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
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
|
+
})
|