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 +21 -0
- package/README.md +92 -0
- package/package.json +42 -0
- package/src/consumer.js +107 -0
- package/src/decoder.js +20 -0
- package/src/index.js +2 -0
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
|
+
}
|
package/src/consumer.js
ADDED
|
@@ -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