@effect/platform 0.70.4 → 0.70.5
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 +95 -81
- 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 +95 -81
- package/dist/esm/Socket.js.map +1 -1
- package/package.json +2 -2
- package/src/OpenApi.ts +8 -4
- package/src/Socket.ts +168 -165
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
|
|
|
@@ -506,15 +495,23 @@ export const fromWebSocket = <RO>(
|
|
|
506
495
|
: handler(data)
|
|
507
496
|
)
|
|
508
497
|
|
|
509
|
-
const write = (chunk: Uint8Array | string | CloseEvent) =>
|
|
498
|
+
const write = (chunk: Uint8Array | string | CloseEvent) =>
|
|
499
|
+
latch.whenOpen(Effect.sync(() => {
|
|
500
|
+
const ws = currentWS!
|
|
501
|
+
if (isCloseEvent(chunk)) {
|
|
502
|
+
ws.close(chunk.code, chunk.reason)
|
|
503
|
+
} else {
|
|
504
|
+
ws.send(chunk)
|
|
505
|
+
}
|
|
506
|
+
}))
|
|
510
507
|
const writer = Effect.succeed(write)
|
|
511
508
|
|
|
512
|
-
return Socket.of({
|
|
509
|
+
return Effect.succeed(Socket.of({
|
|
513
510
|
[TypeId]: TypeId,
|
|
514
511
|
run,
|
|
515
512
|
runRaw,
|
|
516
513
|
writer
|
|
517
|
-
})
|
|
514
|
+
}))
|
|
518
515
|
})
|
|
519
516
|
|
|
520
517
|
/**
|
|
@@ -576,77 +573,60 @@ export interface InputTransformStream {
|
|
|
576
573
|
export const fromTransformStream = <R>(acquire: Effect.Effect<InputTransformStream, SocketError, R>, options?: {
|
|
577
574
|
readonly closeCodeIsError?: (code: number) => boolean
|
|
578
575
|
}): Effect.Effect<Socket, never, Exclude<R, Scope.Scope>> =>
|
|
579
|
-
Effect.
|
|
580
|
-
const
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
}
|
|
576
|
+
Effect.withFiberRuntime((fiber) => {
|
|
577
|
+
const latch = Effect.unsafeMakeLatch(false)
|
|
578
|
+
let currentStream: {
|
|
579
|
+
readonly stream: InputTransformStream
|
|
580
|
+
readonly fiberSet: FiberSet.FiberSet<any, any>
|
|
581
|
+
} | undefined
|
|
585
582
|
const acquireContext = fiber.currentContext as Context.Context<R>
|
|
586
583
|
const closeCodeIsError = options?.closeCodeIsError ?? defaultCloseCodeIsError
|
|
587
584
|
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
|
-
)
|
|
585
|
+
Effect.scopedWith((scope) =>
|
|
586
|
+
Effect.gen(function*() {
|
|
587
|
+
const stream = yield* Scope.extend(acquire, scope)
|
|
588
|
+
const reader = stream.readable.getReader()
|
|
589
|
+
yield* Scope.addFinalizer(scope, Effect.promise(() => reader.cancel()))
|
|
590
|
+
const fiberSet = yield* FiberSet.make<any, E | SocketError>().pipe(
|
|
591
|
+
Scope.extend(scope)
|
|
592
|
+
)
|
|
593
|
+
const runFork = yield* FiberSet.runtime(fiberSet)<R>()
|
|
594
|
+
|
|
595
|
+
yield* Effect.tryPromise({
|
|
596
|
+
try: async () => {
|
|
597
|
+
while (true) {
|
|
598
|
+
const { done, value } = await reader.read()
|
|
599
|
+
if (done) {
|
|
600
|
+
throw new SocketCloseError({ reason: "Close", code: 1000 })
|
|
601
|
+
}
|
|
602
|
+
const result = handler(value)
|
|
603
|
+
if (Effect.isEffect(result)) {
|
|
604
|
+
runFork(result)
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
},
|
|
608
|
+
catch: (cause) => isSocketError(cause) ? cause : new SocketGenericError({ reason: "Read", cause })
|
|
609
|
+
}).pipe(
|
|
610
|
+
FiberSet.run(fiberSet)
|
|
611
|
+
)
|
|
626
612
|
|
|
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
|
-
)
|
|
613
|
+
currentStream = { stream, fiberSet }
|
|
614
|
+
yield* latch.open
|
|
640
615
|
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
616
|
+
return yield* FiberSet.join(fiberSet).pipe(
|
|
617
|
+
Effect.catchIf(
|
|
618
|
+
SocketCloseError.isClean((_) => !closeCodeIsError(_)),
|
|
619
|
+
(_) => Effect.void
|
|
620
|
+
)
|
|
645
621
|
)
|
|
646
|
-
)
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
Effect.
|
|
622
|
+
})
|
|
623
|
+
).pipe(
|
|
624
|
+
(_) => _,
|
|
625
|
+
Effect.mapInputContext((input: Context.Context<R>) => Context.merge(acquireContext, input)),
|
|
626
|
+
Effect.ensuring(Effect.sync(() => {
|
|
627
|
+
latch.unsafeClose()
|
|
628
|
+
currentStream = undefined
|
|
629
|
+
})),
|
|
650
630
|
Effect.interruptible
|
|
651
631
|
)
|
|
652
632
|
|
|
@@ -658,16 +638,39 @@ export const fromTransformStream = <R>(acquire: Effect.Effect<InputTransformStre
|
|
|
658
638
|
: handler(data)
|
|
659
639
|
)
|
|
660
640
|
|
|
661
|
-
const
|
|
641
|
+
const writers = new WeakMap<InputTransformStream, WritableStreamDefaultWriter<Uint8Array>>()
|
|
642
|
+
const getWriter = (stream: InputTransformStream) => {
|
|
643
|
+
let writer = writers.get(stream)
|
|
644
|
+
if (!writer) {
|
|
645
|
+
writer = stream.writable.getWriter()
|
|
646
|
+
writers.set(stream, writer)
|
|
647
|
+
}
|
|
648
|
+
return writer
|
|
649
|
+
}
|
|
650
|
+
const write = (chunk: Uint8Array | string | CloseEvent) =>
|
|
651
|
+
latch.whenOpen(Effect.suspend(() => {
|
|
652
|
+
const { fiberSet, stream } = currentStream!
|
|
653
|
+
if (isCloseEvent(chunk)) {
|
|
654
|
+
return Deferred.fail(
|
|
655
|
+
fiberSet.deferred,
|
|
656
|
+
new SocketCloseError({ reason: "Close", code: chunk.code, closeReason: chunk.reason })
|
|
657
|
+
)
|
|
658
|
+
}
|
|
659
|
+
return Effect.promise(() => getWriter(stream).write(typeof chunk === "string" ? encoder.encode(chunk) : chunk))
|
|
660
|
+
}))
|
|
662
661
|
const writer = Effect.acquireRelease(
|
|
663
662
|
Effect.succeed(write),
|
|
664
|
-
() =>
|
|
663
|
+
() =>
|
|
664
|
+
Effect.promise(async () => {
|
|
665
|
+
if (!currentStream) return
|
|
666
|
+
await getWriter(currentStream.stream).close()
|
|
667
|
+
})
|
|
665
668
|
)
|
|
666
669
|
|
|
667
|
-
return Socket.of({
|
|
670
|
+
return Effect.succeed(Socket.of({
|
|
668
671
|
[TypeId]: TypeId,
|
|
669
672
|
run,
|
|
670
673
|
runRaw,
|
|
671
674
|
writer
|
|
672
|
-
})
|
|
675
|
+
}))
|
|
673
676
|
})
|