@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/index.d.ts CHANGED
@@ -22,7 +22,7 @@ export const enum RecordType {
22
22
  export interface StreamConfigurationOptions {
23
23
  /**
24
24
  * Maximum number of unacknowledged requests that can be in flight.
25
- * Default: 10,000
25
+ * Default: 1,000,000
26
26
  */
27
27
  maxInflightRequests?: number
28
28
  /**
@@ -83,7 +83,8 @@ export interface TableProperties {
83
83
  tableName: string
84
84
  /**
85
85
  * Optional Protocol Buffer descriptor as a base64-encoded string.
86
- * If not provided, JSON encoding will be used.
86
+ * Omitting this does not select JSON. The stream defaults to Protocol Buffers
87
+ * unless `record_type` is set to JSON.
87
88
  */
88
89
  descriptorProto?: string
89
90
  }
@@ -91,12 +92,16 @@ export interface TableProperties {
91
92
  * JavaScript headers provider callback wrapper.
92
93
  *
93
94
  * Allows TypeScript code to provide custom authentication headers
94
- * by implementing a getHeaders() function.
95
+ * by implementing a getHeadersCallback() function.
95
96
  */
96
97
  export interface JsHeadersProvider {
97
- /** JavaScript function: () => Promise<Array<[string, string]>> */
98
+ /** JavaScript function: () => Array<[string, string]> */
98
99
  getHeadersCallback: (...args: any[]) => any
99
100
  }
101
+ export interface ZerobusSdkOptions {
102
+ /** Identifier appended to the `user-agent` header */
103
+ applicationName?: string
104
+ }
100
105
  /**
101
106
  * Custom error type for Zerobus operations.
102
107
  *
@@ -119,8 +124,8 @@ export declare class ZerobusError {
119
124
  *
120
125
  * ```typescript
121
126
  * const stream = await sdk.createStream(tableProps, clientId, clientSecret, options);
122
- * const ackPromise = await stream.ingestRecord(Buffer.from([1, 2, 3]));
123
- * const offset = await ackPromise;
127
+ * const offset = await stream.ingestRecordOffset(Buffer.from([1, 2, 3]));
128
+ * await stream.flush();
124
129
  * await stream.close();
125
130
  * ```
126
131
  */
@@ -210,6 +215,13 @@ export declare class ZerobusStream {
210
215
  * This is the recommended API for high-throughput scenarios where you want to
211
216
  * decouple record ingestion from acknowledgment tracking.
212
217
  *
218
+ * **Acknowledgments:** the idiomatic flow is to ingest in a loop and then `flush()`
219
+ * once to confirm everything queued so far. The returned offset, together with
220
+ * `waitForOffset()`, lets you confirm a specific record when you need it (acks are
221
+ * ordered, so the last offset confirms the whole run) — prefer `flush()` for bulk.
222
+ * Avoid calling `waitForOffset()` after every record in a tight loop, since that
223
+ * limits throughput to one record per round-trip.
224
+ *
213
225
  * # Arguments
214
226
  *
215
227
  * * `payload` - The record data (Buffer, string, protobuf message, or plain object)
@@ -222,11 +234,14 @@ export declare class ZerobusStream {
222
234
  * # Example
223
235
  *
224
236
  * ```typescript
225
- * // Promise resolves immediately with offset (before server ack)
226
- * const offset1 = await stream.ingestRecordOffset(record1);
227
- * const offset2 = await stream.ingestRecordOffset(record2);
228
- * // Wait for both to be acknowledged
229
- * await stream.waitForOffset(offset2);
237
+ * // High-throughput pattern: ingest in a loop, wait once at the end.
238
+ * let lastOffset: bigint | null = null;
239
+ * for (const record of records) {
240
+ * lastOffset = await stream.ingestRecordOffset(record); // resolves on queue, no round-trip
241
+ * }
242
+ * // The ack watermark is monotonic: waiting on the last offset confirms all prior records.
243
+ * if (lastOffset !== null) await stream.waitForOffset(lastOffset);
244
+ * // Or simply: await stream.flush();
230
245
  * ```
231
246
  */
232
247
  ingestRecordOffset(payload: unknown): Promise<bigint>
@@ -237,6 +252,12 @@ export declare class ZerobusStream {
237
252
  * the batch is queued, without waiting for server acknowledgment. Use
238
253
  * `waitForOffset()` to wait for acknowledgment when needed.
239
254
  *
255
+ * **Acknowledgments:** the idiomatic flow is to ingest your batches in a loop and
256
+ * then `flush()` once to confirm. The returned offset, together with `waitForOffset()`,
257
+ * confirms a specific batch when you need it (acks are ordered, so the last offset
258
+ * confirms the whole run) — prefer `flush()` for bulk. Avoid calling `waitForOffset()`
259
+ * after every batch in a tight loop, since that limits throughput to one round-trip per batch.
260
+ *
240
261
  * # Arguments
241
262
  *
242
263
  * * `records` - Array of record data
@@ -249,20 +270,26 @@ export declare class ZerobusStream {
249
270
  * # Example
250
271
  *
251
272
  * ```typescript
252
- * // Promise resolves immediately with offset (before server ack)
253
- * const offset = await stream.ingestRecordsOffset(batch);
254
- * if (offset !== null) {
255
- * await stream.waitForOffset(offset);
273
+ * // Ingest many batches without waiting, then flush once.
274
+ * let lastOffset = null;
275
+ * for (const batch of batches) {
276
+ * const offset = await stream.ingestRecordsOffset(batch); // resolves on queue
277
+ * if (offset !== null) lastOffset = offset;
256
278
  * }
279
+ * if (lastOffset !== null) await stream.waitForOffset(lastOffset);
280
+ * // Or simply: await stream.flush();
257
281
  * ```
258
282
  */
259
283
  ingestRecordsOffset(records: Array<unknown>): Promise<bigint | null>
260
284
  /**
261
285
  * Waits for a specific offset to be acknowledged by the server.
262
286
  *
263
- * Use this method with `ingestRecordOffset()` and `ingestRecordsOffset()` to
264
- * selectively wait for acknowledgments. This allows you to ingest many records
265
- * quickly and then wait only for specific offsets when needed.
287
+ * Use this method with `ingestRecordOffset()` and `ingestRecordsOffset()` to confirm
288
+ * a specific record before continuing. Acks are ordered, so waiting on the LAST offset
289
+ * confirms every prior record too you never need to wait on intermediate offsets.
290
+ * For confirming a bulk run, `flush()` is usually simpler; reach for `waitForOffset()`
291
+ * when one particular record must be confirmed. Avoid calling it after every record in
292
+ * a tight loop, since that limits throughput to one record per round-trip.
266
293
  *
267
294
  * # Arguments
268
295
  *
@@ -276,12 +303,12 @@ export declare class ZerobusStream {
276
303
  * # Example
277
304
  *
278
305
  * ```typescript
279
- * const offsets = [];
306
+ * let lastOffset: bigint | null = null;
280
307
  * for (const record of records) {
281
- * offsets.push(await stream.ingestRecordOffset(record));
308
+ * lastOffset = await stream.ingestRecordOffset(record); // no per-record wait
282
309
  * }
283
- * // Wait for the last offset (implies all previous are also acknowledged)
284
- * await stream.waitForOffset(offsets[offsets.length - 1]);
310
+ * // Wait for the last offset only (implies all previous are also acknowledged).
311
+ * if (lastOffset !== null) await stream.waitForOffset(lastOffset);
285
312
  * ```
286
313
  */
287
314
  waitForOffset(offsetId: bigint): Promise<void>
@@ -291,6 +318,11 @@ export declare class ZerobusStream {
291
318
  * This method ensures all previously ingested records have been sent to the server
292
319
  * and acknowledged. It's useful for checkpointing or ensuring data durability.
293
320
  *
321
+ * This is the idiomatic way to confirm records ingested via `ingestRecordOffset()` /
322
+ * `ingestRecordsOffset()`: ingest in a loop, then `flush()` once (for a bounded batch,
323
+ * or periodically for a long-running stream). It resolves once everything queued so
324
+ * far is acknowledged.
325
+ *
294
326
  * # Errors
295
327
  *
296
328
  * - Timeout errors if flush takes longer than configured timeout
@@ -340,15 +372,12 @@ export declare class ZerobusStream {
340
372
  *
341
373
  * ```typescript
342
374
  * try {
343
- * await stream.ingestRecords(batch1);
344
- * await stream.ingestRecords(batch2);
375
+ * await stream.ingestRecordsOffset(batch1);
376
+ * await stream.ingestRecordsOffset(batch2);
377
+ * await stream.flush();
345
378
  * } catch (error) {
346
379
  * const unackedBatches = await stream.getUnackedBatches();
347
- *
348
- * // Re-ingest with new stream
349
- * for (const batch of unackedBatches) {
350
- * await newStream.ingestRecords(batch);
351
- * }
380
+ * console.log(`Batches available for recovery: ${unackedBatches.length}`);
352
381
  * }
353
382
  * ```
354
383
  */
@@ -364,7 +393,8 @@ export declare class ZerobusStream {
364
393
  * ```typescript
365
394
  * const sdk = new ZerobusSdk(
366
395
  * "https://workspace-id.zerobus.region.cloud.databricks.com",
367
- * "https://workspace.cloud.databricks.com"
396
+ * "https://workspace.cloud.databricks.com",
397
+ * { applicationName: "my-app/1.0" }
368
398
  * );
369
399
  *
370
400
  * const stream = await sdk.createStream(
@@ -384,13 +414,15 @@ export declare class ZerobusSdk {
384
414
  * (e.g., "https://workspace-id.zerobus.region.cloud.databricks.com")
385
415
  * * `unity_catalog_url` - The Unity Catalog endpoint URL
386
416
  * (e.g., "https://workspace.cloud.databricks.com")
417
+ * * `options` - Optional SDK configuration (see `ZerobusSdkOptions`),
418
+ * including `applicationName` for server-side attribution.
387
419
  *
388
420
  * # Errors
389
421
  *
390
422
  * - Invalid endpoint URLs
391
423
  * - Failed to extract workspace ID from the endpoint
392
424
  */
393
- constructor(zerobusEndpoint: string, unityCatalogUrl: string)
425
+ constructor(zerobusEndpoint: string, unityCatalogUrl: string, options?: ZerobusSdkOptions | undefined | null)
394
426
  /**
395
427
  * Creates a new ingestion stream to a Delta table.
396
428
  *
@@ -436,7 +468,7 @@ export declare class ZerobusSdk {
436
468
  * "", // ignored
437
469
  * undefined,
438
470
  * {
439
- * getHeadersCallback: async () => [
471
+ * getHeadersCallback: () => [
440
472
  * ["authorization", `Bearer ${myToken}`],
441
473
  * ["x-databricks-zerobus-table-name", tableName]
442
474
  * ]
@@ -456,7 +488,8 @@ export declare class ZerobusSdk {
456
488
  *
457
489
  * # Arguments
458
490
  *
459
- * * `stream` - The failed or closed stream to recreate
491
+ * * `stream` - The terminally failed stream to recreate. The TypeScript wrapper
492
+ * must not have been closed because `close()` releases its native handle.
460
493
  *
461
494
  * # Returns
462
495
  *
@@ -472,12 +505,23 @@ export declare class ZerobusSdk {
472
505
  *
473
506
  * ```typescript
474
507
  * try {
475
- * await stream.ingestRecords(batch);
508
+ * await stream.ingestRecordsOffset(batch);
509
+ * await stream.flush();
476
510
  * } catch (error) {
477
- * await stream.close();
478
- * // Recreate stream with all unacked batches re-ingested
479
- * const newStream = await sdk.recreateStream(stream);
480
- * // Continue ingesting with newStream
511
+ * try {
512
+ * const newStream = await sdk.recreateStream(stream);
513
+ * try {
514
+ * await newStream.flush();
515
+ * } finally {
516
+ * await newStream.close();
517
+ * }
518
+ * } finally {
519
+ * try {
520
+ * await stream.close();
521
+ * } catch (closeError) {
522
+ * console.error("Failed stream released:", closeError);
523
+ * }
524
+ * }
481
525
  * }
482
526
  * ```
483
527
  */
package/package.json CHANGED
@@ -1,9 +1,19 @@
1
1
  {
2
2
  "name": "@databricks/zerobus-ingest-sdk",
3
- "version": "1.0.2",
3
+ "version": "1.2.0",
4
4
  "description": "TypeScript/Node.js SDK for streaming data ingestion into Databricks Delta tables using Zerobus",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
7
+ "typesVersions": {
8
+ "*": {
9
+ "utils/descriptor": [
10
+ "utils/descriptor.d.ts"
11
+ ],
12
+ "utils/descriptor.js": [
13
+ "utils/descriptor.d.ts"
14
+ ]
15
+ }
16
+ },
7
17
  "keywords": [
8
18
  "databricks",
9
19
  "delta",
@@ -63,22 +73,22 @@
63
73
  "example:json:batch": "tsx examples/json/batch.ts",
64
74
  "example:proto:single": "tsx examples/proto/single.ts",
65
75
  "example:proto:batch": "tsx examples/proto/batch.ts",
66
- "example:arrow:single": "tsx examples/arrow/single.ts",
67
- "example:arrow:batch": "tsx examples/arrow/batch.ts"
76
+ "example:arrow": "tsx examples/arrow/batch.ts"
68
77
  },
69
78
  "optionalDependencies": {
70
- "@databricks/zerobus-ingest-sdk-linux-x64-gnu": "1.0.2",
71
- "@databricks/zerobus-ingest-sdk-linux-arm64-gnu": "1.0.2",
72
- "@databricks/zerobus-ingest-sdk-win32-x64-msvc": "1.0.2"
79
+ "@databricks/zerobus-ingest-sdk-linux-x64-gnu": "1.2.0",
80
+ "@databricks/zerobus-ingest-sdk-linux-arm64-gnu": "1.2.0",
81
+ "@databricks/zerobus-ingest-sdk-win32-x64-msvc": "1.2.0",
82
+ "@databricks/zerobus-ingest-sdk-darwin-x64": "1.2.0",
83
+ "@databricks/zerobus-ingest-sdk-darwin-arm64": "1.2.0"
84
+ },
85
+ "dependencies": {
86
+ "protobufjs": "^8.7.1"
73
87
  },
74
88
  "peerDependencies": {
75
- "protobufjs": "^7.0.0",
76
- "apache-arrow": "^56.0.0"
89
+ "apache-arrow": "^18.0.0"
77
90
  },
78
91
  "peerDependenciesMeta": {
79
- "protobufjs": {
80
- "optional": true
81
- },
82
92
  "apache-arrow": {
83
93
  "optional": true
84
94
  }
@@ -88,12 +98,18 @@
88
98
  "@types/node": "^20.0.0",
89
99
  "apache-arrow": "^18.1.0",
90
100
  "dotenv": "^17.2.3",
91
- "protobufjs": "^7.5.4",
92
- "protobufjs-cli": "^2.0.0",
101
+ "protobufjs-cli": "^2.6.1",
93
102
  "tsx": "^4.21.0",
94
103
  "typescript": "^5.3.0"
95
104
  },
96
105
  "overrides": {
97
- "glob": "^10.0.0"
106
+ "@protobufjs/utf8": "^1.1.1",
107
+ "brace-expansion": "^2.1.2",
108
+ "glob": "^10.0.0",
109
+ "lodash": "^4.18.0",
110
+ "markdown-it": "^14.2.0",
111
+ "minimatch": "^9.0.7",
112
+ "tmp": "^0.2.6",
113
+ "underscore": "^1.13.8"
98
114
  }
99
115
  }
@@ -1,69 +1,57 @@
1
1
  /**
2
- * Interface for providing custom headers to Zerobus streams.
2
+ * Custom headers provider accepted by `createStream()`.
3
3
  *
4
- * Implement this interface to use custom authentication beyond OAuth,
5
- * such as Personal Access Tokens (PAT) or custom auth tokens.
4
+ * The native adapter invokes `getHeadersCallback` synchronously once during
5
+ * stream creation and stores the returned tuples. Returning a Promise, or
6
+ * passing a class with async `getHeaders()`, is not supported and can terminate
7
+ * the process. Token refresh is not currently wired through this callback.
6
8
  */
7
9
  export interface HeadersProvider {
8
10
  /**
9
- * Returns headers as array of [name, value] tuples.
11
+ * Returns headers as an array of [name, value] tuples.
10
12
  *
11
13
  * Required headers:
12
14
  * - ["authorization", "Bearer <token>"]
13
15
  * - ["x-databricks-zerobus-table-name", "<table_name>"]
14
- *
15
- * @returns Promise resolving to array of header name-value pairs
16
16
  */
17
- getHeaders(): Promise<Array<[string, string]>>;
17
+ getHeadersCallback: () => Array<[string, string]>;
18
18
  }
19
19
 
20
20
  /**
21
21
  * OAuth 2.0 Client Credentials headers provider.
22
22
  *
23
- * **IMPORTANT: DO NOT instantiate this class directly.**
23
+ * Do not instantiate this class or pass it to `createStream()`.
24
24
  *
25
25
  * OAuth authentication is handled automatically by the Rust SDK when you call
26
- * `createStream()` with clientId and clientSecret parameters (without providing
27
- * a headers_provider).
28
- *
29
- * This class exists for:
30
- * 1. Documentation purposes - showing the HeadersProvider pattern
31
- * 2. API consistency with other Zerobus SDKs (Python, Java, Rust)
26
+ * `createStream()` with clientId and clientSecret and omit the headers provider.
32
27
  *
33
- * **How to use OAuth authentication:**
28
+ * How to use OAuth authentication:
34
29
  * ```typescript
35
- * // OAuth is the default - just pass clientId and clientSecret
36
30
  * const stream = await sdk.createStream(
37
31
  * tableProperties,
38
- * clientId, // OAuth client ID
39
- * clientSecret, // OAuth client secret
32
+ * clientId,
33
+ * clientSecret,
40
34
  * options
41
- * // No headers_provider parameter = OAuth authentication
42
35
  * );
43
36
  * ```
44
37
  *
45
- * **How to use custom authentication (PAT, etc.):**
38
+ * How to use custom authentication (PAT or a static token):
46
39
  * ```typescript
47
- * class CustomHeadersProvider implements HeadersProvider {
48
- * async getHeaders() {
49
- * return [
50
- * ["authorization", `Bearer ${myToken}`],
51
- * ["x-databricks-zerobus-table-name", tableName]
52
- * ];
53
- * }
54
- * }
55
- *
56
- * const provider = new CustomHeadersProvider();
57
40
  * const stream = await sdk.createStream(
58
41
  * tableProperties,
59
- * '', // ignored
60
- * '', // ignored
42
+ * '',
43
+ * '',
61
44
  * options,
62
- * { getHeadersCallback: provider.getHeaders.bind(provider) }
45
+ * {
46
+ * getHeadersCallback: () => [
47
+ * ["authorization", `Bearer ${myToken}`],
48
+ * ["x-databricks-zerobus-table-name", tableName]
49
+ * ]
50
+ * }
63
51
  * );
64
52
  * ```
65
53
  */
66
- export class OAuthHeadersProvider implements HeadersProvider {
54
+ export class OAuthHeadersProvider {
67
55
  constructor(
68
56
  private clientId: string,
69
57
  private clientSecret: string,
@@ -75,8 +63,8 @@ export class OAuthHeadersProvider implements HeadersProvider {
75
63
  throw new Error(
76
64
  'OAuthHeadersProvider should not be instantiated directly. ' +
77
65
  'OAuth authentication is handled internally by the Rust SDK. ' +
78
- 'To use OAuth: call createStream(tableProperties, clientId, clientSecret, options) without the headers_provider parameter. ' +
79
- 'To use custom authentication: implement the HeadersProvider interface.'
66
+ 'To use OAuth: call createStream(tableProperties, clientId, clientSecret, options) without a headers provider. ' +
67
+ 'To use custom authentication: pass { getHeadersCallback: () => [...] } as the headers provider.'
80
68
  );
81
69
  }
82
70
  }