@databricks/zerobus-ingest-sdk 1.0.2 → 1.2.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/Cargo.lock +189 -115
- package/Cargo.toml +14 -21
- package/LICENSE +1 -1
- package/README.md +151 -208
- package/index.d.ts +82 -38
- package/package.json +30 -14
- package/src/headers_provider.ts +24 -36
- package/src/lib.rs +410 -310
- package/utils/descriptor.d.ts +32 -0
- package/utils/descriptor.js +55 -0
- package/utils/descriptor.ts +2 -2
package/README.md
CHANGED
|
@@ -28,7 +28,7 @@ The Databricks Zerobus Ingest SDK for TypeScript provides a high-performance cli
|
|
|
28
28
|
- **High-throughput ingestion**: Optimized for high-volume data ingestion with native Rust implementation
|
|
29
29
|
- **Automatic recovery**: Built-in retry and recovery mechanisms for transient failures
|
|
30
30
|
- **Flexible configuration**: Customizable stream behavior and timeouts
|
|
31
|
-
- **Multiple serialization formats**: Support for JSON and
|
|
31
|
+
- **Multiple serialization formats**: Support for JSON, Protocol Buffers, and Arrow Flight (Beta) with optional LZ4 / ZSTD compression
|
|
32
32
|
- **Type widening**: Accept high-level types (plain objects, protobuf messages) or low-level types (strings, buffers) - automatically handles serialization
|
|
33
33
|
- **Batch ingestion**: Ingest multiple records with a single acknowledgment for higher throughput
|
|
34
34
|
- **OAuth 2.0 authentication**: Secure authentication with client credentials
|
|
@@ -42,21 +42,14 @@ The Databricks Zerobus Ingest SDK for TypeScript provides a high-performance cli
|
|
|
42
42
|
- **Node.js**: >= 16
|
|
43
43
|
- **Databricks workspace** with Zerobus access enabled
|
|
44
44
|
|
|
45
|
-
### Build Requirements
|
|
45
|
+
### Source Build Requirements
|
|
46
46
|
|
|
47
47
|
- **Rust toolchain**: 1.70 or higher - [Install Rust](https://rustup.rs/)
|
|
48
48
|
- **Cargo**: Included with Rust
|
|
49
|
+
- Platform C/C++ build tools
|
|
49
50
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
These will be installed automatically:
|
|
53
|
-
|
|
54
|
-
```json
|
|
55
|
-
{
|
|
56
|
-
"@napi-rs/cli": "^2.18.4",
|
|
57
|
-
"napi-build": "^0.3.3"
|
|
58
|
-
}
|
|
59
|
-
```
|
|
51
|
+
You only need these source-build tools when npm cannot use a pre-built native
|
|
52
|
+
package for your platform, or when developing the SDK from this repository.
|
|
60
53
|
|
|
61
54
|
## Quick Start User Guide
|
|
62
55
|
|
|
@@ -66,110 +59,30 @@ Before using the SDK, you need a Databricks workspace URL, a Delta table, and a
|
|
|
66
59
|
|
|
67
60
|
### Installation
|
|
68
61
|
|
|
69
|
-
#### Prerequisites
|
|
70
|
-
|
|
71
|
-
Before installing the SDK, ensure you have the required tools:
|
|
72
|
-
|
|
73
|
-
**1. Node.js >= 16**
|
|
74
|
-
|
|
75
|
-
Check if Node.js is installed:
|
|
76
62
|
```bash
|
|
77
|
-
|
|
63
|
+
npm install @databricks/zerobus-ingest-sdk
|
|
78
64
|
```
|
|
79
65
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
**2. Rust Toolchain (1.70+)**
|
|
66
|
+
On supported platforms, npm installs the TypeScript package and the matching
|
|
67
|
+
pre-built native binary package automatically.
|
|
83
68
|
|
|
84
|
-
|
|
69
|
+
#### Local Development From Source
|
|
85
70
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
|
|
89
|
-
```
|
|
90
|
-
|
|
91
|
-
Follow the prompts (typically just press Enter to accept defaults).
|
|
92
|
-
|
|
93
|
-
**On Windows:**
|
|
94
|
-
|
|
95
|
-
Download and run the installer from [rustup.rs](https://rustup.rs/), or use:
|
|
96
|
-
```powershell
|
|
97
|
-
# Using winget
|
|
98
|
-
winget install Rustlang.Rustup
|
|
99
|
-
|
|
100
|
-
# Or download from https://rustup.rs/
|
|
101
|
-
```
|
|
102
|
-
|
|
103
|
-
**Verify Installation:**
|
|
104
|
-
```bash
|
|
105
|
-
rustc --version
|
|
106
|
-
cargo --version
|
|
107
|
-
```
|
|
71
|
+
Clone and build from source only when modifying this SDK or when your platform
|
|
72
|
+
does not have a pre-built native binary:
|
|
108
73
|
|
|
109
|
-
You should see version 1.70 or higher. If the commands aren't found, restart your terminal or add Rust to your PATH:
|
|
110
74
|
```bash
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
# Restart your terminal
|
|
75
|
+
git clone https://github.com/databricks/zerobus-sdk.git
|
|
76
|
+
cd zerobus-sdk/typescript
|
|
77
|
+
npm install
|
|
78
|
+
npm run build
|
|
116
79
|
```
|
|
117
80
|
|
|
118
|
-
**Additional Platform Requirements:**
|
|
119
|
-
|
|
120
|
-
- **Linux**: Build essentials
|
|
121
|
-
```bash
|
|
122
|
-
# Ubuntu/Debian
|
|
123
|
-
sudo apt-get install build-essential
|
|
124
|
-
|
|
125
|
-
# CentOS/RHEL
|
|
126
|
-
sudo yum groupinstall "Development Tools"
|
|
127
|
-
```
|
|
128
|
-
|
|
129
|
-
- **macOS**: Xcode Command Line Tools
|
|
130
|
-
```bash
|
|
131
|
-
xcode-select --install
|
|
132
|
-
```
|
|
133
|
-
|
|
134
|
-
- **Windows**: Visual Studio Build Tools
|
|
135
|
-
- Install [Visual Studio Build Tools](https://visualstudio.microsoft.com/downloads/#build-tools-for-visual-studio-2022)
|
|
136
|
-
- During installation, select "Desktop development with C++"
|
|
137
|
-
|
|
138
|
-
#### Installation Steps
|
|
139
|
-
|
|
140
|
-
**Note for macOS users**: Pre-built binaries are not available. The package will automatically build from source during `npm install`. Ensure you have Rust toolchain and Xcode Command Line Tools installed (see prerequisites above).
|
|
141
|
-
|
|
142
|
-
1. Clone the repository:
|
|
143
|
-
```bash
|
|
144
|
-
git clone https://github.com/databricks/zerobus-sdk.git
|
|
145
|
-
cd zerobus-sdk/ts
|
|
146
|
-
```
|
|
147
|
-
|
|
148
|
-
2. Install dependencies:
|
|
149
|
-
```bash
|
|
150
|
-
npm install
|
|
151
|
-
```
|
|
152
|
-
|
|
153
|
-
3. Build the native addon:
|
|
154
|
-
```bash
|
|
155
|
-
npm run build
|
|
156
|
-
```
|
|
157
|
-
|
|
158
|
-
This will compile the Rust code into a native Node.js addon (`.node` file) for your platform.
|
|
159
|
-
|
|
160
|
-
4. Verify the build:
|
|
161
|
-
```bash
|
|
162
|
-
# You should see a .node file
|
|
163
|
-
ls -la *.node
|
|
164
|
-
```
|
|
165
|
-
|
|
166
|
-
5. The SDK is now ready to use! You can:
|
|
167
|
-
- Use it directly in this directory for examples
|
|
168
|
-
- Link it globally: `npm link`
|
|
169
|
-
- Or copy it into your project's `node_modules`
|
|
170
|
-
|
|
171
81
|
**Troubleshooting:**
|
|
172
82
|
|
|
83
|
+
- **Unsupported platform or source build requested**: Install Rust 1.70+,
|
|
84
|
+
Cargo, and your platform C/C++ build tools, clone this repository, and run
|
|
85
|
+
`npm install` followed by `npm run build` from `zerobus-sdk/typescript`
|
|
173
86
|
- **"rustc: command not found"**: Restart your terminal after installing Rust
|
|
174
87
|
- **Build fails on Windows**: Ensure Visual Studio Build Tools are installed with C++ support
|
|
175
88
|
- **Build fails on Linux**: Install build-essential or equivalent package
|
|
@@ -184,6 +97,10 @@ The SDK supports two serialization formats. **Protocol Buffers is the default**
|
|
|
184
97
|
|
|
185
98
|
> **Note:** If you don't specify `recordType`, the SDK will use Protocol Buffers by default. To use JSON, explicitly set `recordType: RecordType.Json`.
|
|
186
99
|
|
|
100
|
+
### Acknowledgments and throughput
|
|
101
|
+
|
|
102
|
+
Ingestion is asynchronous. `ingestRecordOffset()` (and `ingestRecordsOffset()`) resolves as soon as the record is queued; the SDK sends it and tracks its acknowledgment in the background. To confirm records are durably committed, call `flush()` — it resolves once everything queued so far is acknowledged. The idiomatic flow is **ingest in a loop, then `flush()`** (once for a bounded batch, or periodically for a long-running stream). Each ingest also returns the record's offset, and `waitForOffset(offset)` resolves when that offset is acknowledged — handy when a specific record must be confirmed before continuing (acks are ordered, so waiting on the last offset confirms the whole run). Just avoid calling `waitForOffset()` after every record in a tight loop, since that limits throughput to one record per round-trip. The examples below follow this pattern.
|
|
103
|
+
|
|
187
104
|
### Option 1: Using JSON (Quick Start)
|
|
188
105
|
|
|
189
106
|
JSON mode is the simplest way to get started. You don't need to define or compile protobuf schemas, but you must explicitly specify `RecordType.Json`.
|
|
@@ -191,12 +108,13 @@ JSON mode is the simplest way to get started. You don't need to define or compil
|
|
|
191
108
|
```typescript
|
|
192
109
|
import { ZerobusSdk, RecordType } from '@databricks/zerobus-ingest-sdk';
|
|
193
110
|
|
|
111
|
+
async function main(): Promise<void> {
|
|
194
112
|
// Configuration
|
|
195
113
|
// For AWS:
|
|
196
114
|
const zerobusEndpoint = 'https://<workspace-id>.zerobus.<region>.cloud.databricks.com';
|
|
197
115
|
const workspaceUrl = 'https://<workspace-name>.cloud.databricks.com';
|
|
198
116
|
// For Azure:
|
|
199
|
-
// const zerobusEndpoint = '
|
|
117
|
+
// const zerobusEndpoint = 'https://<workspace-id>.zerobus.<region>.azuredatabricks.net';
|
|
200
118
|
// const workspaceUrl = 'https://<workspace-name>.azuredatabricks.net';
|
|
201
119
|
|
|
202
120
|
const tableName = 'main.default.air_quality';
|
|
@@ -225,8 +143,6 @@ const stream = await sdk.createStream(
|
|
|
225
143
|
);
|
|
226
144
|
|
|
227
145
|
try {
|
|
228
|
-
let lastOffset: bigint;
|
|
229
|
-
|
|
230
146
|
// Send all records
|
|
231
147
|
for (let i = 0; i < 100; i++) {
|
|
232
148
|
const record = {
|
|
@@ -235,16 +151,23 @@ try {
|
|
|
235
151
|
humidity: 50 + (i % 40)
|
|
236
152
|
};
|
|
237
153
|
|
|
238
|
-
//
|
|
239
|
-
|
|
154
|
+
// Queue the record; do not wait for its acknowledgement here
|
|
155
|
+
await stream.ingestRecordOffset(record);
|
|
240
156
|
}
|
|
241
157
|
|
|
242
158
|
// Wait for all records to be acknowledged
|
|
243
|
-
await stream.
|
|
159
|
+
await stream.flush();
|
|
244
160
|
console.log('Successfully ingested 100 records!');
|
|
245
161
|
} finally {
|
|
246
162
|
await stream.close();
|
|
247
163
|
}
|
|
164
|
+
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
main().catch((error) => {
|
|
168
|
+
console.error('Fatal error:', error);
|
|
169
|
+
process.exitCode = 1;
|
|
170
|
+
});
|
|
248
171
|
```
|
|
249
172
|
|
|
250
173
|
### Option 2: Using Protocol Buffers (Default, Recommended)
|
|
@@ -256,7 +179,15 @@ Protocol Buffers is the default serialization format and provides efficient bina
|
|
|
256
179
|
Before starting, ensure you have:
|
|
257
180
|
|
|
258
181
|
1. **Protocol Buffer Compiler (`protoc`)** - Required for generating descriptor files
|
|
259
|
-
2. **protobufjs**
|
|
182
|
+
2. **protobufjs** - Required at runtime by your generated Protocol Buffer code
|
|
183
|
+
3. **protobufjs-cli** - Required during development to generate JavaScript and type declarations
|
|
184
|
+
|
|
185
|
+
Install the JavaScript runtime and code-generation tools in your application:
|
|
186
|
+
|
|
187
|
+
```bash
|
|
188
|
+
npm install protobufjs
|
|
189
|
+
npm install --save-dev protobufjs-cli
|
|
190
|
+
```
|
|
260
191
|
|
|
261
192
|
#### Step 1: Install Protocol Buffer Compiler
|
|
262
193
|
|
|
@@ -297,7 +228,9 @@ protoc --version
|
|
|
297
228
|
|
|
298
229
|
#### Step 2: Define Your Protocol Buffer Schema
|
|
299
230
|
|
|
300
|
-
|
|
231
|
+
Create `schemas/air_quality.proto` in your application with the following
|
|
232
|
+
example schema. Also create an `examples/generated` directory for the generated
|
|
233
|
+
JavaScript and type declarations:
|
|
301
234
|
|
|
302
235
|
```protobuf
|
|
303
236
|
syntax = "proto2";
|
|
@@ -317,13 +250,8 @@ message AirQuality {
|
|
|
317
250
|
Generate TypeScript code from your proto schema:
|
|
318
251
|
|
|
319
252
|
```bash
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
This runs:
|
|
324
|
-
```bash
|
|
325
|
-
pbjs -t static-module -w commonjs -o examples/generated/air_quality.js schemas/air_quality.proto
|
|
326
|
-
pbts -o examples/generated/air_quality.d.ts examples/generated/air_quality.js
|
|
253
|
+
npx pbjs -t static-module -w commonjs -o examples/generated/air_quality.js schemas/air_quality.proto
|
|
254
|
+
npx pbts -o examples/generated/air_quality.d.ts examples/generated/air_quality.js
|
|
327
255
|
```
|
|
328
256
|
|
|
329
257
|
**Output:**
|
|
@@ -353,8 +281,9 @@ That's it! The SDK will automatically extract the message descriptor from this f
|
|
|
353
281
|
```typescript
|
|
354
282
|
import { ZerobusSdk, RecordType } from '@databricks/zerobus-ingest-sdk';
|
|
355
283
|
import * as airQuality from './examples/generated/air_quality';
|
|
356
|
-
import { loadDescriptorProto } from '@databricks/zerobus-ingest-sdk/utils/descriptor';
|
|
284
|
+
import { loadDescriptorProto } from '@databricks/zerobus-ingest-sdk/utils/descriptor.js';
|
|
357
285
|
|
|
286
|
+
async function main(): Promise<void> {
|
|
358
287
|
// Configuration
|
|
359
288
|
const zerobusEndpoint = 'https://<workspace-id>.zerobus.<region>.cloud.databricks.com';
|
|
360
289
|
const workspaceUrl = 'https://<workspace-name>.cloud.databricks.com';
|
|
@@ -390,26 +319,32 @@ const stream = await sdk.createStream(tableProperties, clientId, clientSecret, o
|
|
|
390
319
|
|
|
391
320
|
try {
|
|
392
321
|
const AirQuality = airQuality.examples.AirQuality;
|
|
393
|
-
let lastOffset: bigint;
|
|
394
322
|
|
|
395
323
|
// Send all records
|
|
396
324
|
for (let i = 0; i < 100; i++) {
|
|
397
325
|
const record = AirQuality.create({
|
|
398
|
-
|
|
326
|
+
deviceName: `sensor-${i}`,
|
|
399
327
|
temp: 20 + i,
|
|
400
328
|
humidity: 50 + i
|
|
401
329
|
});
|
|
402
330
|
|
|
403
|
-
//
|
|
404
|
-
|
|
331
|
+
// Queue the record; do not wait for its acknowledgement here
|
|
332
|
+
await stream.ingestRecordOffset(record);
|
|
405
333
|
}
|
|
406
334
|
|
|
407
335
|
// Wait for all records to be acknowledged
|
|
408
|
-
await stream.
|
|
336
|
+
await stream.flush();
|
|
409
337
|
console.log('Successfully ingested 100 records!');
|
|
410
338
|
} finally {
|
|
411
339
|
await stream.close();
|
|
412
340
|
}
|
|
341
|
+
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
main().catch((error) => {
|
|
345
|
+
console.error('Fatal error:', error);
|
|
346
|
+
process.exitCode = 1;
|
|
347
|
+
});
|
|
413
348
|
```
|
|
414
349
|
|
|
415
350
|
#### Type Mapping: Delta ↔ Protocol Buffers
|
|
@@ -468,24 +403,16 @@ message NestedData {
|
|
|
468
403
|
EOF
|
|
469
404
|
```
|
|
470
405
|
|
|
471
|
-
2. **
|
|
472
|
-
```json
|
|
473
|
-
{
|
|
474
|
-
"scripts": {
|
|
475
|
-
"build:proto:myschema": "pbjs -t static-module -w commonjs -o examples/generated/my_schema.js schemas/my_schema.proto && pbts -o examples/generated/my_schema.d.ts examples/generated/my_schema.js"
|
|
476
|
-
}
|
|
477
|
-
}
|
|
478
|
-
```
|
|
479
|
-
|
|
480
|
-
3. **Generate code and descriptor:**
|
|
406
|
+
2. **Generate code and descriptor:**
|
|
481
407
|
```bash
|
|
482
|
-
|
|
408
|
+
npx pbjs -t static-module -w commonjs -o examples/generated/my_schema.js schemas/my_schema.proto
|
|
409
|
+
npx pbts -o examples/generated/my_schema.d.ts examples/generated/my_schema.js
|
|
483
410
|
protoc --descriptor_set_out=schemas/my_schema_descriptor.pb --include_imports schemas/my_schema.proto
|
|
484
411
|
```
|
|
485
412
|
|
|
486
|
-
|
|
413
|
+
3. **Load descriptor in your code:**
|
|
487
414
|
```typescript
|
|
488
|
-
import { loadDescriptorProto } from '@databricks/zerobus-ingest-sdk/utils/descriptor';
|
|
415
|
+
import { loadDescriptorProto } from '@databricks/zerobus-ingest-sdk/utils/descriptor.js';
|
|
489
416
|
const descriptorBase64 = loadDescriptorProto({
|
|
490
417
|
descriptorPath: 'schemas/my_schema_descriptor.pb',
|
|
491
418
|
protoFileName: 'my_schema.proto',
|
|
@@ -499,7 +426,7 @@ message NestedData {
|
|
|
499
426
|
- Install `protoc` (see Step 1 above)
|
|
500
427
|
|
|
501
428
|
**"Cannot find module './generated/air_quality'"**
|
|
502
|
-
- Run `
|
|
429
|
+
- Run the `npx pbjs` and `npx pbts` commands from Step 3
|
|
503
430
|
|
|
504
431
|
**"Descriptor file not found"**
|
|
505
432
|
- Generate the descriptor file using the commands in Step 4
|
|
@@ -511,27 +438,27 @@ message NestedData {
|
|
|
511
438
|
- Make sure you're using `loadDescriptorProto()` from the utils
|
|
512
439
|
|
|
513
440
|
**Build fails on proto generation**
|
|
514
|
-
- Ensure
|
|
441
|
+
- Ensure the runtime and CLI are installed: `npm install protobufjs` and
|
|
442
|
+
`npm install --save-dev protobufjs-cli`
|
|
515
443
|
|
|
516
444
|
#### Quick Reference
|
|
517
445
|
|
|
518
|
-
|
|
446
|
+
After creating `schemas/air_quality.proto` and the `examples/generated`
|
|
447
|
+
directory as described above:
|
|
519
448
|
```bash
|
|
520
|
-
# Install
|
|
521
|
-
npm install
|
|
522
|
-
npm
|
|
449
|
+
# Install the SDK and protobuf codegen tools
|
|
450
|
+
npm install @databricks/zerobus-ingest-sdk protobufjs
|
|
451
|
+
npm install --save-dev protobufjs-cli
|
|
523
452
|
|
|
524
|
-
#
|
|
525
|
-
|
|
453
|
+
# Generate protobuf code and descriptor
|
|
454
|
+
npx pbjs -t static-module -w commonjs -o examples/generated/air_quality.js schemas/air_quality.proto
|
|
455
|
+
npx pbts -o examples/generated/air_quality.d.ts examples/generated/air_quality.js
|
|
526
456
|
protoc --descriptor_set_out=schemas/air_quality_descriptor.pb --include_imports schemas/air_quality.proto
|
|
527
|
-
|
|
528
|
-
# Run example
|
|
529
|
-
npm run example:proto:single
|
|
530
457
|
```
|
|
531
458
|
|
|
532
459
|
#### Why Two Steps (TypeScript + Descriptor)?
|
|
533
460
|
|
|
534
|
-
1. **TypeScript Code Generation** (`
|
|
461
|
+
1. **TypeScript Code Generation** (`npx pbjs` and `npx pbts`):
|
|
535
462
|
- Creates JavaScript/TypeScript code for your application
|
|
536
463
|
- Provides type-safe message creation and encoding
|
|
537
464
|
- Used in your application code
|
|
@@ -545,10 +472,14 @@ Both are necessary for Protocol Buffers ingestion!
|
|
|
545
472
|
|
|
546
473
|
## Usage Examples
|
|
547
474
|
|
|
548
|
-
|
|
475
|
+
The source repository contains complete, runnable examples in `examples/`.
|
|
476
|
+
Clone and build the repository using the [local development](#local-development-from-source)
|
|
477
|
+
instructions, then see [examples/README.md](examples/README.md) for details.
|
|
549
478
|
|
|
550
479
|
### Running Examples
|
|
551
480
|
|
|
481
|
+
Run these commands from the cloned repository's `typescript` directory:
|
|
482
|
+
|
|
552
483
|
```bash
|
|
553
484
|
# Set environment variables
|
|
554
485
|
export ZEROBUS_SERVER_ENDPOINT="https://<workspace-id>.zerobus.<region>.cloud.databricks.com"
|
|
@@ -578,7 +509,7 @@ For higher throughput, use batch ingestion to send multiple records with a singl
|
|
|
578
509
|
|
|
579
510
|
```typescript
|
|
580
511
|
const records = Array.from({ length: 1000 }, (_, i) =>
|
|
581
|
-
AirQuality.create({
|
|
512
|
+
AirQuality.create({ deviceName: `sensor-${i}`, temp: 20 + i, humidity: 50 + i })
|
|
582
513
|
);
|
|
583
514
|
|
|
584
515
|
// Protobuf Type 1: Message objects (high-level) - SDK auto-serializes
|
|
@@ -661,7 +592,7 @@ const stream = await sdk.createStream(
|
|
|
661
592
|
'', // client_secret (ignored when headers_provider is provided)
|
|
662
593
|
options,
|
|
663
594
|
{
|
|
664
|
-
getHeadersCallback:
|
|
595
|
+
getHeadersCallback: () => [
|
|
665
596
|
["authorization", `Bearer ${myToken}`],
|
|
666
597
|
["x-databricks-zerobus-table-name", tableName]
|
|
667
598
|
]
|
|
@@ -682,7 +613,7 @@ const stream = await sdk.createStream(
|
|
|
682
613
|
| Option | Default | Description |
|
|
683
614
|
|--------|---------|-------------|
|
|
684
615
|
| `recordType` | `RecordType.Proto` | Serialization format: `RecordType.Json` or `RecordType.Proto` |
|
|
685
|
-
| `maxInflightRequests` |
|
|
616
|
+
| `maxInflightRequests` | 1,000,000 | Maximum number of unacknowledged requests |
|
|
686
617
|
| `recovery` | true | Enable automatic stream recovery |
|
|
687
618
|
| `recoveryTimeoutMs` | 15,000 | Timeout for recovery operations (ms) |
|
|
688
619
|
| `recoveryBackoffMs` | 2,000 | Delay between recovery attempts (ms) |
|
|
@@ -716,13 +647,15 @@ const stream = await sdk.createStream(
|
|
|
716
647
|
## Descriptor Utilities
|
|
717
648
|
|
|
718
649
|
The SDK provides a helper function to extract Protocol Buffer descriptors from FileDescriptorSets.
|
|
650
|
+
Use the `.js` subpath shown below for compatibility with CommonJS and native
|
|
651
|
+
Node.js ESM imports.
|
|
719
652
|
|
|
720
653
|
### loadDescriptorProto()
|
|
721
654
|
|
|
722
655
|
Extracts a specific message descriptor from a FileDescriptorSet:
|
|
723
656
|
|
|
724
657
|
```typescript
|
|
725
|
-
import { loadDescriptorProto } from '@databricks/zerobus-ingest-sdk/utils/descriptor';
|
|
658
|
+
import { loadDescriptorProto } from '@databricks/zerobus-ingest-sdk/utils/descriptor.js';
|
|
726
659
|
|
|
727
660
|
const descriptorBase64 = loadDescriptorProto({
|
|
728
661
|
descriptorPath: 'schemas/my_schema_descriptor.pb',
|
|
@@ -755,38 +688,37 @@ const descriptorBase64 = loadDescriptorProto({
|
|
|
755
688
|
|
|
756
689
|
## Error Handling
|
|
757
690
|
|
|
758
|
-
The SDK includes automatic recovery for transient failures (enabled by default with `recovery: true`).
|
|
691
|
+
The SDK includes automatic recovery for transient failures (enabled by default with `recovery: true`). `getUnackedBatches()` and `recreateStream()` succeed only after a terminal native-stream failure, which already closes the stream. An enqueue failure leaves the wrapper active, so those calls reject; rethrow the original error. Do not call `stream.close()` before `recreateStream()`, because close releases the native handle.
|
|
759
692
|
|
|
760
693
|
```typescript
|
|
694
|
+
let replacement;
|
|
761
695
|
try {
|
|
762
696
|
const offset = await stream.ingestRecordOffset(record);
|
|
763
|
-
await stream.
|
|
697
|
+
await stream.flush();
|
|
764
698
|
console.log(`Success: offset ${offset}`);
|
|
765
699
|
} catch (error) {
|
|
766
700
|
console.error('Ingestion failed:', error);
|
|
767
|
-
|
|
768
|
-
// When stream fails, close it first
|
|
769
|
-
await stream.close();
|
|
770
|
-
console.log('Stream closed after error');
|
|
771
|
-
|
|
772
|
-
// Optional: Inspect what needs recovery (must be called on closed stream)
|
|
773
|
-
const unackedBatches = await stream.getUnackedBatches();
|
|
774
|
-
console.log(`Batches to recover: ${unackedBatches.length}`);
|
|
775
|
-
|
|
776
|
-
// Recommended recovery approach: Use recreateStream()
|
|
777
|
-
// This method:
|
|
778
|
-
// 1. Gets all unacknowledged batches from the failed stream
|
|
779
|
-
// 2. Creates a new stream with the same configuration
|
|
780
|
-
// 3. Re-ingests all unacknowledged batches automatically
|
|
781
|
-
// 4. Returns the new stream ready for continued use
|
|
782
|
-
const newStream = await sdk.recreateStream(stream);
|
|
783
|
-
console.log(`Stream recreated with ${unackedBatches.length} batches re-ingested`);
|
|
784
|
-
|
|
785
|
-
// Continue using newStream for further ingestion
|
|
786
701
|
try {
|
|
787
|
-
|
|
702
|
+
const unackedBatches = await stream.getUnackedBatches();
|
|
703
|
+
console.log(`Batches to recover: ${unackedBatches.length}`);
|
|
704
|
+
replacement = await sdk.recreateStream(stream);
|
|
705
|
+
await replacement.flush();
|
|
706
|
+
} catch (recoveryError) {
|
|
707
|
+
console.error('Stream was not terminal or recovery failed:', recoveryError);
|
|
708
|
+
throw new AggregateError(
|
|
709
|
+
[error, recoveryError],
|
|
710
|
+
'ingestion and recovery both failed',
|
|
711
|
+
);
|
|
788
712
|
} finally {
|
|
789
|
-
|
|
713
|
+
if (replacement) {
|
|
714
|
+
await replacement.close();
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
} finally {
|
|
718
|
+
try {
|
|
719
|
+
await stream.close();
|
|
720
|
+
} catch (closeError) {
|
|
721
|
+
console.error('Failed stream released:', closeError);
|
|
790
722
|
}
|
|
791
723
|
}
|
|
792
724
|
```
|
|
@@ -806,12 +738,14 @@ Main entry point for the SDK.
|
|
|
806
738
|
**Constructor:**
|
|
807
739
|
|
|
808
740
|
```typescript
|
|
809
|
-
new ZerobusSdk(zerobusEndpoint: string, unityCatalogUrl: string)
|
|
741
|
+
new ZerobusSdk(zerobusEndpoint: string, unityCatalogUrl: string, options?: ZerobusSdkOptions)
|
|
810
742
|
```
|
|
811
743
|
|
|
812
744
|
**Parameters:**
|
|
813
745
|
- `zerobusEndpoint` (string) - The Zerobus gRPC endpoint (e.g., `https://<workspace-id>.zerobus.<region>.cloud.databricks.com` for AWS, or `https://<workspace-id>.zerobus.<region>.azuredatabricks.net` for Azure)
|
|
814
746
|
- `unityCatalogUrl` (string) - The Unity Catalog endpoint (your workspace URL)
|
|
747
|
+
- `options` (ZerobusSdkOptions, optional) - Additional SDK configuration:
|
|
748
|
+
- `applicationName` (string, optional) - Application identifier appended to the HTTP `user-agent` header, conventionally `"<product>/<version>"` (e.g. `"my-app/1.0"`). The header becomes `zerobus-sdk-ts/<version> <applicationName>`, enabling server-side attribution.
|
|
815
749
|
|
|
816
750
|
**Methods:**
|
|
817
751
|
|
|
@@ -847,23 +781,29 @@ This method is the **recommended approach** for recovering from stream failures.
|
|
|
847
781
|
4. Returns the new stream ready for continued ingestion
|
|
848
782
|
|
|
849
783
|
**Parameters:**
|
|
850
|
-
- `stream` - The failed
|
|
784
|
+
- `stream` - The terminally failed stream to recreate. Do not call `stream.close()`
|
|
785
|
+
first because the TypeScript wrapper releases its native handle on close.
|
|
851
786
|
|
|
852
787
|
**Returns:** Promise resolving to a new `ZerobusStream` with all unacknowledged batches re-ingested
|
|
853
788
|
|
|
854
789
|
**Example:**
|
|
855
790
|
```typescript
|
|
856
791
|
try {
|
|
857
|
-
await stream.
|
|
792
|
+
await stream.ingestRecordsOffset(batch);
|
|
793
|
+
await stream.flush();
|
|
858
794
|
} catch (error) {
|
|
859
|
-
|
|
860
|
-
// Automatically recreate stream and recover all unacked batches
|
|
795
|
+
// recreateStream() rejects unless the native stream already failed closed.
|
|
861
796
|
const newStream = await sdk.recreateStream(stream);
|
|
862
|
-
|
|
797
|
+
try {
|
|
798
|
+
await newStream.flush();
|
|
799
|
+
} finally {
|
|
800
|
+
await newStream.close();
|
|
801
|
+
}
|
|
863
802
|
}
|
|
864
803
|
```
|
|
865
804
|
|
|
866
|
-
**Note:** This method preserves batch structure and re-ingests batches atomically. For
|
|
805
|
+
**Note:** This method preserves batch structure and re-ingests batches atomically. For
|
|
806
|
+
debugging, inspect `getUnackedBatches()` after a terminal failure and before closing the wrapper.
|
|
867
807
|
|
|
868
808
|
---
|
|
869
809
|
|
|
@@ -877,13 +817,16 @@ Represents an active ingestion stream.
|
|
|
877
817
|
async ingestRecordOffset(payload: Buffer | string | object): Promise<bigint>
|
|
878
818
|
```
|
|
879
819
|
|
|
880
|
-
**(Recommended)** Ingests a single record. The Promise resolves immediately after the record is queued (before server acknowledgment).
|
|
820
|
+
**(Recommended)** Ingests a single record. The Promise resolves immediately after the record is queued (before server acknowledgment); the round-trip happens in the background. The idiomatic flow is to ingest in a loop and then `flush()` once to confirm everything queued so far. The returned offset, together with `waitForOffset()`, lets you confirm a specific record when needed — prefer that for bulk over waiting after each record, since per-record waiting limits throughput to one round-trip per record.
|
|
881
821
|
|
|
882
822
|
```typescript
|
|
883
|
-
//
|
|
884
|
-
|
|
885
|
-
const
|
|
886
|
-
await stream.
|
|
823
|
+
// Idiomatic flow: ingest in a loop, then flush once
|
|
824
|
+
let lastOffset: bigint | null = null;
|
|
825
|
+
for (const record of records) {
|
|
826
|
+
lastOffset = await stream.ingestRecordOffset(record); // Resolves immediately
|
|
827
|
+
}
|
|
828
|
+
await stream.flush(); // Resolves once everything queued so far is acknowledged
|
|
829
|
+
// (Or, to confirm a specific record: if (lastOffset !== null) await stream.waitForOffset(lastOffset))
|
|
887
830
|
```
|
|
888
831
|
|
|
889
832
|
---
|
|
@@ -892,7 +835,7 @@ await stream.waitForOffset(offset2); // Waits for server to acknowledge all rec
|
|
|
892
835
|
async ingestRecordsOffset(payloads: Array<Buffer | string | object>): Promise<bigint | null>
|
|
893
836
|
```
|
|
894
837
|
|
|
895
|
-
**(Recommended)** Ingests multiple records as a batch. The Promise resolves immediately after the batch is queued (before server acknowledgment). Returns `null` for empty batches.
|
|
838
|
+
**(Recommended)** Ingests multiple records as a batch. The Promise resolves immediately after the batch is queued (before server acknowledgment); the round-trip happens in the background. Returns `null` for empty batches. As with `ingestRecordOffset()`, the idiomatic flow is to ingest in a loop and `flush()` once to confirm; reach for `waitForOffset()` when a specific batch must be confirmed before continuing.
|
|
896
839
|
|
|
897
840
|
---
|
|
898
841
|
|
|
@@ -900,7 +843,7 @@ async ingestRecordsOffset(payloads: Array<Buffer | string | object>): Promise<bi
|
|
|
900
843
|
async waitForOffset(offsetId: bigint): Promise<void>
|
|
901
844
|
```
|
|
902
845
|
|
|
903
|
-
Waits for the server to acknowledge all records up to and including the specified offset ID.
|
|
846
|
+
Waits for the server to acknowledge all records up to and including the specified offset ID. Acks are ordered, so waiting on the **last** offset confirms every prior record too. Use this when a specific record must be confirmed before continuing; for confirming a bulk run, `flush()` is usually simpler. Avoid calling it after every record in a tight loop, since that limits throughput to one record per round-trip.
|
|
904
847
|
|
|
905
848
|
---
|
|
906
849
|
|
|
@@ -1000,7 +943,7 @@ await stream.ingestRecords(buffers);
|
|
|
1000
943
|
async flush(): Promise<void>
|
|
1001
944
|
```
|
|
1002
945
|
|
|
1003
|
-
Flushes all pending records and waits for acknowledgments.
|
|
946
|
+
Flushes all pending records and waits for acknowledgments. This is the recommended way to confirm a batch of `ingestRecordOffset()` / `ingestRecordsOffset()` calls: ingest in a loop without waiting, then `flush()` once at the end instead of calling `waitForOffset()` after every record.
|
|
1004
947
|
|
|
1005
948
|
```typescript
|
|
1006
949
|
async close(): Promise<void>
|
|
@@ -1014,7 +957,8 @@ async getUnackedRecords(): Promise<Buffer[]>
|
|
|
1014
957
|
|
|
1015
958
|
Returns unacknowledged record payloads as a flat array for inspection purposes.
|
|
1016
959
|
|
|
1017
|
-
**Important:**
|
|
960
|
+
**Important:** This can only be called after a terminal stream failure. Do not call
|
|
961
|
+
`stream.close()` first: the TypeScript wrapper releases the underlying stream handle on close.
|
|
1018
962
|
|
|
1019
963
|
**Returns:** Array of Buffer containing the raw record payloads
|
|
1020
964
|
|
|
@@ -1028,7 +972,8 @@ async getUnackedBatches(): Promise<Buffer[][]>
|
|
|
1028
972
|
|
|
1029
973
|
Returns unacknowledged records grouped by their original batches for inspection purposes.
|
|
1030
974
|
|
|
1031
|
-
**Important:**
|
|
975
|
+
**Important:** This can only be called after a terminal stream failure. Do not call
|
|
976
|
+
`stream.close()` first: the TypeScript wrapper releases the underlying stream handle on close.
|
|
1032
977
|
|
|
1033
978
|
**Returns:** Array of arrays, where each inner array represents a batch of records as Buffers
|
|
1034
979
|
|
|
@@ -1041,15 +986,11 @@ try {
|
|
|
1041
986
|
await stream.ingestRecords(batch2);
|
|
1042
987
|
// ... error occurs
|
|
1043
988
|
} catch (error) {
|
|
1044
|
-
await stream.close();
|
|
1045
989
|
const unackedBatches = await stream.getUnackedBatches();
|
|
1046
990
|
// unackedBatches[0] contains records from batch1 (if not acked)
|
|
1047
991
|
// unackedBatches[1] contains records from batch2 (if not acked)
|
|
1048
992
|
|
|
1049
|
-
|
|
1050
|
-
for (const batch of unackedBatches) {
|
|
1051
|
-
await newStream.ingestRecords(batch);
|
|
1052
|
-
}
|
|
993
|
+
console.log(`Batches available for recovery: ${unackedBatches.length}`);
|
|
1053
994
|
}
|
|
1054
995
|
```
|
|
1055
996
|
|
|
@@ -1072,10 +1013,10 @@ interface TableProperties {
|
|
|
1072
1013
|
|
|
1073
1014
|
```typescript
|
|
1074
1015
|
// JSON mode
|
|
1075
|
-
const
|
|
1016
|
+
const jsonTableProperties = { tableName: 'main.default.air_quality' };
|
|
1076
1017
|
|
|
1077
1018
|
// Protocol Buffers mode
|
|
1078
|
-
const
|
|
1019
|
+
const protoTableProperties = {
|
|
1079
1020
|
tableName: 'main.default.air_quality',
|
|
1080
1021
|
descriptorProto: descriptorBase64 // Required for protobuf
|
|
1081
1022
|
};
|
|
@@ -1092,7 +1033,7 @@ Configuration options for stream behavior.
|
|
|
1092
1033
|
```typescript
|
|
1093
1034
|
interface StreamConfigurationOptions {
|
|
1094
1035
|
recordType?: RecordType; // RecordType.Json or RecordType.Proto. Default: RecordType.Proto
|
|
1095
|
-
maxInflightRequests?: number; // Default:
|
|
1036
|
+
maxInflightRequests?: number; // Default: 1,000,000
|
|
1096
1037
|
recovery?: boolean; // Default: true
|
|
1097
1038
|
recoveryTimeoutMs?: number; // Default: 15,000
|
|
1098
1039
|
recoveryBackoffMs?: number; // Default: 2,000
|
|
@@ -1112,11 +1053,12 @@ enum RecordType {
|
|
|
1112
1053
|
|
|
1113
1054
|
1. **Reuse SDK instances**: Create one `ZerobusSdk` instance per application
|
|
1114
1055
|
2. **Stream lifecycle**: Always close streams in a `finally` block to ensure all records are flushed
|
|
1115
|
-
3. **Batch size**: Adjust `maxInflightRequests` based on your throughput requirements (default:
|
|
1056
|
+
3. **Batch size**: Adjust `maxInflightRequests` based on your throughput requirements (default: 1,000,000)
|
|
1116
1057
|
4. **Error handling**: The stream handles errors internally with automatic retry. Only use `recreateStream()` for persistent failures after internal retries are exhausted.
|
|
1117
1058
|
5. **Use Protocol Buffers for production**: Protocol Buffers (the default) provides better performance and schema validation. Use JSON only when you need schema flexibility or for quick prototyping.
|
|
1118
1059
|
6. **Store credentials securely**: Use environment variables, never hardcode credentials
|
|
1119
1060
|
7. **Use batch ingestion**: For high-throughput scenarios, use `ingestRecordsOffset()` instead of individual `ingestRecordOffset()` calls
|
|
1061
|
+
8. **Ingest in a loop, then `flush()`**: See [Acknowledgments and throughput](#acknowledgments-and-throughput) above for the full explanation.
|
|
1120
1062
|
|
|
1121
1063
|
## Platform Support
|
|
1122
1064
|
|
|
@@ -1127,14 +1069,15 @@ The SDK supports all platforms where Node.js and Rust are available.
|
|
|
1127
1069
|
Pre-built native binaries are available for:
|
|
1128
1070
|
|
|
1129
1071
|
- **Linux**: x64, ARM64
|
|
1072
|
+
- **macOS**: x64, ARM64
|
|
1130
1073
|
- **Windows**: x64
|
|
1131
1074
|
|
|
1132
1075
|
### Build from Source
|
|
1133
1076
|
|
|
1134
|
-
|
|
1077
|
+
Other platforms (e.g. Linux musl, FreeBSD) need to build from source during `npm install`, which requires:
|
|
1135
1078
|
|
|
1136
1079
|
- **Rust toolchain** (1.70+): Install via `curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh`
|
|
1137
|
-
-
|
|
1080
|
+
- Platform C/C++ build tools (Xcode CLT on macOS, build-essential on Debian/Ubuntu, etc.)
|
|
1138
1081
|
|
|
1139
1082
|
The build process happens automatically during installation and typically takes 2-3 minutes.
|
|
1140
1083
|
|
|
@@ -1162,7 +1105,7 @@ This SDK wraps the high-performance [Rust Zerobus SDK](https://github.com/databr
|
|
|
1162
1105
|
**Benefits:**
|
|
1163
1106
|
- **Native performance** - Rust implementation for high-throughput ingestion
|
|
1164
1107
|
- **Native async/await support** - Rust futures become JavaScript Promises
|
|
1165
|
-
- **Automatic memory management**
|
|
1108
|
+
- **Automatic memory management** for native objects. You still must `await stream.close()` to flush and release the stream.
|
|
1166
1109
|
- **Type safety** - Compile-time checks on both sides
|
|
1167
1110
|
|
|
1168
1111
|
## Community and Contributing
|