@databricks/zerobus-ingest-sdk 1.1.0 → 1.3.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 +82 -97
- package/Cargo.toml +6 -13
- package/README.md +150 -205
- package/index.d.ts +355 -41
- package/index.js +4 -1
- package/package.json +28 -13
- package/src/headers_provider.ts +24 -36
- package/src/lib.rs +119 -82
- 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, Protocol Buffers, and Arrow Flight
|
|
31
|
+
- **Multiple serialization formats**: Support for JSON, Protocol Buffers, and Arrow Flight ingestion 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,108 +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
|
-
1. Clone the repository:
|
|
141
|
-
```bash
|
|
142
|
-
git clone https://github.com/databricks/zerobus-sdk.git
|
|
143
|
-
cd zerobus-sdk/ts
|
|
144
|
-
```
|
|
145
|
-
|
|
146
|
-
2. Install dependencies:
|
|
147
|
-
```bash
|
|
148
|
-
npm install
|
|
149
|
-
```
|
|
150
|
-
|
|
151
|
-
3. Build the native addon:
|
|
152
|
-
```bash
|
|
153
|
-
npm run build
|
|
154
|
-
```
|
|
155
|
-
|
|
156
|
-
This will compile the Rust code into a native Node.js addon (`.node` file) for your platform.
|
|
157
|
-
|
|
158
|
-
4. Verify the build:
|
|
159
|
-
```bash
|
|
160
|
-
# You should see a .node file
|
|
161
|
-
ls -la *.node
|
|
162
|
-
```
|
|
163
|
-
|
|
164
|
-
5. The SDK is now ready to use! You can:
|
|
165
|
-
- Use it directly in this directory for examples
|
|
166
|
-
- Link it globally: `npm link`
|
|
167
|
-
- Or copy it into your project's `node_modules`
|
|
168
|
-
|
|
169
81
|
**Troubleshooting:**
|
|
170
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`
|
|
171
86
|
- **"rustc: command not found"**: Restart your terminal after installing Rust
|
|
172
87
|
- **Build fails on Windows**: Ensure Visual Studio Build Tools are installed with C++ support
|
|
173
88
|
- **Build fails on Linux**: Install build-essential or equivalent package
|
|
@@ -175,13 +90,18 @@ source $HOME/.cargo/env
|
|
|
175
90
|
|
|
176
91
|
### Choose Your Serialization Format
|
|
177
92
|
|
|
178
|
-
The SDK supports
|
|
93
|
+
The SDK supports three ingestion formats. Protocol Buffers is the default record format and recommended for production row-oriented workloads:
|
|
179
94
|
|
|
180
95
|
- **Protocol Buffers (Default)** - Strongly-typed schemas, efficient binary encoding, better performance. This is the default format.
|
|
181
96
|
- **JSON** - Simple, no schema compilation needed. Good for getting started quickly or when schema flexibility is needed.
|
|
97
|
+
- Arrow Flight - High-performance columnar ingestion for applications that already produce Arrow data. See the [Arrow example](examples/arrow/README.md).
|
|
182
98
|
|
|
183
99
|
> **Note:** If you don't specify `recordType`, the SDK will use Protocol Buffers by default. To use JSON, explicitly set `recordType: RecordType.Json`.
|
|
184
100
|
|
|
101
|
+
### Acknowledgments and throughput
|
|
102
|
+
|
|
103
|
+
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.
|
|
104
|
+
|
|
185
105
|
### Option 1: Using JSON (Quick Start)
|
|
186
106
|
|
|
187
107
|
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`.
|
|
@@ -189,12 +109,13 @@ JSON mode is the simplest way to get started. You don't need to define or compil
|
|
|
189
109
|
```typescript
|
|
190
110
|
import { ZerobusSdk, RecordType } from '@databricks/zerobus-ingest-sdk';
|
|
191
111
|
|
|
112
|
+
async function main(): Promise<void> {
|
|
192
113
|
// Configuration
|
|
193
114
|
// For AWS:
|
|
194
115
|
const zerobusEndpoint = 'https://<workspace-id>.zerobus.<region>.cloud.databricks.com';
|
|
195
116
|
const workspaceUrl = 'https://<workspace-name>.cloud.databricks.com';
|
|
196
117
|
// For Azure:
|
|
197
|
-
// const zerobusEndpoint = '
|
|
118
|
+
// const zerobusEndpoint = 'https://<workspace-id>.zerobus.<region>.azuredatabricks.net';
|
|
198
119
|
// const workspaceUrl = 'https://<workspace-name>.azuredatabricks.net';
|
|
199
120
|
|
|
200
121
|
const tableName = 'main.default.air_quality';
|
|
@@ -223,8 +144,6 @@ const stream = await sdk.createStream(
|
|
|
223
144
|
);
|
|
224
145
|
|
|
225
146
|
try {
|
|
226
|
-
let lastOffset: bigint;
|
|
227
|
-
|
|
228
147
|
// Send all records
|
|
229
148
|
for (let i = 0; i < 100; i++) {
|
|
230
149
|
const record = {
|
|
@@ -233,16 +152,23 @@ try {
|
|
|
233
152
|
humidity: 50 + (i % 40)
|
|
234
153
|
};
|
|
235
154
|
|
|
236
|
-
//
|
|
237
|
-
|
|
155
|
+
// Queue the record; do not wait for its acknowledgement here
|
|
156
|
+
await stream.ingestRecordOffset(record);
|
|
238
157
|
}
|
|
239
158
|
|
|
240
159
|
// Wait for all records to be acknowledged
|
|
241
|
-
await stream.
|
|
160
|
+
await stream.flush();
|
|
242
161
|
console.log('Successfully ingested 100 records!');
|
|
243
162
|
} finally {
|
|
244
163
|
await stream.close();
|
|
245
164
|
}
|
|
165
|
+
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
main().catch((error) => {
|
|
169
|
+
console.error('Fatal error:', error);
|
|
170
|
+
process.exitCode = 1;
|
|
171
|
+
});
|
|
246
172
|
```
|
|
247
173
|
|
|
248
174
|
### Option 2: Using Protocol Buffers (Default, Recommended)
|
|
@@ -254,7 +180,15 @@ Protocol Buffers is the default serialization format and provides efficient bina
|
|
|
254
180
|
Before starting, ensure you have:
|
|
255
181
|
|
|
256
182
|
1. **Protocol Buffer Compiler (`protoc`)** - Required for generating descriptor files
|
|
257
|
-
2. **protobufjs**
|
|
183
|
+
2. **protobufjs** - Required at runtime by your generated Protocol Buffer code
|
|
184
|
+
3. **protobufjs-cli** - Required during development to generate JavaScript and type declarations
|
|
185
|
+
|
|
186
|
+
Install the JavaScript runtime and code-generation tools in your application:
|
|
187
|
+
|
|
188
|
+
```bash
|
|
189
|
+
npm install protobufjs
|
|
190
|
+
npm install --save-dev protobufjs-cli
|
|
191
|
+
```
|
|
258
192
|
|
|
259
193
|
#### Step 1: Install Protocol Buffer Compiler
|
|
260
194
|
|
|
@@ -295,7 +229,9 @@ protoc --version
|
|
|
295
229
|
|
|
296
230
|
#### Step 2: Define Your Protocol Buffer Schema
|
|
297
231
|
|
|
298
|
-
|
|
232
|
+
Create `schemas/air_quality.proto` in your application with the following
|
|
233
|
+
example schema. Also create an `examples/generated` directory for the generated
|
|
234
|
+
JavaScript and type declarations:
|
|
299
235
|
|
|
300
236
|
```protobuf
|
|
301
237
|
syntax = "proto2";
|
|
@@ -315,13 +251,8 @@ message AirQuality {
|
|
|
315
251
|
Generate TypeScript code from your proto schema:
|
|
316
252
|
|
|
317
253
|
```bash
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
This runs:
|
|
322
|
-
```bash
|
|
323
|
-
pbjs -t static-module -w commonjs -o examples/generated/air_quality.js schemas/air_quality.proto
|
|
324
|
-
pbts -o examples/generated/air_quality.d.ts examples/generated/air_quality.js
|
|
254
|
+
npx pbjs -t static-module -w commonjs -o examples/generated/air_quality.js schemas/air_quality.proto
|
|
255
|
+
npx pbts -o examples/generated/air_quality.d.ts examples/generated/air_quality.js
|
|
325
256
|
```
|
|
326
257
|
|
|
327
258
|
**Output:**
|
|
@@ -351,8 +282,9 @@ That's it! The SDK will automatically extract the message descriptor from this f
|
|
|
351
282
|
```typescript
|
|
352
283
|
import { ZerobusSdk, RecordType } from '@databricks/zerobus-ingest-sdk';
|
|
353
284
|
import * as airQuality from './examples/generated/air_quality';
|
|
354
|
-
import { loadDescriptorProto } from '@databricks/zerobus-ingest-sdk/utils/descriptor';
|
|
285
|
+
import { loadDescriptorProto } from '@databricks/zerobus-ingest-sdk/utils/descriptor.js';
|
|
355
286
|
|
|
287
|
+
async function main(): Promise<void> {
|
|
356
288
|
// Configuration
|
|
357
289
|
const zerobusEndpoint = 'https://<workspace-id>.zerobus.<region>.cloud.databricks.com';
|
|
358
290
|
const workspaceUrl = 'https://<workspace-name>.cloud.databricks.com';
|
|
@@ -388,26 +320,32 @@ const stream = await sdk.createStream(tableProperties, clientId, clientSecret, o
|
|
|
388
320
|
|
|
389
321
|
try {
|
|
390
322
|
const AirQuality = airQuality.examples.AirQuality;
|
|
391
|
-
let lastOffset: bigint;
|
|
392
323
|
|
|
393
324
|
// Send all records
|
|
394
325
|
for (let i = 0; i < 100; i++) {
|
|
395
326
|
const record = AirQuality.create({
|
|
396
|
-
|
|
327
|
+
deviceName: `sensor-${i}`,
|
|
397
328
|
temp: 20 + i,
|
|
398
329
|
humidity: 50 + i
|
|
399
330
|
});
|
|
400
331
|
|
|
401
|
-
//
|
|
402
|
-
|
|
332
|
+
// Queue the record; do not wait for its acknowledgement here
|
|
333
|
+
await stream.ingestRecordOffset(record);
|
|
403
334
|
}
|
|
404
335
|
|
|
405
336
|
// Wait for all records to be acknowledged
|
|
406
|
-
await stream.
|
|
337
|
+
await stream.flush();
|
|
407
338
|
console.log('Successfully ingested 100 records!');
|
|
408
339
|
} finally {
|
|
409
340
|
await stream.close();
|
|
410
341
|
}
|
|
342
|
+
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
main().catch((error) => {
|
|
346
|
+
console.error('Fatal error:', error);
|
|
347
|
+
process.exitCode = 1;
|
|
348
|
+
});
|
|
411
349
|
```
|
|
412
350
|
|
|
413
351
|
#### Type Mapping: Delta ↔ Protocol Buffers
|
|
@@ -466,24 +404,16 @@ message NestedData {
|
|
|
466
404
|
EOF
|
|
467
405
|
```
|
|
468
406
|
|
|
469
|
-
2. **
|
|
470
|
-
```json
|
|
471
|
-
{
|
|
472
|
-
"scripts": {
|
|
473
|
-
"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"
|
|
474
|
-
}
|
|
475
|
-
}
|
|
476
|
-
```
|
|
477
|
-
|
|
478
|
-
3. **Generate code and descriptor:**
|
|
407
|
+
2. **Generate code and descriptor:**
|
|
479
408
|
```bash
|
|
480
|
-
|
|
409
|
+
npx pbjs -t static-module -w commonjs -o examples/generated/my_schema.js schemas/my_schema.proto
|
|
410
|
+
npx pbts -o examples/generated/my_schema.d.ts examples/generated/my_schema.js
|
|
481
411
|
protoc --descriptor_set_out=schemas/my_schema_descriptor.pb --include_imports schemas/my_schema.proto
|
|
482
412
|
```
|
|
483
413
|
|
|
484
|
-
|
|
414
|
+
3. **Load descriptor in your code:**
|
|
485
415
|
```typescript
|
|
486
|
-
import { loadDescriptorProto } from '@databricks/zerobus-ingest-sdk/utils/descriptor';
|
|
416
|
+
import { loadDescriptorProto } from '@databricks/zerobus-ingest-sdk/utils/descriptor.js';
|
|
487
417
|
const descriptorBase64 = loadDescriptorProto({
|
|
488
418
|
descriptorPath: 'schemas/my_schema_descriptor.pb',
|
|
489
419
|
protoFileName: 'my_schema.proto',
|
|
@@ -497,7 +427,7 @@ message NestedData {
|
|
|
497
427
|
- Install `protoc` (see Step 1 above)
|
|
498
428
|
|
|
499
429
|
**"Cannot find module './generated/air_quality'"**
|
|
500
|
-
- Run `
|
|
430
|
+
- Run the `npx pbjs` and `npx pbts` commands from Step 3
|
|
501
431
|
|
|
502
432
|
**"Descriptor file not found"**
|
|
503
433
|
- Generate the descriptor file using the commands in Step 4
|
|
@@ -509,27 +439,27 @@ message NestedData {
|
|
|
509
439
|
- Make sure you're using `loadDescriptorProto()` from the utils
|
|
510
440
|
|
|
511
441
|
**Build fails on proto generation**
|
|
512
|
-
- Ensure
|
|
442
|
+
- Ensure the runtime and CLI are installed: `npm install protobufjs` and
|
|
443
|
+
`npm install --save-dev protobufjs-cli`
|
|
513
444
|
|
|
514
445
|
#### Quick Reference
|
|
515
446
|
|
|
516
|
-
|
|
447
|
+
After creating `schemas/air_quality.proto` and the `examples/generated`
|
|
448
|
+
directory as described above:
|
|
517
449
|
```bash
|
|
518
|
-
# Install
|
|
519
|
-
npm install
|
|
520
|
-
npm
|
|
450
|
+
# Install the SDK and protobuf codegen tools
|
|
451
|
+
npm install @databricks/zerobus-ingest-sdk protobufjs
|
|
452
|
+
npm install --save-dev protobufjs-cli
|
|
521
453
|
|
|
522
|
-
#
|
|
523
|
-
|
|
454
|
+
# Generate protobuf code and descriptor
|
|
455
|
+
npx pbjs -t static-module -w commonjs -o examples/generated/air_quality.js schemas/air_quality.proto
|
|
456
|
+
npx pbts -o examples/generated/air_quality.d.ts examples/generated/air_quality.js
|
|
524
457
|
protoc --descriptor_set_out=schemas/air_quality_descriptor.pb --include_imports schemas/air_quality.proto
|
|
525
|
-
|
|
526
|
-
# Run example
|
|
527
|
-
npm run example:proto:single
|
|
528
458
|
```
|
|
529
459
|
|
|
530
460
|
#### Why Two Steps (TypeScript + Descriptor)?
|
|
531
461
|
|
|
532
|
-
1. **TypeScript Code Generation** (`
|
|
462
|
+
1. **TypeScript Code Generation** (`npx pbjs` and `npx pbts`):
|
|
533
463
|
- Creates JavaScript/TypeScript code for your application
|
|
534
464
|
- Provides type-safe message creation and encoding
|
|
535
465
|
- Used in your application code
|
|
@@ -543,10 +473,14 @@ Both are necessary for Protocol Buffers ingestion!
|
|
|
543
473
|
|
|
544
474
|
## Usage Examples
|
|
545
475
|
|
|
546
|
-
|
|
476
|
+
The source repository contains complete, runnable examples in `examples/`.
|
|
477
|
+
Clone and build the repository using the [local development](#local-development-from-source)
|
|
478
|
+
instructions, then see [examples/README.md](examples/README.md) for details.
|
|
547
479
|
|
|
548
480
|
### Running Examples
|
|
549
481
|
|
|
482
|
+
Run these commands from the cloned repository's `typescript` directory:
|
|
483
|
+
|
|
550
484
|
```bash
|
|
551
485
|
# Set environment variables
|
|
552
486
|
export ZEROBUS_SERVER_ENDPOINT="https://<workspace-id>.zerobus.<region>.cloud.databricks.com"
|
|
@@ -576,7 +510,7 @@ For higher throughput, use batch ingestion to send multiple records with a singl
|
|
|
576
510
|
|
|
577
511
|
```typescript
|
|
578
512
|
const records = Array.from({ length: 1000 }, (_, i) =>
|
|
579
|
-
AirQuality.create({
|
|
513
|
+
AirQuality.create({ deviceName: `sensor-${i}`, temp: 20 + i, humidity: 50 + i })
|
|
580
514
|
);
|
|
581
515
|
|
|
582
516
|
// Protobuf Type 1: Message objects (high-level) - SDK auto-serializes
|
|
@@ -659,7 +593,7 @@ const stream = await sdk.createStream(
|
|
|
659
593
|
'', // client_secret (ignored when headers_provider is provided)
|
|
660
594
|
options,
|
|
661
595
|
{
|
|
662
|
-
getHeadersCallback:
|
|
596
|
+
getHeadersCallback: () => [
|
|
663
597
|
["authorization", `Bearer ${myToken}`],
|
|
664
598
|
["x-databricks-zerobus-table-name", tableName]
|
|
665
599
|
]
|
|
@@ -680,7 +614,7 @@ const stream = await sdk.createStream(
|
|
|
680
614
|
| Option | Default | Description |
|
|
681
615
|
|--------|---------|-------------|
|
|
682
616
|
| `recordType` | `RecordType.Proto` | Serialization format: `RecordType.Json` or `RecordType.Proto` |
|
|
683
|
-
| `maxInflightRequests` |
|
|
617
|
+
| `maxInflightRequests` | 1,000,000 | Maximum number of unacknowledged requests |
|
|
684
618
|
| `recovery` | true | Enable automatic stream recovery |
|
|
685
619
|
| `recoveryTimeoutMs` | 15,000 | Timeout for recovery operations (ms) |
|
|
686
620
|
| `recoveryBackoffMs` | 2,000 | Delay between recovery attempts (ms) |
|
|
@@ -714,13 +648,15 @@ const stream = await sdk.createStream(
|
|
|
714
648
|
## Descriptor Utilities
|
|
715
649
|
|
|
716
650
|
The SDK provides a helper function to extract Protocol Buffer descriptors from FileDescriptorSets.
|
|
651
|
+
Use the `.js` subpath shown below for compatibility with CommonJS and native
|
|
652
|
+
Node.js ESM imports.
|
|
717
653
|
|
|
718
654
|
### loadDescriptorProto()
|
|
719
655
|
|
|
720
656
|
Extracts a specific message descriptor from a FileDescriptorSet:
|
|
721
657
|
|
|
722
658
|
```typescript
|
|
723
|
-
import { loadDescriptorProto } from '@databricks/zerobus-ingest-sdk/utils/descriptor';
|
|
659
|
+
import { loadDescriptorProto } from '@databricks/zerobus-ingest-sdk/utils/descriptor.js';
|
|
724
660
|
|
|
725
661
|
const descriptorBase64 = loadDescriptorProto({
|
|
726
662
|
descriptorPath: 'schemas/my_schema_descriptor.pb',
|
|
@@ -753,38 +689,37 @@ const descriptorBase64 = loadDescriptorProto({
|
|
|
753
689
|
|
|
754
690
|
## Error Handling
|
|
755
691
|
|
|
756
|
-
The SDK includes automatic recovery for transient failures (enabled by default with `recovery: true`).
|
|
692
|
+
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.
|
|
757
693
|
|
|
758
694
|
```typescript
|
|
695
|
+
let replacement;
|
|
759
696
|
try {
|
|
760
697
|
const offset = await stream.ingestRecordOffset(record);
|
|
761
|
-
await stream.
|
|
698
|
+
await stream.flush();
|
|
762
699
|
console.log(`Success: offset ${offset}`);
|
|
763
700
|
} catch (error) {
|
|
764
701
|
console.error('Ingestion failed:', error);
|
|
765
|
-
|
|
766
|
-
// When stream fails, close it first
|
|
767
|
-
await stream.close();
|
|
768
|
-
console.log('Stream closed after error');
|
|
769
|
-
|
|
770
|
-
// Optional: Inspect what needs recovery (must be called on closed stream)
|
|
771
|
-
const unackedBatches = await stream.getUnackedBatches();
|
|
772
|
-
console.log(`Batches to recover: ${unackedBatches.length}`);
|
|
773
|
-
|
|
774
|
-
// Recommended recovery approach: Use recreateStream()
|
|
775
|
-
// This method:
|
|
776
|
-
// 1. Gets all unacknowledged batches from the failed stream
|
|
777
|
-
// 2. Creates a new stream with the same configuration
|
|
778
|
-
// 3. Re-ingests all unacknowledged batches automatically
|
|
779
|
-
// 4. Returns the new stream ready for continued use
|
|
780
|
-
const newStream = await sdk.recreateStream(stream);
|
|
781
|
-
console.log(`Stream recreated with ${unackedBatches.length} batches re-ingested`);
|
|
782
|
-
|
|
783
|
-
// Continue using newStream for further ingestion
|
|
784
702
|
try {
|
|
785
|
-
|
|
703
|
+
const unackedBatches = await stream.getUnackedBatches();
|
|
704
|
+
console.log(`Batches to recover: ${unackedBatches.length}`);
|
|
705
|
+
replacement = await sdk.recreateStream(stream);
|
|
706
|
+
await replacement.flush();
|
|
707
|
+
} catch (recoveryError) {
|
|
708
|
+
console.error('Stream was not terminal or recovery failed:', recoveryError);
|
|
709
|
+
throw new AggregateError(
|
|
710
|
+
[error, recoveryError],
|
|
711
|
+
'ingestion and recovery both failed',
|
|
712
|
+
);
|
|
786
713
|
} finally {
|
|
787
|
-
|
|
714
|
+
if (replacement) {
|
|
715
|
+
await replacement.close();
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
} finally {
|
|
719
|
+
try {
|
|
720
|
+
await stream.close();
|
|
721
|
+
} catch (closeError) {
|
|
722
|
+
console.error('Failed stream released:', closeError);
|
|
788
723
|
}
|
|
789
724
|
}
|
|
790
725
|
```
|
|
@@ -804,12 +739,14 @@ Main entry point for the SDK.
|
|
|
804
739
|
**Constructor:**
|
|
805
740
|
|
|
806
741
|
```typescript
|
|
807
|
-
new ZerobusSdk(zerobusEndpoint: string, unityCatalogUrl: string)
|
|
742
|
+
new ZerobusSdk(zerobusEndpoint: string, unityCatalogUrl: string, options?: ZerobusSdkOptions)
|
|
808
743
|
```
|
|
809
744
|
|
|
810
745
|
**Parameters:**
|
|
811
746
|
- `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)
|
|
812
747
|
- `unityCatalogUrl` (string) - The Unity Catalog endpoint (your workspace URL)
|
|
748
|
+
- `options` (ZerobusSdkOptions, optional) - Additional SDK configuration:
|
|
749
|
+
- `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.
|
|
813
750
|
|
|
814
751
|
**Methods:**
|
|
815
752
|
|
|
@@ -845,23 +782,29 @@ This method is the **recommended approach** for recovering from stream failures.
|
|
|
845
782
|
4. Returns the new stream ready for continued ingestion
|
|
846
783
|
|
|
847
784
|
**Parameters:**
|
|
848
|
-
- `stream` - The failed
|
|
785
|
+
- `stream` - The terminally failed stream to recreate. Do not call `stream.close()`
|
|
786
|
+
first because the TypeScript wrapper releases its native handle on close.
|
|
849
787
|
|
|
850
788
|
**Returns:** Promise resolving to a new `ZerobusStream` with all unacknowledged batches re-ingested
|
|
851
789
|
|
|
852
790
|
**Example:**
|
|
853
791
|
```typescript
|
|
854
792
|
try {
|
|
855
|
-
await stream.
|
|
793
|
+
await stream.ingestRecordsOffset(batch);
|
|
794
|
+
await stream.flush();
|
|
856
795
|
} catch (error) {
|
|
857
|
-
|
|
858
|
-
// Automatically recreate stream and recover all unacked batches
|
|
796
|
+
// recreateStream() rejects unless the native stream already failed closed.
|
|
859
797
|
const newStream = await sdk.recreateStream(stream);
|
|
860
|
-
|
|
798
|
+
try {
|
|
799
|
+
await newStream.flush();
|
|
800
|
+
} finally {
|
|
801
|
+
await newStream.close();
|
|
802
|
+
}
|
|
861
803
|
}
|
|
862
804
|
```
|
|
863
805
|
|
|
864
|
-
**Note:** This method preserves batch structure and re-ingests batches atomically. For
|
|
806
|
+
**Note:** This method preserves batch structure and re-ingests batches atomically. For
|
|
807
|
+
debugging, inspect `getUnackedBatches()` after a terminal failure and before closing the wrapper.
|
|
865
808
|
|
|
866
809
|
---
|
|
867
810
|
|
|
@@ -875,13 +818,16 @@ Represents an active ingestion stream.
|
|
|
875
818
|
async ingestRecordOffset(payload: Buffer | string | object): Promise<bigint>
|
|
876
819
|
```
|
|
877
820
|
|
|
878
|
-
**(Recommended)** Ingests a single record. The Promise resolves immediately after the record is queued (before server acknowledgment).
|
|
821
|
+
**(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.
|
|
879
822
|
|
|
880
823
|
```typescript
|
|
881
|
-
//
|
|
882
|
-
|
|
883
|
-
const
|
|
884
|
-
await stream.
|
|
824
|
+
// Idiomatic flow: ingest in a loop, then flush once
|
|
825
|
+
let lastOffset: bigint | null = null;
|
|
826
|
+
for (const record of records) {
|
|
827
|
+
lastOffset = await stream.ingestRecordOffset(record); // Resolves immediately
|
|
828
|
+
}
|
|
829
|
+
await stream.flush(); // Resolves once everything queued so far is acknowledged
|
|
830
|
+
// (Or, to confirm a specific record: if (lastOffset !== null) await stream.waitForOffset(lastOffset))
|
|
885
831
|
```
|
|
886
832
|
|
|
887
833
|
---
|
|
@@ -890,7 +836,7 @@ await stream.waitForOffset(offset2); // Waits for server to acknowledge all rec
|
|
|
890
836
|
async ingestRecordsOffset(payloads: Array<Buffer | string | object>): Promise<bigint | null>
|
|
891
837
|
```
|
|
892
838
|
|
|
893
|
-
**(Recommended)** Ingests multiple records as a batch. The Promise resolves immediately after the batch is queued (before server acknowledgment). Returns `null` for empty batches.
|
|
839
|
+
**(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.
|
|
894
840
|
|
|
895
841
|
---
|
|
896
842
|
|
|
@@ -898,7 +844,7 @@ async ingestRecordsOffset(payloads: Array<Buffer | string | object>): Promise<bi
|
|
|
898
844
|
async waitForOffset(offsetId: bigint): Promise<void>
|
|
899
845
|
```
|
|
900
846
|
|
|
901
|
-
Waits for the server to acknowledge all records up to and including the specified offset ID.
|
|
847
|
+
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.
|
|
902
848
|
|
|
903
849
|
---
|
|
904
850
|
|
|
@@ -998,7 +944,7 @@ await stream.ingestRecords(buffers);
|
|
|
998
944
|
async flush(): Promise<void>
|
|
999
945
|
```
|
|
1000
946
|
|
|
1001
|
-
Flushes all pending records and waits for acknowledgments.
|
|
947
|
+
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.
|
|
1002
948
|
|
|
1003
949
|
```typescript
|
|
1004
950
|
async close(): Promise<void>
|
|
@@ -1012,7 +958,8 @@ async getUnackedRecords(): Promise<Buffer[]>
|
|
|
1012
958
|
|
|
1013
959
|
Returns unacknowledged record payloads as a flat array for inspection purposes.
|
|
1014
960
|
|
|
1015
|
-
**Important:**
|
|
961
|
+
**Important:** This can only be called after a terminal stream failure. Do not call
|
|
962
|
+
`stream.close()` first: the TypeScript wrapper releases the underlying stream handle on close.
|
|
1016
963
|
|
|
1017
964
|
**Returns:** Array of Buffer containing the raw record payloads
|
|
1018
965
|
|
|
@@ -1026,7 +973,8 @@ async getUnackedBatches(): Promise<Buffer[][]>
|
|
|
1026
973
|
|
|
1027
974
|
Returns unacknowledged records grouped by their original batches for inspection purposes.
|
|
1028
975
|
|
|
1029
|
-
**Important:**
|
|
976
|
+
**Important:** This can only be called after a terminal stream failure. Do not call
|
|
977
|
+
`stream.close()` first: the TypeScript wrapper releases the underlying stream handle on close.
|
|
1030
978
|
|
|
1031
979
|
**Returns:** Array of arrays, where each inner array represents a batch of records as Buffers
|
|
1032
980
|
|
|
@@ -1039,15 +987,11 @@ try {
|
|
|
1039
987
|
await stream.ingestRecords(batch2);
|
|
1040
988
|
// ... error occurs
|
|
1041
989
|
} catch (error) {
|
|
1042
|
-
await stream.close();
|
|
1043
990
|
const unackedBatches = await stream.getUnackedBatches();
|
|
1044
991
|
// unackedBatches[0] contains records from batch1 (if not acked)
|
|
1045
992
|
// unackedBatches[1] contains records from batch2 (if not acked)
|
|
1046
993
|
|
|
1047
|
-
|
|
1048
|
-
for (const batch of unackedBatches) {
|
|
1049
|
-
await newStream.ingestRecords(batch);
|
|
1050
|
-
}
|
|
994
|
+
console.log(`Batches available for recovery: ${unackedBatches.length}`);
|
|
1051
995
|
}
|
|
1052
996
|
```
|
|
1053
997
|
|
|
@@ -1070,10 +1014,10 @@ interface TableProperties {
|
|
|
1070
1014
|
|
|
1071
1015
|
```typescript
|
|
1072
1016
|
// JSON mode
|
|
1073
|
-
const
|
|
1017
|
+
const jsonTableProperties = { tableName: 'main.default.air_quality' };
|
|
1074
1018
|
|
|
1075
1019
|
// Protocol Buffers mode
|
|
1076
|
-
const
|
|
1020
|
+
const protoTableProperties = {
|
|
1077
1021
|
tableName: 'main.default.air_quality',
|
|
1078
1022
|
descriptorProto: descriptorBase64 // Required for protobuf
|
|
1079
1023
|
};
|
|
@@ -1090,7 +1034,7 @@ Configuration options for stream behavior.
|
|
|
1090
1034
|
```typescript
|
|
1091
1035
|
interface StreamConfigurationOptions {
|
|
1092
1036
|
recordType?: RecordType; // RecordType.Json or RecordType.Proto. Default: RecordType.Proto
|
|
1093
|
-
maxInflightRequests?: number; // Default:
|
|
1037
|
+
maxInflightRequests?: number; // Default: 1,000,000
|
|
1094
1038
|
recovery?: boolean; // Default: true
|
|
1095
1039
|
recoveryTimeoutMs?: number; // Default: 15,000
|
|
1096
1040
|
recoveryBackoffMs?: number; // Default: 2,000
|
|
@@ -1110,11 +1054,12 @@ enum RecordType {
|
|
|
1110
1054
|
|
|
1111
1055
|
1. **Reuse SDK instances**: Create one `ZerobusSdk` instance per application
|
|
1112
1056
|
2. **Stream lifecycle**: Always close streams in a `finally` block to ensure all records are flushed
|
|
1113
|
-
3. **Batch size**: Adjust `maxInflightRequests` based on your throughput requirements (default:
|
|
1057
|
+
3. **Batch size**: Adjust `maxInflightRequests` based on your throughput requirements (default: 1,000,000)
|
|
1114
1058
|
4. **Error handling**: The stream handles errors internally with automatic retry. Only use `recreateStream()` for persistent failures after internal retries are exhausted.
|
|
1115
1059
|
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.
|
|
1116
1060
|
6. **Store credentials securely**: Use environment variables, never hardcode credentials
|
|
1117
1061
|
7. **Use batch ingestion**: For high-throughput scenarios, use `ingestRecordsOffset()` instead of individual `ingestRecordOffset()` calls
|
|
1062
|
+
8. **Ingest in a loop, then `flush()`**: See [Acknowledgments and throughput](#acknowledgments-and-throughput) above for the full explanation.
|
|
1118
1063
|
|
|
1119
1064
|
## Platform Support
|
|
1120
1065
|
|
|
@@ -1161,7 +1106,7 @@ This SDK wraps the high-performance [Rust Zerobus SDK](https://github.com/databr
|
|
|
1161
1106
|
**Benefits:**
|
|
1162
1107
|
- **Native performance** - Rust implementation for high-throughput ingestion
|
|
1163
1108
|
- **Native async/await support** - Rust futures become JavaScript Promises
|
|
1164
|
-
- **Automatic memory management**
|
|
1109
|
+
- **Automatic memory management** for native objects. You still must `await stream.close()` to flush and release the stream.
|
|
1165
1110
|
- **Type safety** - Compile-time checks on both sides
|
|
1166
1111
|
|
|
1167
1112
|
## Community and Contributing
|