boilstream-consumer 0.1.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 BoilStream
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,92 @@
1
+ # @boilstream/consumer
2
+
3
+ Real-time Arrow data streaming for browsers and Node.js via Server-Sent Events (SSE).
4
+
5
+ Connects to a [BoilStream](https://boilstream.com) SSE endpoint and delivers decoded [Apache Arrow](https://arrow.apache.org/) tables using [flechette](https://github.com/uwdata/flechette). Automatic reconnection with `Last-Event-ID` is handled by `EventSource`.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ npm install @boilstream/consumer
11
+ ```
12
+
13
+ ## Quick Start
14
+
15
+ ### Node.js
16
+
17
+ ```js
18
+ import { BoilStreamConsumer } from "@boilstream/consumer";
19
+ import EventSource from "eventsource";
20
+
21
+ const consumer = new BoilStreamConsumer("https://host:8443/stream/<token>", {
22
+ onSchema: (schema) => console.log("Fields:", schema.fields.map(f => f.name)),
23
+ onBatch: (table, seq) => console.log(`Batch ${seq}: ${table.numRows} rows`),
24
+ onError: (err) => console.error(err),
25
+ EventSource, // required in Node.js
26
+ });
27
+
28
+ // Later: disconnect
29
+ consumer.close();
30
+ ```
31
+
32
+ ### Browser
33
+
34
+ ```js
35
+ import { BoilStreamConsumer } from "@boilstream/consumer";
36
+
37
+ const consumer = new BoilStreamConsumer("https://host:8443/stream/<token>", {
38
+ onSchema: (schema) => console.log("Fields:", schema.fields.map(f => f.name)),
39
+ onBatch: (table, seq) => console.log(`Batch ${seq}: ${table.numRows} rows`),
40
+ onError: (err) => console.error(err),
41
+ // Native EventSource is used automatically in browsers
42
+ });
43
+ ```
44
+
45
+ See [`examples/browser-test.html`](examples/browser-test.html) for a standalone browser demo.
46
+
47
+ ## API
48
+
49
+ ### `new BoilStreamConsumer(url, options)`
50
+
51
+ | Parameter | Type | Description |
52
+ |-----------|------|-------------|
53
+ | `url` | `string` | SSE endpoint URL (`https://host/stream/<token>`) |
54
+ | `options.onSchema` | `Function` | Called with flechette `Schema` on schema events |
55
+ | `options.onBatch` | `Function` | Called with `(Table, batchSeq)` on batch events |
56
+ | `options.onHeartbeat` | `Function` | Called on heartbeat events |
57
+ | `options.onError` | `Function` | Called with error on connection/decode errors |
58
+ | `options.EventSource` | `class` | EventSource implementation (required in Node.js) |
59
+
60
+ ### `consumer.schema`
61
+
62
+ Current schema (`null` until first schema event).
63
+
64
+ ### `consumer.close()`
65
+
66
+ Disconnect from the SSE stream.
67
+
68
+ ### `decodeArrowIPC(base64Data)`
69
+
70
+ Low-level utility: decode a base64-encoded Arrow IPC stream into a flechette `Table`.
71
+
72
+ ```js
73
+ import { decodeArrowIPC } from "@boilstream/consumer";
74
+ const table = decodeArrowIPC(base64String);
75
+ ```
76
+
77
+ ## SSE Event Types
78
+
79
+ | Event | Data | Description |
80
+ |-------|------|-------------|
81
+ | `schema` | Base64 Arrow IPC | Table schema (sent on connect and schema changes) |
82
+ | `batch` | Base64 Arrow IPC | Record batch with `lastEventId` for resume |
83
+ | `heartbeat` | empty | Keep-alive signal |
84
+
85
+ ## Dependencies
86
+
87
+ - [`@uwdata/flechette`](https://github.com/uwdata/flechette) — Arrow IPC decoding
88
+ - [`eventsource`](https://github.com/EventSource/eventsource) — Node.js EventSource polyfill
89
+
90
+ ## License
91
+
92
+ MIT
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "boilstream-consumer",
3
+ "version": "0.1.0",
4
+ "description": "BoilStream SSE consumer SDK — real-time Arrow data streaming for browsers and Node.js",
5
+ "type": "module",
6
+ "main": "src/index.js",
7
+ "exports": {
8
+ ".": "./src/index.js"
9
+ },
10
+ "files": [
11
+ "src/"
12
+ ],
13
+ "scripts": {
14
+ "test": "node test/consumer.test.js",
15
+ "test:browser": "node test/browser.test.js"
16
+ },
17
+ "keywords": [
18
+ "boilstream",
19
+ "sse",
20
+ "arrow",
21
+ "streaming",
22
+ "real-time",
23
+ "apache-arrow",
24
+ "eventsource",
25
+ "ipc",
26
+ "flechette"
27
+ ],
28
+ "author": "BoilStream",
29
+ "license": "MIT",
30
+ "homepage": "https://github.com/dforsber/boilstream-consumer-js",
31
+ "repository": {
32
+ "type": "git",
33
+ "url": "https://github.com/dforsber/boilstream-consumer-js.git"
34
+ },
35
+ "dependencies": {
36
+ "@uwdata/flechette": "^2.2.0",
37
+ "eventsource": "^3.0.0"
38
+ },
39
+ "devDependencies": {
40
+ "playwright": "^1.50.0"
41
+ }
42
+ }
@@ -0,0 +1,107 @@
1
+ /**
2
+ * BoilStream SSE Consumer — real-time Arrow data streaming.
3
+ *
4
+ * Connects to a BoilStream SSE endpoint and decodes Arrow IPC events
5
+ * using flechette. Works in browsers (native EventSource) and Node.js
6
+ * (pass the `eventsource` polyfill).
7
+ *
8
+ * Reconnection with Last-Event-ID is handled automatically by EventSource.
9
+ *
10
+ * @example
11
+ * import { BoilStreamConsumer } from "@boilstream/consumer";
12
+ * import EventSource from "eventsource"; // Node.js only
13
+ *
14
+ * const consumer = new BoilStreamConsumer("https://host:8443/stream/{token}", {
15
+ * onSchema: (schema) => console.log("Schema:", schema),
16
+ * onBatch: (table, batchSeq) => console.log(`Batch ${batchSeq}: ${table.numRows} rows`),
17
+ * onHeartbeat: () => {},
18
+ * onError: (err) => console.error(err),
19
+ * EventSource, // pass polyfill for Node.js; omit in browsers
20
+ * });
21
+ *
22
+ * // Later: disconnect
23
+ * consumer.close();
24
+ */
25
+
26
+ import { decodeArrowIPC } from "./decoder.js";
27
+
28
+ export class BoilStreamConsumer {
29
+ #url;
30
+ #callbacks;
31
+ #EventSourceImpl;
32
+ #es;
33
+ #schema;
34
+
35
+ /**
36
+ * @param {string} url - SSE endpoint URL (e.g. "https://host/stream/{token}")
37
+ * @param {Object} options
38
+ * @param {Function} [options.onSchema] - Called with flechette schema on schema events
39
+ * @param {Function} [options.onBatch] - Called with (flechette Table, batchSeq) on batch events
40
+ * @param {Function} [options.onHeartbeat] - Called on heartbeat events
41
+ * @param {Function} [options.onError] - Called with error on connection/decode errors
42
+ * @param {typeof EventSource} [options.EventSource] - EventSource implementation (required in Node.js)
43
+ */
44
+ constructor(url, options = {}) {
45
+ this.#url = url;
46
+ this.#callbacks = {
47
+ onSchema: options.onSchema,
48
+ onBatch: options.onBatch,
49
+ onHeartbeat: options.onHeartbeat,
50
+ onError: options.onError,
51
+ };
52
+ this.#EventSourceImpl = options.EventSource || globalThis.EventSource;
53
+ this.#schema = null;
54
+
55
+ if (!this.#EventSourceImpl) {
56
+ throw new Error(
57
+ "EventSource is not available. In Node.js, install and pass the `eventsource` package.",
58
+ );
59
+ }
60
+
61
+ this.#connect();
62
+ }
63
+
64
+ /** Current schema (null until first schema event). */
65
+ get schema() {
66
+ return this.#schema;
67
+ }
68
+
69
+ #connect() {
70
+ this.#es = new this.#EventSourceImpl(this.#url);
71
+
72
+ this.#es.addEventListener("schema", (event) => {
73
+ try {
74
+ const table = decodeArrowIPC(event.data);
75
+ this.#schema = table.schema;
76
+ this.#callbacks.onSchema?.(table.schema);
77
+ } catch (err) {
78
+ this.#callbacks.onError?.(err);
79
+ }
80
+ });
81
+
82
+ this.#es.addEventListener("batch", (event) => {
83
+ try {
84
+ const batchSeq = Number(event.lastEventId);
85
+ const table = decodeArrowIPC(event.data);
86
+ this.#callbacks.onBatch?.(table, batchSeq);
87
+ } catch (err) {
88
+ this.#callbacks.onError?.(err);
89
+ }
90
+ });
91
+
92
+ this.#es.addEventListener("heartbeat", () => {
93
+ this.#callbacks.onHeartbeat?.();
94
+ });
95
+
96
+ this.#es.onerror = (err) => {
97
+ this.#callbacks.onError?.(err);
98
+ // EventSource auto-reconnects with Last-Event-ID — no manual logic needed
99
+ };
100
+ }
101
+
102
+ /** Disconnect from the SSE stream. */
103
+ close() {
104
+ this.#es?.close();
105
+ this.#es = null;
106
+ }
107
+ }
package/src/decoder.js ADDED
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Arrow IPC decoder — base64 SSE data to flechette Table.
3
+ *
4
+ * Works in both Node.js and browsers.
5
+ */
6
+
7
+ import { tableFromIPC } from "@uwdata/flechette";
8
+
9
+ /**
10
+ * Decode base64-encoded Arrow IPC stream into a flechette Table.
11
+ * @param {string} base64Data - Base64-encoded Arrow IPC stream bytes
12
+ * @returns {import("@uwdata/flechette").Table} Decoded Arrow table
13
+ */
14
+ export function decodeArrowIPC(base64Data) {
15
+ const bytes =
16
+ typeof Buffer !== "undefined"
17
+ ? new Uint8Array(Buffer.from(base64Data, "base64"))
18
+ : Uint8Array.from(atob(base64Data), (c) => c.charCodeAt(0));
19
+ return tableFromIPC(bytes);
20
+ }
package/src/index.js ADDED
@@ -0,0 +1,2 @@
1
+ export { BoilStreamConsumer } from "./consumer.js";
2
+ export { decodeArrowIPC } from "./decoder.js";