@effect/platform-bun 4.0.0-rc.112 → 4.0.0-rc.113
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/AGENTS.md +24 -9
- package/CLAUDE.md +24 -9
- package/ai-docs/package.json +2 -2
- package/ai-docs/src/01_effect/01_basics/02_effect-fn.ts +18 -5
- package/ai-docs/src/01_effect/01_basics/index.md +5 -3
- package/ai-docs/src/01_effect/03_services/20_layer-composition.ts +1 -1
- package/ai-docs/src/01_effect/03_services/20_layer-unwrap.ts +2 -2
- package/ai-docs/src/01_effect/05_resources/10_acquire-release.ts +2 -2
- package/ai-docs/src/03_stream/30_encoding.ts +5 -7
- package/ai-docs/src/08_observability/10_logging.ts +1 -1
- package/ai-docs/src/70_cli/10_basics.ts +7 -7
- package/ai-docs/src/71_ai/10_language-model.ts +2 -2
- package/ai-docs/src/71_ai/20_tools.ts +1 -1
- package/ai-docs/src/71_ai/30_chat.ts +1 -1
- package/dist/BunClusterHttp.d.ts +2 -2
- package/dist/BunClusterHttp.d.ts.map +1 -1
- package/dist/BunClusterHttp.js +3 -3
- package/dist/BunClusterHttp.js.map +1 -1
- package/dist/BunClusterSocket.d.ts +1 -1
- package/dist/BunClusterSocket.d.ts.map +1 -1
- package/dist/BunClusterSocket.js +2 -2
- package/dist/BunClusterSocket.js.map +1 -1
- package/dist/BunHttpPlatform.d.ts.map +1 -1
- package/dist/BunHttpPlatform.js +7 -4
- package/dist/BunHttpPlatform.js.map +1 -1
- package/dist/BunHttpServer.d.ts +9 -6
- package/dist/BunHttpServer.d.ts.map +1 -1
- package/dist/BunHttpServer.js +138 -35
- package/dist/BunHttpServer.js.map +1 -1
- package/dist/BunRedis.d.ts +1 -1
- package/dist/BunRedis.d.ts.map +1 -1
- package/dist/BunRedis.js +3 -12
- package/dist/BunRedis.js.map +1 -1
- package/dist/BunSocket.d.ts +3 -3
- package/dist/BunSocket.d.ts.map +1 -1
- package/dist/BunSocket.js +6 -3
- package/dist/BunSocket.js.map +1 -1
- package/dist/BunStream.d.ts +1 -1
- package/dist/BunStream.d.ts.map +1 -1
- package/dist/BunStream.js +8 -2
- package/dist/BunStream.js.map +1 -1
- package/package.json +5 -5
- package/src/BunClusterHttp.ts +3 -3
- package/src/BunClusterSocket.ts +4 -2
- package/src/BunHttpPlatform.ts +8 -4
- package/src/BunHttpServer.ts +153 -56
- package/src/BunRedis.ts +2 -1
- package/src/BunSocket.ts +7 -4
- package/src/BunStream.ts +12 -3
package/src/BunHttpServer.ts
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
* @since 4.0.0
|
|
13
13
|
*/
|
|
14
14
|
import type { Server as BunServer, ServerWebSocket } from "bun"
|
|
15
|
+
import type * as Arr from "effect/Array"
|
|
15
16
|
import * as Config from "effect/Config"
|
|
16
17
|
import type { ConfigError } from "effect/Config"
|
|
17
18
|
import * as Context from "effect/Context"
|
|
@@ -20,14 +21,15 @@ import * as Duration from "effect/Duration"
|
|
|
20
21
|
import * as Effect from "effect/Effect"
|
|
21
22
|
import * as Exit from "effect/Exit"
|
|
22
23
|
import * as Fiber from "effect/Fiber"
|
|
23
|
-
import * as FiberSet from "effect/FiberSet"
|
|
24
24
|
import type * as FileSystem from "effect/FileSystem"
|
|
25
|
-
import { flow } from "effect/Function"
|
|
25
|
+
import { constVoid, flow } from "effect/Function"
|
|
26
26
|
import * as Inspectable from "effect/Inspectable"
|
|
27
27
|
import * as Layer from "effect/Layer"
|
|
28
28
|
import * as Option from "effect/Option"
|
|
29
29
|
import type * as Path from "effect/Path"
|
|
30
30
|
import type * as Record from "effect/Record"
|
|
31
|
+
import * as Result from "effect/Result"
|
|
32
|
+
import * as Scheduler from "effect/Scheduler"
|
|
31
33
|
import type * as Schema from "effect/Schema"
|
|
32
34
|
import * as Scope from "effect/Scope"
|
|
33
35
|
import * as Semaphore from "effect/Semaphore"
|
|
@@ -47,6 +49,7 @@ import * as ServerRequest from "effect/unstable/http/HttpServerRequest"
|
|
|
47
49
|
import type * as ServerResponse from "effect/unstable/http/HttpServerResponse"
|
|
48
50
|
import type * as Multipart from "effect/unstable/http/Multipart"
|
|
49
51
|
import * as UrlParams from "effect/unstable/http/UrlParams"
|
|
52
|
+
import * as NetAddress from "effect/unstable/net/NetAddress"
|
|
50
53
|
import * as Socket from "effect/unstable/socket/Socket"
|
|
51
54
|
import * as Platform from "./BunHttpPlatform.ts"
|
|
52
55
|
import * as BunMultipart from "./BunMultipart.ts"
|
|
@@ -114,6 +117,22 @@ export const make = Effect.fnUntraced(
|
|
|
114
117
|
}
|
|
115
118
|
) {
|
|
116
119
|
const scope = yield* Effect.scope
|
|
120
|
+
let listenOptions = options
|
|
121
|
+
if (!("unix" in options) || options.unix === undefined) {
|
|
122
|
+
const internetOptions = options as Bun.Serve.HostnamePortServeOptions<WebSocketContext>
|
|
123
|
+
const hostname = internetOptions.hostname ?? "0.0.0.0"
|
|
124
|
+
if (Result.isFailure(NetAddress.ipFromString(hostname))) {
|
|
125
|
+
const resolved = yield* Effect.tryPromise({
|
|
126
|
+
try: async () => {
|
|
127
|
+
const result = await Bun.dns.lookup(hostname, { socketType: "tcp" })
|
|
128
|
+
if (result.length === 0) throw new globalThis.Error(`Could not resolve hostname: ${hostname}`)
|
|
129
|
+
return result[0].address
|
|
130
|
+
},
|
|
131
|
+
catch: (cause) => new Error.ServeError({ cause })
|
|
132
|
+
})
|
|
133
|
+
listenOptions = { ...options, hostname: resolved }
|
|
134
|
+
}
|
|
135
|
+
}
|
|
117
136
|
const { compressionThreshold = MIN_COMPRESSIBLE_SIZE, ...websocket } = options.websocket ?? {}
|
|
118
137
|
const handlerStack: Array<(request: Request, server: BunServer<WebSocketContext>) => Response | Promise<Response>> =
|
|
119
138
|
[
|
|
@@ -122,7 +141,7 @@ export const make = Effect.fnUntraced(
|
|
|
122
141
|
}
|
|
123
142
|
]
|
|
124
143
|
const server = Bun.serve<WebSocketContext, R>({
|
|
125
|
-
...
|
|
144
|
+
...listenOptions as ServeOptions<R>,
|
|
126
145
|
fetch: handlerStack[0],
|
|
127
146
|
websocket: {
|
|
128
147
|
...websocket,
|
|
@@ -134,16 +153,11 @@ export const make = Effect.fnUntraced(
|
|
|
134
153
|
},
|
|
135
154
|
close(ws, code, closeReason) {
|
|
136
155
|
code = typeof code === "number" ? code : 1001
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
reason: new Socket.SocketCloseError({ code, closeReason })
|
|
143
|
-
})
|
|
144
|
-
)
|
|
145
|
-
: Exit.void
|
|
146
|
-
)
|
|
156
|
+
const error = new Socket.SocketError({
|
|
157
|
+
reason: new Socket.SocketCloseError({ code, closeReason })
|
|
158
|
+
})
|
|
159
|
+
ws.data.closeError = error
|
|
160
|
+
ws.data.onClose(error)
|
|
147
161
|
}
|
|
148
162
|
}
|
|
149
163
|
})
|
|
@@ -158,8 +172,14 @@ export const make = Effect.fnUntraced(
|
|
|
158
172
|
|
|
159
173
|
yield* Scope.addFinalizer(scope, shutdown)
|
|
160
174
|
|
|
175
|
+
const address = "unix" in options && options.unix !== undefined
|
|
176
|
+
? NetAddress.unixPathAddress(options.unix)
|
|
177
|
+
: yield* Effect.fromResult(NetAddress.inetAddressFromIpString(server.hostname!, server.port!)).pipe(
|
|
178
|
+
Effect.mapError((cause) => new Error.ServeError({ cause }))
|
|
179
|
+
)
|
|
180
|
+
|
|
161
181
|
return Server.make({
|
|
162
|
-
address
|
|
182
|
+
address,
|
|
163
183
|
serve: Effect.fnUntraced(function*(httpApp, middleware) {
|
|
164
184
|
const parent = yield* Effect.fiber
|
|
165
185
|
const services = parent.context
|
|
@@ -188,11 +208,17 @@ export const make = Effect.fnUntraced(
|
|
|
188
208
|
yield* Scope.addFinalizerExit(serveScope, () => {
|
|
189
209
|
const index = handlerStack.indexOf(handler)
|
|
190
210
|
if (index !== -1) handlerStack.splice(index, 1)
|
|
191
|
-
server.reload({
|
|
211
|
+
server.reload({
|
|
212
|
+
fetch: handlerStack[handlerStack.length - 1],
|
|
213
|
+
...(options.routes === undefined ? undefined : { routes: options.routes })
|
|
214
|
+
})
|
|
192
215
|
return handlerStack.length === 1 ? preemptiveShutdown : Effect.void
|
|
193
216
|
})
|
|
194
217
|
handlerStack.push(handler)
|
|
195
|
-
server.reload({
|
|
218
|
+
server.reload({
|
|
219
|
+
fetch: handler,
|
|
220
|
+
...(options.routes === undefined ? undefined : { routes: options.routes })
|
|
221
|
+
})
|
|
196
222
|
})
|
|
197
223
|
})
|
|
198
224
|
}
|
|
@@ -234,7 +260,9 @@ const makeResponse = (
|
|
|
234
260
|
case "Empty": {
|
|
235
261
|
return new Response(undefined, fields)
|
|
236
262
|
}
|
|
237
|
-
case "Uint8Array":
|
|
263
|
+
case "Uint8Array": {
|
|
264
|
+
return new Response(body.text ?? body.body as any, fields)
|
|
265
|
+
}
|
|
238
266
|
case "Raw": {
|
|
239
267
|
if (body.body instanceof Response) {
|
|
240
268
|
for (const [key, value] of fields.headers.entries()) {
|
|
@@ -274,7 +302,7 @@ export const layerServer: <R extends string>(
|
|
|
274
302
|
readonly gracefulShutdownTimeout?: Duration.Input | undefined
|
|
275
303
|
readonly websocket?: WebSocketOptions | undefined
|
|
276
304
|
}
|
|
277
|
-
) => Layer.Layer<Server.HttpServer> = flow(make, Layer.effect(Server.HttpServer)) as any
|
|
305
|
+
) => Layer.Layer<Server.HttpServer, Error.ServeError> = flow(make, Layer.effect(Server.HttpServer)) as any
|
|
278
306
|
|
|
279
307
|
/**
|
|
280
308
|
* Layer that provides Bun HTTP support services: `HttpPlatform`, weak ETag generation, and `BunServices`.
|
|
@@ -308,7 +336,8 @@ export const layer = <R extends string>(
|
|
|
308
336
|
| Server.HttpServer
|
|
309
337
|
| HttpPlatform
|
|
310
338
|
| Etag.Generator
|
|
311
|
-
| BunServices.BunServices
|
|
339
|
+
| BunServices.BunServices,
|
|
340
|
+
Error.ServeError
|
|
312
341
|
> => Layer.mergeAll(layerServer(options), layerHttpServices)
|
|
313
342
|
|
|
314
343
|
/**
|
|
@@ -323,7 +352,7 @@ export const layerTest: Layer.Layer<
|
|
|
323
352
|
Layer.provide(FetchHttpClient.layer.pipe(
|
|
324
353
|
Layer.provide(Layer.succeed(FetchHttpClient.RequestInit)({ keepalive: false }))
|
|
325
354
|
)),
|
|
326
|
-
Layer.provideMerge(layer({ port: 0 }))
|
|
355
|
+
Layer.provideMerge(Layer.orDie(layer({ hostname: "127.0.0.1", port: 0 })))
|
|
327
356
|
)
|
|
328
357
|
|
|
329
358
|
/**
|
|
@@ -342,7 +371,7 @@ export const layerConfig = <R extends string>(
|
|
|
342
371
|
>
|
|
343
372
|
): Layer.Layer<
|
|
344
373
|
Server.HttpServer | HttpPlatform | FileSystem.FileSystem | Etag.Generator | Path.Path,
|
|
345
|
-
ConfigError
|
|
374
|
+
ConfigError | Error.ServeError
|
|
346
375
|
> =>
|
|
347
376
|
Layer.mergeAll(
|
|
348
377
|
Layer.effect(Server.HttpServer)(Effect.flatMap(Config.unwrap(options), make)),
|
|
@@ -355,9 +384,10 @@ export const layerConfig = <R extends string>(
|
|
|
355
384
|
|
|
356
385
|
interface WebSocketContext {
|
|
357
386
|
readonly deferred: Deferred.Deferred<ServerWebSocket<WebSocketContext>>
|
|
358
|
-
readonly closeDeferred: Deferred.Deferred<void, Socket.SocketError>
|
|
359
387
|
readonly buffer: Array<Uint8Array | string>
|
|
388
|
+
closeError: Socket.SocketError | undefined
|
|
360
389
|
run: (_: Uint8Array | string) => void
|
|
390
|
+
onClose: (error: Socket.SocketError) => void
|
|
361
391
|
}
|
|
362
392
|
|
|
363
393
|
function wsDefaultRun(this: WebSocketContext, _: Uint8Array | string) {
|
|
@@ -560,15 +590,15 @@ class BunServerRequest extends Inspectable.Class implements ServerRequest.HttpSe
|
|
|
560
590
|
get upgrade(): Effect.Effect<Socket.Socket, Error.HttpServerError> {
|
|
561
591
|
return Effect.callback<Socket.Socket, Error.HttpServerError>((resume) => {
|
|
562
592
|
const deferred = Deferred.makeUnsafe<ServerWebSocket<WebSocketContext>>()
|
|
563
|
-
const closeDeferred = Deferred.makeUnsafe<void, Socket.SocketError>()
|
|
564
593
|
const semaphore = Semaphore.makeUnsafe(1)
|
|
565
594
|
|
|
566
595
|
const success = this.bunServer.upgrade(this.source, {
|
|
567
596
|
data: {
|
|
568
597
|
deferred,
|
|
569
|
-
closeDeferred,
|
|
570
598
|
buffer: [],
|
|
571
|
-
|
|
599
|
+
closeError: undefined,
|
|
600
|
+
run: wsDefaultRun,
|
|
601
|
+
onClose: constVoid
|
|
572
602
|
}
|
|
573
603
|
})
|
|
574
604
|
if (!success) {
|
|
@@ -582,49 +612,116 @@ class BunServerRequest extends Inspectable.Class implements ServerRequest.HttpSe
|
|
|
582
612
|
))
|
|
583
613
|
return
|
|
584
614
|
}
|
|
615
|
+
const compressionThreshold = this.compressionThreshold
|
|
585
616
|
resume(Effect.map(Deferred.await(deferred), (ws) => {
|
|
586
617
|
const write = (chunk: Uint8Array | string | Socket.CloseEvent) =>
|
|
587
618
|
Effect.sync(() => {
|
|
588
619
|
if (typeof chunk === "string") {
|
|
589
|
-
ws.sendText(chunk, chunk.length >=
|
|
620
|
+
ws.sendText(chunk, chunk.length >= compressionThreshold)
|
|
590
621
|
} else if (Socket.isCloseEvent(chunk)) {
|
|
591
622
|
ws.close(chunk.code, chunk.reason)
|
|
592
623
|
} else {
|
|
593
|
-
ws.sendBinary(chunk, chunk.byteLength >=
|
|
624
|
+
ws.sendBinary(chunk, chunk.byteLength >= compressionThreshold)
|
|
594
625
|
}
|
|
595
|
-
|
|
596
|
-
return true
|
|
597
626
|
})
|
|
598
|
-
const
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
function runRaw(data: Uint8Array | string) {
|
|
607
|
-
const result = handler(data)
|
|
608
|
-
if (Effect.isEffect(result)) {
|
|
609
|
-
run(result)
|
|
627
|
+
const writeAll = (chunks: ReadonlyArray<Uint8Array | string>) =>
|
|
628
|
+
Effect.sync(() => {
|
|
629
|
+
for (let i = 0; i < chunks.length; i++) {
|
|
630
|
+
const chunk = chunks[i]
|
|
631
|
+
if (typeof chunk === "string") {
|
|
632
|
+
ws.sendText(chunk, chunk.length >= compressionThreshold)
|
|
633
|
+
} else {
|
|
634
|
+
ws.sendBinary(chunk, chunk.byteLength >= compressionThreshold)
|
|
610
635
|
}
|
|
611
636
|
}
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
637
|
+
})
|
|
638
|
+
const writer: Socket.Socket["writer"] = Effect.succeed({ write, writeAll })
|
|
639
|
+
|
|
640
|
+
const reader: Socket.Socket["reader"] = Effect.gen(function*() {
|
|
641
|
+
const dispatcher = (yield* Scheduler.Scheduler).makeDispatcher()
|
|
642
|
+
yield* Effect.acquireRelease(semaphore.take(1), () => semaphore.release(1))
|
|
643
|
+
const closeError = ws.data.closeError ?? (ws.readyState >= 2
|
|
644
|
+
? new Socket.SocketError({
|
|
645
|
+
reason: new Socket.SocketCloseError({ code: 1006 })
|
|
646
|
+
})
|
|
647
|
+
: undefined)
|
|
648
|
+
if (closeError !== undefined && ws.data.buffer.length === 0) {
|
|
649
|
+
return yield* closeError
|
|
650
|
+
}
|
|
651
|
+
const scope = yield* Effect.scope
|
|
652
|
+
|
|
653
|
+
type ReadResume = (
|
|
654
|
+
effect: Effect.Effect<Arr.NonEmptyReadonlyArray<Uint8Array | string>, Socket.SocketError>
|
|
655
|
+
) => void
|
|
656
|
+
|
|
657
|
+
let buffer: Array<Uint8Array | string> = ws.data.buffer.splice(0)
|
|
658
|
+
let error: Socket.SocketError | undefined = closeError
|
|
659
|
+
let waiter: ReadResume | undefined
|
|
660
|
+
let flushScheduled = false
|
|
661
|
+
|
|
662
|
+
function takeBuffer(): Arr.NonEmptyReadonlyArray<Uint8Array | string> {
|
|
663
|
+
const chunk = buffer
|
|
664
|
+
buffer = []
|
|
665
|
+
return chunk as unknown as Arr.NonEmptyReadonlyArray<Uint8Array | string>
|
|
666
|
+
}
|
|
667
|
+
function deliver() {
|
|
668
|
+
flushScheduled = false
|
|
669
|
+
if (waiter === undefined || buffer.length === 0) return
|
|
670
|
+
const resumeRead = waiter
|
|
671
|
+
waiter = undefined
|
|
672
|
+
resumeRead(Effect.succeed(takeBuffer()))
|
|
673
|
+
}
|
|
674
|
+
function push(data: Uint8Array | string) {
|
|
675
|
+
buffer.push(data)
|
|
676
|
+
if (waiter !== undefined && !flushScheduled) {
|
|
677
|
+
flushScheduled = true
|
|
678
|
+
dispatcher.scheduleTask(deliver, 0)
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
function fail(err: Socket.SocketError) {
|
|
682
|
+
if (error === undefined) error = err
|
|
683
|
+
if (waiter !== undefined) {
|
|
684
|
+
const resumeRead = waiter
|
|
685
|
+
waiter = undefined
|
|
686
|
+
resumeRead(buffer.length > 0 ? Effect.succeed(takeBuffer()) : Effect.fail(error))
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
ws.data.run = push
|
|
691
|
+
ws.data.onClose = fail
|
|
692
|
+
yield* Scope.addFinalizer(
|
|
693
|
+
scope,
|
|
694
|
+
Effect.suspend(() => {
|
|
695
|
+
// resume a pull blocked in another fiber before detaching
|
|
696
|
+
fail(
|
|
697
|
+
new Socket.SocketError({
|
|
698
|
+
reason: new Socket.SocketCloseError({ code: 1006 })
|
|
699
|
+
})
|
|
700
|
+
)
|
|
701
|
+
ws.data.run = wsDefaultRun
|
|
702
|
+
ws.data.onClose = constVoid
|
|
703
|
+
ws.close(1000)
|
|
704
|
+
return Effect.void
|
|
705
|
+
})
|
|
706
|
+
)
|
|
707
|
+
|
|
708
|
+
return {
|
|
709
|
+
pull: Effect.callback<
|
|
710
|
+
Arr.NonEmptyReadonlyArray<Uint8Array | string>,
|
|
711
|
+
Socket.SocketError
|
|
712
|
+
>((resumeRead) => {
|
|
713
|
+
if (buffer.length > 0) return resumeRead(Effect.succeed(takeBuffer()))
|
|
714
|
+
if (error !== undefined) return resumeRead(Effect.fail(error))
|
|
715
|
+
waiter = resumeRead
|
|
716
|
+
return Effect.sync(() => {
|
|
717
|
+
if (waiter === resumeRead) waiter = undefined
|
|
718
|
+
})
|
|
719
|
+
}),
|
|
720
|
+
upgrade: Socket.SocketUpgradeError.unsupported
|
|
721
|
+
}
|
|
627
722
|
})
|
|
723
|
+
|
|
724
|
+
return Socket.make({ reader, writer })
|
|
628
725
|
}))
|
|
629
726
|
})
|
|
630
727
|
}
|
package/src/BunRedis.ts
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
*
|
|
10
10
|
* @since 4.0.0
|
|
11
11
|
*/
|
|
12
|
-
import { RedisClient,
|
|
12
|
+
import type { RedisClient, RedisOptions } from "bun"
|
|
13
13
|
import * as Config from "effect/Config"
|
|
14
14
|
import * as Context from "effect/Context"
|
|
15
15
|
import * as Deferred from "effect/Deferred"
|
|
@@ -35,6 +35,7 @@ const make = Effect.fnUntraced(function*(
|
|
|
35
35
|
readonly url?: string
|
|
36
36
|
} & RedisOptions
|
|
37
37
|
) {
|
|
38
|
+
const { RedisClient } = yield* Effect.promise(() => import("bun"))
|
|
38
39
|
const scope = yield* Effect.scope
|
|
39
40
|
yield* Scope.addFinalizer(scope, Effect.sync(() => client.close()))
|
|
40
41
|
const client = new RedisClient(options?.url, options)
|
package/src/BunSocket.ts
CHANGED
|
@@ -29,13 +29,16 @@ export * from "@effect/platform-node-shared/NodeSocket"
|
|
|
29
29
|
export const layerWebSocketConstructor: Layer.Layer<
|
|
30
30
|
Socket.WebSocketConstructor
|
|
31
31
|
> = Layer.succeed(Socket.WebSocketConstructor)(
|
|
32
|
-
(url,
|
|
32
|
+
(url, options) =>
|
|
33
|
+
// Bun accepts `WebSocketOptions`, but `bun-types` selects the DOM overload
|
|
34
|
+
// when `lib.dom` is loaded and hides those constructor options.
|
|
35
|
+
new globalThis.WebSocket(url, options as string | Array<string> | undefined)
|
|
33
36
|
)
|
|
34
37
|
|
|
35
38
|
/**
|
|
36
39
|
* Creates a `Socket.Socket` layer for a WebSocket URL using Bun's global
|
|
37
|
-
* `WebSocket` constructor, honoring protocol, open-timeout, and
|
|
38
|
-
*
|
|
40
|
+
* `WebSocket` constructor, honoring protocol, open-timeout, and
|
|
41
|
+
* high-water-mark options.
|
|
39
42
|
*
|
|
40
43
|
* @category layers
|
|
41
44
|
* @since 4.0.0
|
|
@@ -43,9 +46,9 @@ export const layerWebSocketConstructor: Layer.Layer<
|
|
|
43
46
|
export const layerWebSocket: (
|
|
44
47
|
url: string | Effect<string>,
|
|
45
48
|
options?: {
|
|
46
|
-
readonly closeCodeIsError?: ((code: number) => boolean) | undefined
|
|
47
49
|
readonly openTimeout?: Duration.Input | undefined
|
|
48
50
|
readonly protocols?: string | Array<string> | undefined
|
|
51
|
+
readonly highWaterMark?: number | undefined
|
|
49
52
|
} | undefined
|
|
50
53
|
) => Layer.Layer<Socket.Socket, never, never> = flow(
|
|
51
54
|
Socket.makeWebSocket,
|
package/src/BunStream.ts
CHANGED
|
@@ -13,7 +13,7 @@ import * as Arr from "effect/Array"
|
|
|
13
13
|
import * as Cause from "effect/Cause"
|
|
14
14
|
import * as Channel from "effect/Channel"
|
|
15
15
|
import * as Effect from "effect/Effect"
|
|
16
|
-
import type
|
|
16
|
+
import { constVoid, type LazyArg } from "effect/Function"
|
|
17
17
|
import type * as Pull from "effect/Pull"
|
|
18
18
|
import * as Scope from "effect/Scope"
|
|
19
19
|
import * as Stream from "effect/Stream"
|
|
@@ -41,10 +41,19 @@ export const fromReadableStream = <A, E>(
|
|
|
41
41
|
const reader = options.evaluate().getReader()
|
|
42
42
|
yield* Scope.addFinalizer(
|
|
43
43
|
scope,
|
|
44
|
-
options.releaseLockOnEnd
|
|
44
|
+
options.releaseLockOnEnd
|
|
45
|
+
? Effect.sync(() => reader.releaseLock())
|
|
46
|
+
: Effect.promise(() => reader.cancel().catch(constVoid))
|
|
45
47
|
)
|
|
46
48
|
function readMany(): Pull.Pull<Arr.NonEmptyReadonlyArray<A>, E> {
|
|
47
|
-
|
|
49
|
+
let result:
|
|
50
|
+
| Bun.ReadableStreamDefaultReadManyResult<A>
|
|
51
|
+
| Promise<Bun.ReadableStreamDefaultReadManyResult<A>>
|
|
52
|
+
try {
|
|
53
|
+
result = reader.readMany()
|
|
54
|
+
} catch (error) {
|
|
55
|
+
return Effect.fail(options.onError(error))
|
|
56
|
+
}
|
|
48
57
|
if ("then" in result) {
|
|
49
58
|
return Effect.callback<Arr.NonEmptyReadonlyArray<A>, E | Cause.Done>((resume) => {
|
|
50
59
|
result.then((_) => resume(handleResult(_)), (e) => resume(Effect.fail(options.onError(e))))
|