diff options
author | 2023-06-20 18:57:37 -0700 | |
---|---|---|
committer | 2023-06-20 18:57:37 -0700 | |
commit | adb451eec6b8286a4ee18b16b5b87644d5ef3020 (patch) | |
tree | f1bca25150bf1c9ad9b1ac2d5703b71e5953fc51 /docs/api | |
parent | 62639081c1b8ffef0dbf8d729d9d21e10ed41734 (diff) | |
download | bun-adb451eec6b8286a4ee18b16b5b87644d5ef3020.tar.gz bun-adb451eec6b8286a4ee18b16b5b87644d5ef3020.tar.zst bun-adb451eec6b8286a4ee18b16b5b87644d5ef3020.zip |
Docs for DOM testing and FileSink (#3330)
* Update websocket docs & jsdoc
* Add info about user-specific data in ws
* Document FileSink
* Docs for happydom test
* Updates
Diffstat (limited to 'docs/api')
-rw-r--r-- | docs/api/file-io.md | 57 | ||||
-rw-r--r-- | docs/api/websockets.md | 113 |
2 files changed, 122 insertions, 48 deletions
diff --git a/docs/api/file-io.md b/docs/api/file-io.md index 07336d071..effc57580 100644 --- a/docs/api/file-io.md +++ b/docs/api/file-io.md @@ -202,6 +202,53 @@ const response = await fetch("https://bun.sh"); await Bun.write("index.html", response); ``` +## Incremental writing with `FileSink` + +Bun provides a native incremental file writing API called `FileSink`. To retrieve a `FileSink` instance from a `BunFile`: + +```ts +const file = Bun.file("output.txt"); +const writer = file.writer(); +``` + +To incrementally write to the file, call `.write()`. + +```ts +const file = Bun.file("output.txt"); +const writer = file.writer(); + +writer.write("it was the best of times\n"); +writer.write("it was the worst of times\n"); +``` + +These chunks will be buffered internally. To flush the buffer to disk, use `.flush()`. This returns the number of flushed bytes. + +```ts +writer.flush(); // write buffer to disk +``` + +The buffer will also auto-flush when the `FileSink`'s _high water mark_ is reached; that is, when its internal buffer is full. This value can be configured. + +```ts +const file = Bun.file("output.txt"); +const writer = file.writer({ highWaterMark: 1024 * 1024 }); // 1MB +``` + +To flush the buffer and close the file: + +```ts +writer.end(); +``` + +Note that, by default, the `bun` process will stay alive until this `FileSink` is explicitly closed with `.end()`. To opt out of this behavior, you can "unref" the instance. + +```ts +writer.unref(); + +// to "re-ref" it later +writer.ref(); +``` + ## Benchmarks The following is a 3-line implementation of the Linux `cat` command. @@ -250,5 +297,15 @@ interface BunFile { stream(): Promise<ReadableStream>; arrayBuffer(): Promise<ArrayBuffer>; json(): Promise<any>; + writer(params: { highWaterMark?: number }): FileSink; +} + +export interface FileSink { + write(chunk: string | ArrayBufferView | ArrayBuffer | SharedArrayBuffer): number; + flush(): number | Promise<number>; + end(error?: Error): number | Promise<number>; + start(options?: { highWaterMark?: number }): void; + ref(): void; + unref(): void; } ``` diff --git a/docs/api/websockets.md b/docs/api/websockets.md index d1e1d3831..b8b7f7a8e 100644 --- a/docs/api/websockets.md +++ b/docs/api/websockets.md @@ -12,41 +12,7 @@ Internally Bun's WebSocket implementation is built on [uWebSockets](https://github.com/uNetworking/uWebSockets). {% /callout %} -## Connect to a WebSocket server - -To connect to an external socket server, create an instance of `WebSocket` with the constructor. - -```ts -const socket = new WebSocket("ws://localhost:3000"); -``` - -Bun supports setting custom headers. This is a Bun-specific extension of the `WebSocket` standard. _This will not work in browsers._ - -```ts -const socket = new WebSocket("ws://localhost:3000", { - headers: { - // custom headers - }, -}); -``` - -To add event listeners to the socket: - -```ts -// message is received -socket.addEventListener("message", event => {}); - -// socket opened -socket.addEventListener("open", event => {}); - -// socket closed -socket.addEventListener("close", event => {}); - -// error handler -socket.addEventListener("error", event => {}); -``` - -## Create a WebSocket server +## Start a WebSocket server Below is a simple WebSocket server built with `Bun.serve`, in which all incoming requests are [upgraded](https://developer.mozilla.org/en-US/docs/Web/HTTP/Protocol_upgrade_mechanism) to WebSocket connections in the `fetch` handler. The socket handlers are declared in the `websocket` parameter. @@ -109,7 +75,7 @@ Bun.serve({ }); ``` -## Sending messages +### Sending messages Each `ServerWebSocket` instance has a `.send()` method for sending messages to the client. It supports a range of input types. @@ -119,7 +85,7 @@ ws.send(response.arrayBuffer()); // ArrayBuffer ws.send(new Uint8Array([1, 2, 3])); // TypedArray | DataView ``` -## Headers +### Headers Once the upgrade succeeds, Bun will send a `101 Switching Protocols` response per the [spec](https://developer.mozilla.org/en-US/docs/Web/HTTP/Protocol_upgrade_mechanism). Additional `headers` can be attched to this `Response` in the call to `server.upgrade()`. @@ -137,7 +103,7 @@ Bun.serve({ }); ``` -## Contextual data +### Contextual data Contextual `data` can be attached to a new WebSocket in the `.upgrade()` call. This data is made available on the `ws.data` property inside the WebSocket handlers. @@ -145,14 +111,16 @@ Contextual `data` can be attached to a new WebSocket in the `.upgrade()` call. T type WebSocketData = { createdAt: number; channelId: string; + authToken: string; }; // TypeScript: specify the type of `data` Bun.serve<WebSocketData>({ fetch(req, server) { + // use a library to parse cookies const cookies = parseCookies(req.headers.get("Cookie")); server.upgrade(req, { - // TS: this object must conform to WebSocketData + // this object must conform to WebSocketData data: { createdAt: Date.now(), channelId: new URL(req.url).searchParams.get("channelId"), @@ -165,10 +133,12 @@ Bun.serve<WebSocketData>({ websocket: { // handler called when a message is received async message(ws, message) { - ws.data; // WebSocketData + const user = getUserFromToken(ws.data.authToken); + await saveMessageToDatabase({ channel: ws.data.channelId, message: String(message), + userId: user.id, }); }, }, @@ -185,9 +155,11 @@ socket.addEventListener("message", event => { }) ``` -The cookies that are currently set on the page will be sent with the WebSocket upgrade request and available on `req.headers` in the `fetch` handler. Parse these cookies to determine the identity of the connecting user and set the value of `data` accordingly. +{% callout %} +**Identifying users** — The cookies that are currently set on the page will be sent with the WebSocket upgrade request and available on `req.headers` in the `fetch` handler. Parse these cookies to determine the identity of the connecting user and set the value of `data` accordingly. +{% /callout %} -## Pub/Sub +### Pub/Sub Bun's `ServerWebSocket` implementation implements a native publish-subscribe API for topic-based broadcasting. Individual sockets can `.subscribe()` to a topic (specified with a string identifier) and `.publish()` messages to all other subscribers to that topic. This topic-based broadcast API is similar to [MQTT](https://en.wikipedia.org/wiki/MQTT) and [Redis Pub/Sub](https://redis.io/topics/pubsub). @@ -199,7 +171,9 @@ const server = Bun.serve<{ username: string }>({ console.log(`upgrade!`); const username = getUsernameFromReq(req); const success = server.upgrade(req, { data: { username } }); - return success ? undefined : new Response("WebSocket upgrade error", { status: 400 }); + return success + ? undefined + : new Response("WebSocket upgrade error", { status: 400 }); } return new Response("Hello world"); @@ -226,9 +200,9 @@ const server = Bun.serve<{ username: string }>({ console.log(`Listening on ${server.hostname}:${server.port}`); ``` -Calling `.publish(data)` will send the message to all subscribers of a topic (excluding the socket that called `.publish()`). +Calling `.publish(data)` will send the message to all subscribers of a topic _except_ the socket that called `.publish()`. -## Compression +### Compression Per-message [compression](https://websockets.readthedocs.io/en/stable/topics/compression.html) can be enabled with the `perMessageDeflate` parameter. @@ -250,7 +224,7 @@ ws.send("Hello world", true); For fine-grained control over compression characteristics, refer to the [Reference](#reference). -## Backpressure +### Backpressure The `.send(message)` method of `ServerWebSocket` returns a `number` indicating the result of the operation. @@ -260,6 +234,42 @@ The `.send(message)` method of `ServerWebSocket` returns a `number` indicating t This gives you better control over backpressure in your server. +## Connect to a `Websocket` server + +To connect to an external socket server, either from a browser or from Bun, create an instance of `WebSocket` with the constructor. + +```ts +const socket = new WebSocket("ws://localhost:3000"); +``` + +In browsers, the cookies that are currently set on the page will be sent with the WebSocket upgrade request. This is a standard feature of the `WebSocket` API. + +For convenience, Bun lets you setting custom headers directly in the constructor. This is a Bun-specific extension of the `WebSocket` standard. _This will not work in browsers._ + +```ts +const socket = new WebSocket("ws://localhost:3000", { + headers: { + // custom headers + }, +}); +``` + +To add event listeners to the socket: + +```ts +// message is received +socket.addEventListener("message", event => {}); + +// socket opened +socket.addEventListener("open", event => {}); + +// socket closed +socket.addEventListener("close", event => {}); + +// error handler +socket.addEventListener("error", event => {}); +``` + ## Reference ```ts @@ -267,7 +277,10 @@ namespace Bun { export function serve(params: { fetch: (req: Request, server: Server) => Response | Promise<Response>; websocket?: { - message: (ws: ServerWebSocket, message: string | ArrayBuffer | Uint8Array) => void; + message: ( + ws: ServerWebSocket, + message: string | ArrayBuffer | Uint8Array, + ) => void; open?: (ws: ServerWebSocket) => void; close?: (ws: ServerWebSocket) => void; error?: (ws: ServerWebSocket, error: Error) => void; @@ -297,7 +310,11 @@ type Compressor = interface Server { pendingWebsockets: number; - publish(topic: string, data: string | ArrayBufferView | ArrayBuffer, compress?: boolean): number; + publish( + topic: string, + data: string | ArrayBufferView | ArrayBuffer, + compress?: boolean, + ): number; upgrade( req: Request, options?: { |