@effect/platform 0.70.4 → 0.70.6
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/README.md +1 -0
- package/dist/cjs/OpenApi.js +16 -4
- package/dist/cjs/OpenApi.js.map +1 -1
- package/dist/cjs/Socket.js +96 -82
- package/dist/cjs/Socket.js.map +1 -1
- package/dist/dts/OpenApi.d.ts.map +1 -1
- package/dist/dts/Socket.d.ts +1 -1
- package/dist/dts/Socket.d.ts.map +1 -1
- package/dist/esm/OpenApi.js +16 -4
- package/dist/esm/OpenApi.js.map +1 -1
- package/dist/esm/Socket.js +96 -82
- package/dist/esm/Socket.js.map +1 -1
- package/package.json +2 -2
- package/src/OpenApi.ts +8 -4
- package/src/Socket.ts +171 -166
package/src/Socket.ts
CHANGED
|
@@ -9,14 +9,12 @@ 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"
|
|
13
12
|
import * as FiberRef from "effect/FiberRef"
|
|
14
13
|
import * as FiberSet from "effect/FiberSet"
|
|
15
14
|
import { dual } from "effect/Function"
|
|
16
15
|
import { globalValue } from "effect/GlobalValue"
|
|
17
16
|
import * as Layer from "effect/Layer"
|
|
18
17
|
import * as Mailbox from "effect/Mailbox"
|
|
19
|
-
import * as Option from "effect/Option"
|
|
20
18
|
import * as Predicate from "effect/Predicate"
|
|
21
19
|
import * as Scope from "effect/Scope"
|
|
22
20
|
import type * as AsyncProducer from "effect/SingleProducerAsyncInput"
|
|
@@ -61,7 +59,7 @@ export interface Socket {
|
|
|
61
59
|
handler: (_: string | Uint8Array) => Effect.Effect<_, E, R> | void
|
|
62
60
|
) => Effect.Effect<void, SocketError | E, R>
|
|
63
61
|
readonly writer: Effect.Effect<
|
|
64
|
-
(chunk: Uint8Array | string | CloseEvent) => Effect.Effect<
|
|
62
|
+
(chunk: Uint8Array | string | CloseEvent) => Effect.Effect<void, SocketError>,
|
|
65
63
|
never,
|
|
66
64
|
Scope.Scope
|
|
67
65
|
>
|
|
@@ -195,11 +193,16 @@ export const toChannelMap = <IE, A>(
|
|
|
195
193
|
const mailbox = yield* Mailbox.make<A, SocketError | IE>()
|
|
196
194
|
const writeScope = yield* Scope.fork(scope, ExecutionStrategy.sequential)
|
|
197
195
|
const write = yield* Scope.extend(self.writer, writeScope)
|
|
196
|
+
function* emit(chunk: Chunk.Chunk<Uint8Array | string | CloseEvent>) {
|
|
197
|
+
for (const data of chunk) {
|
|
198
|
+
yield* write(data)
|
|
199
|
+
}
|
|
200
|
+
}
|
|
198
201
|
const input: AsyncProducer.AsyncInputProducer<IE, Chunk.Chunk<Uint8Array | string | CloseEvent>, unknown> = {
|
|
199
202
|
awaitRead: () => Effect.void,
|
|
200
203
|
emit(chunk) {
|
|
201
204
|
return Effect.catchAllCause(
|
|
202
|
-
Effect.
|
|
205
|
+
Effect.gen(() => emit(chunk)),
|
|
203
206
|
(cause) => mailbox.failCause(cause)
|
|
204
207
|
)
|
|
205
208
|
},
|
|
@@ -395,106 +398,92 @@ export const fromWebSocket = <RO>(
|
|
|
395
398
|
readonly openTimeout?: DurationInput
|
|
396
399
|
}
|
|
397
400
|
): Effect.Effect<Socket, never, Exclude<RO, Scope.Scope>> =>
|
|
398
|
-
Effect.
|
|
399
|
-
|
|
400
|
-
const
|
|
401
|
-
capacity: fiber.getFiberRef(currentSendQueueCapacity),
|
|
402
|
-
strategy: "dropping"
|
|
403
|
-
})
|
|
401
|
+
Effect.withFiberRuntime((fiber) => {
|
|
402
|
+
let currentWS: globalThis.WebSocket | undefined
|
|
403
|
+
const latch = Effect.unsafeMakeLatch(false)
|
|
404
404
|
const acquireContext = fiber.currentContext as Context.Context<RO>
|
|
405
405
|
const closeCodeIsError = options?.closeCodeIsError ?? defaultCloseCodeIsError
|
|
406
406
|
|
|
407
407
|
const runRaw = <_, E, R>(handler: (_: string | Uint8Array) => Effect.Effect<_, E, R> | void) =>
|
|
408
|
-
Effect.
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
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
|
|
419
|
-
)
|
|
420
|
-
}
|
|
421
|
-
const result = handler(event.data)
|
|
422
|
-
if (Effect.isEffect(result)) {
|
|
423
|
-
run(result)
|
|
424
|
-
}
|
|
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 }))
|
|
408
|
+
Effect.scopedWith((scope) =>
|
|
409
|
+
Effect.gen(function*() {
|
|
410
|
+
const fiberSet = yield* FiberSet.make<any, E | SocketError>().pipe(
|
|
411
|
+
Scope.extend(scope)
|
|
432
412
|
)
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
413
|
+
const ws = yield* Scope.extend(acquire, scope)
|
|
414
|
+
const run = yield* Effect.provideService(FiberSet.runtime(fiberSet)<R>(), WebSocket, ws)
|
|
415
|
+
let open = false
|
|
416
|
+
|
|
417
|
+
function onMessage(event: MessageEvent) {
|
|
418
|
+
if (event.data instanceof Blob) {
|
|
419
|
+
return Effect.promise(() => event.data.arrayBuffer() as Promise<ArrayBuffer>).pipe(
|
|
420
|
+
Effect.andThen((buffer) => handler(new Uint8Array(buffer))),
|
|
421
|
+
run
|
|
422
|
+
)
|
|
423
|
+
}
|
|
424
|
+
const result = handler(event.data)
|
|
425
|
+
if (Effect.isEffect(result)) {
|
|
426
|
+
run(result)
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
function onError(cause: Event) {
|
|
430
|
+
ws.removeEventListener("message", onMessage)
|
|
431
|
+
ws.removeEventListener("close", onClose)
|
|
432
|
+
Deferred.unsafeDone(
|
|
433
|
+
fiberSet.deferred,
|
|
434
|
+
Effect.fail(new SocketGenericError({ reason: open ? "Read" : "Open", cause }))
|
|
445
435
|
)
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
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.take.pipe(
|
|
469
|
-
Effect.tap((chunk) => {
|
|
470
|
-
if (isCloseEvent(chunk)) {
|
|
471
|
-
ws.close(chunk.code, chunk.reason)
|
|
472
|
-
return Effect.fail(
|
|
436
|
+
}
|
|
437
|
+
function onClose(event: globalThis.CloseEvent) {
|
|
438
|
+
ws.removeEventListener("message", onMessage)
|
|
439
|
+
ws.removeEventListener("error", onError)
|
|
440
|
+
Deferred.unsafeDone(
|
|
441
|
+
fiberSet.deferred,
|
|
442
|
+
Effect.fail(
|
|
473
443
|
new SocketCloseError({
|
|
474
444
|
reason: "Close",
|
|
475
|
-
code:
|
|
476
|
-
closeReason:
|
|
445
|
+
code: event.code,
|
|
446
|
+
closeReason: event.reason
|
|
477
447
|
})
|
|
478
448
|
)
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
(
|
|
449
|
+
)
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
ws.addEventListener("close", onClose, { once: true })
|
|
453
|
+
ws.addEventListener("error", onError, { once: true })
|
|
454
|
+
ws.addEventListener("message", onMessage)
|
|
455
|
+
|
|
456
|
+
if (ws.readyState !== 1) {
|
|
457
|
+
const openDeferred = Deferred.unsafeMake<void>(fiber.id())
|
|
458
|
+
ws.addEventListener("open", () => {
|
|
459
|
+
open = true
|
|
460
|
+
Deferred.unsafeDone(openDeferred, Effect.void)
|
|
461
|
+
}, { once: true })
|
|
462
|
+
yield* Deferred.await(openDeferred).pipe(
|
|
463
|
+
Effect.timeoutFail({
|
|
464
|
+
duration: options?.openTimeout ?? 10000,
|
|
465
|
+
onTimeout: () =>
|
|
466
|
+
new SocketGenericError({ reason: "OpenTimeout", cause: "timeout waiting for \"open\"" })
|
|
467
|
+
}),
|
|
468
|
+
Effect.raceFirst(FiberSet.join(fiberSet))
|
|
469
|
+
)
|
|
470
|
+
}
|
|
471
|
+
open = true
|
|
472
|
+
currentWS = ws
|
|
473
|
+
yield* latch.open
|
|
474
|
+
return yield* FiberSet.join(fiberSet).pipe(
|
|
475
|
+
Effect.catchIf(
|
|
476
|
+
SocketCloseError.isClean((_) => !closeCodeIsError(_)),
|
|
477
|
+
(_) => Effect.void
|
|
478
|
+
)
|
|
493
479
|
)
|
|
494
|
-
)
|
|
495
|
-
|
|
496
|
-
Effect.mapInputContext((input: Context.Context<R
|
|
497
|
-
Effect.
|
|
480
|
+
})
|
|
481
|
+
).pipe(
|
|
482
|
+
Effect.mapInputContext((input: Context.Context<R>) => Context.merge(acquireContext, input)),
|
|
483
|
+
Effect.ensuring(Effect.sync(() => {
|
|
484
|
+
latch.unsafeClose()
|
|
485
|
+
currentWS = undefined
|
|
486
|
+
})),
|
|
498
487
|
Effect.interruptible
|
|
499
488
|
)
|
|
500
489
|
|
|
@@ -503,18 +492,28 @@ export const fromWebSocket = <RO>(
|
|
|
503
492
|
runRaw((data) =>
|
|
504
493
|
typeof data === "string"
|
|
505
494
|
? handler(encoder.encode(data))
|
|
506
|
-
:
|
|
495
|
+
: data instanceof Uint8Array
|
|
496
|
+
? handler(data)
|
|
497
|
+
: handler(new Uint8Array(data))
|
|
507
498
|
)
|
|
508
499
|
|
|
509
|
-
const write = (chunk: Uint8Array | string | CloseEvent) =>
|
|
500
|
+
const write = (chunk: Uint8Array | string | CloseEvent) =>
|
|
501
|
+
latch.whenOpen(Effect.sync(() => {
|
|
502
|
+
const ws = currentWS!
|
|
503
|
+
if (isCloseEvent(chunk)) {
|
|
504
|
+
ws.close(chunk.code, chunk.reason)
|
|
505
|
+
} else {
|
|
506
|
+
ws.send(chunk)
|
|
507
|
+
}
|
|
508
|
+
}))
|
|
510
509
|
const writer = Effect.succeed(write)
|
|
511
510
|
|
|
512
|
-
return Socket.of({
|
|
511
|
+
return Effect.succeed(Socket.of({
|
|
513
512
|
[TypeId]: TypeId,
|
|
514
513
|
run,
|
|
515
514
|
runRaw,
|
|
516
515
|
writer
|
|
517
|
-
})
|
|
516
|
+
}))
|
|
518
517
|
})
|
|
519
518
|
|
|
520
519
|
/**
|
|
@@ -576,77 +575,60 @@ export interface InputTransformStream {
|
|
|
576
575
|
export const fromTransformStream = <R>(acquire: Effect.Effect<InputTransformStream, SocketError, R>, options?: {
|
|
577
576
|
readonly closeCodeIsError?: (code: number) => boolean
|
|
578
577
|
}): Effect.Effect<Socket, never, Exclude<R, Scope.Scope>> =>
|
|
579
|
-
Effect.
|
|
580
|
-
const
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
}
|
|
578
|
+
Effect.withFiberRuntime((fiber) => {
|
|
579
|
+
const latch = Effect.unsafeMakeLatch(false)
|
|
580
|
+
let currentStream: {
|
|
581
|
+
readonly stream: InputTransformStream
|
|
582
|
+
readonly fiberSet: FiberSet.FiberSet<any, any>
|
|
583
|
+
} | undefined
|
|
585
584
|
const acquireContext = fiber.currentContext as Context.Context<R>
|
|
586
585
|
const closeCodeIsError = options?.closeCodeIsError ?? defaultCloseCodeIsError
|
|
587
586
|
const runRaw = <_, E, R>(handler: (_: string | Uint8Array) => Effect.Effect<_, E, R> | void) =>
|
|
588
|
-
Effect.
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
(
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
}
|
|
616
|
-
return Effect.tryPromise({
|
|
617
|
-
try: () => writer.write(typeof chunk === "string" ? encoder.encode(chunk) : chunk),
|
|
618
|
-
catch: (cause) => new SocketGenericError({ reason: "Write", cause })
|
|
619
|
-
})
|
|
620
|
-
}),
|
|
621
|
-
Effect.forever,
|
|
622
|
-
Effect.catchTag("NoSuchElementException", () => Effect.void),
|
|
623
|
-
Effect.ensuring(Effect.promise(() => writer.close())),
|
|
624
|
-
FiberSet.run(fiberSet)
|
|
625
|
-
)
|
|
587
|
+
Effect.scopedWith((scope) =>
|
|
588
|
+
Effect.gen(function*() {
|
|
589
|
+
const stream = yield* Scope.extend(acquire, scope)
|
|
590
|
+
const reader = stream.readable.getReader()
|
|
591
|
+
yield* Scope.addFinalizer(scope, Effect.promise(() => reader.cancel()))
|
|
592
|
+
const fiberSet = yield* FiberSet.make<any, E | SocketError>().pipe(
|
|
593
|
+
Scope.extend(scope)
|
|
594
|
+
)
|
|
595
|
+
const runFork = yield* FiberSet.runtime(fiberSet)<R>()
|
|
596
|
+
|
|
597
|
+
yield* Effect.tryPromise({
|
|
598
|
+
try: async () => {
|
|
599
|
+
while (true) {
|
|
600
|
+
const { done, value } = await reader.read()
|
|
601
|
+
if (done) {
|
|
602
|
+
throw new SocketCloseError({ reason: "Close", code: 1000 })
|
|
603
|
+
}
|
|
604
|
+
const result = handler(value)
|
|
605
|
+
if (Effect.isEffect(result)) {
|
|
606
|
+
runFork(result)
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
},
|
|
610
|
+
catch: (cause) => isSocketError(cause) ? cause : new SocketGenericError({ reason: "Read", cause })
|
|
611
|
+
}).pipe(
|
|
612
|
+
FiberSet.run(fiberSet)
|
|
613
|
+
)
|
|
626
614
|
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
catch: (cause) => new SocketGenericError({ reason: "Read", cause })
|
|
630
|
-
}).pipe(
|
|
631
|
-
Effect.tap((result) => {
|
|
632
|
-
if (result.done) {
|
|
633
|
-
return Effect.fail(new SocketCloseError({ reason: "Close", code: 1000 }))
|
|
634
|
-
}
|
|
635
|
-
return handler(result.value)
|
|
636
|
-
}),
|
|
637
|
-
Effect.forever,
|
|
638
|
-
FiberSet.run(fiberSet)
|
|
639
|
-
)
|
|
615
|
+
currentStream = { stream, fiberSet }
|
|
616
|
+
yield* latch.open
|
|
640
617
|
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
618
|
+
return yield* FiberSet.join(fiberSet).pipe(
|
|
619
|
+
Effect.catchIf(
|
|
620
|
+
SocketCloseError.isClean((_) => !closeCodeIsError(_)),
|
|
621
|
+
(_) => Effect.void
|
|
622
|
+
)
|
|
645
623
|
)
|
|
646
|
-
)
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
Effect.
|
|
624
|
+
})
|
|
625
|
+
).pipe(
|
|
626
|
+
(_) => _,
|
|
627
|
+
Effect.mapInputContext((input: Context.Context<R>) => Context.merge(acquireContext, input)),
|
|
628
|
+
Effect.ensuring(Effect.sync(() => {
|
|
629
|
+
latch.unsafeClose()
|
|
630
|
+
currentStream = undefined
|
|
631
|
+
})),
|
|
650
632
|
Effect.interruptible
|
|
651
633
|
)
|
|
652
634
|
|
|
@@ -658,16 +640,39 @@ export const fromTransformStream = <R>(acquire: Effect.Effect<InputTransformStre
|
|
|
658
640
|
: handler(data)
|
|
659
641
|
)
|
|
660
642
|
|
|
661
|
-
const
|
|
643
|
+
const writers = new WeakMap<InputTransformStream, WritableStreamDefaultWriter<Uint8Array>>()
|
|
644
|
+
const getWriter = (stream: InputTransformStream) => {
|
|
645
|
+
let writer = writers.get(stream)
|
|
646
|
+
if (!writer) {
|
|
647
|
+
writer = stream.writable.getWriter()
|
|
648
|
+
writers.set(stream, writer)
|
|
649
|
+
}
|
|
650
|
+
return writer
|
|
651
|
+
}
|
|
652
|
+
const write = (chunk: Uint8Array | string | CloseEvent) =>
|
|
653
|
+
latch.whenOpen(Effect.suspend(() => {
|
|
654
|
+
const { fiberSet, stream } = currentStream!
|
|
655
|
+
if (isCloseEvent(chunk)) {
|
|
656
|
+
return Deferred.fail(
|
|
657
|
+
fiberSet.deferred,
|
|
658
|
+
new SocketCloseError({ reason: "Close", code: chunk.code, closeReason: chunk.reason })
|
|
659
|
+
)
|
|
660
|
+
}
|
|
661
|
+
return Effect.promise(() => getWriter(stream).write(typeof chunk === "string" ? encoder.encode(chunk) : chunk))
|
|
662
|
+
}))
|
|
662
663
|
const writer = Effect.acquireRelease(
|
|
663
664
|
Effect.succeed(write),
|
|
664
|
-
() =>
|
|
665
|
+
() =>
|
|
666
|
+
Effect.promise(async () => {
|
|
667
|
+
if (!currentStream) return
|
|
668
|
+
await getWriter(currentStream.stream).close()
|
|
669
|
+
})
|
|
665
670
|
)
|
|
666
671
|
|
|
667
|
-
return Socket.of({
|
|
672
|
+
return Effect.succeed(Socket.of({
|
|
668
673
|
[TypeId]: TypeId,
|
|
669
674
|
run,
|
|
670
675
|
runRaw,
|
|
671
676
|
writer
|
|
672
|
-
})
|
|
677
|
+
}))
|
|
673
678
|
})
|