@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/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:
|
|
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
|
-
*
|
|
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
|
|
95
|
+
* by implementing a getHeadersCallback() function.
|
|
95
96
|
*/
|
|
96
97
|
export interface JsHeadersProvider {
|
|
97
|
-
/** JavaScript function: () =>
|
|
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
|
|
123
|
-
*
|
|
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
|
-
* //
|
|
226
|
-
*
|
|
227
|
-
* const
|
|
228
|
-
* //
|
|
229
|
-
*
|
|
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
|
-
* //
|
|
253
|
-
*
|
|
254
|
-
*
|
|
255
|
-
* await stream.
|
|
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
|
-
*
|
|
265
|
-
*
|
|
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
|
-
*
|
|
306
|
+
* let lastOffset: bigint | null = null;
|
|
280
307
|
* for (const record of records) {
|
|
281
|
-
*
|
|
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(
|
|
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.
|
|
344
|
-
* await stream.
|
|
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:
|
|
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
|
|
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.
|
|
508
|
+
* await stream.ingestRecordsOffset(batch);
|
|
509
|
+
* await stream.flush();
|
|
476
510
|
* } catch (error) {
|
|
477
|
-
*
|
|
478
|
-
*
|
|
479
|
-
*
|
|
480
|
-
*
|
|
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
|
|
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
|
|
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
|
|
71
|
-
"@databricks/zerobus-ingest-sdk-linux-arm64-gnu": "1.0
|
|
72
|
-
"@databricks/zerobus-ingest-sdk-win32-x64-msvc": "1.0
|
|
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
|
-
"
|
|
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": "^
|
|
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
|
-
"
|
|
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
|
}
|
package/src/headers_provider.ts
CHANGED
|
@@ -1,69 +1,57 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Custom headers provider accepted by `createStream()`.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
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
|
-
|
|
17
|
+
getHeadersCallback: () => Array<[string, string]>;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
/**
|
|
21
21
|
* OAuth 2.0 Client Credentials headers provider.
|
|
22
22
|
*
|
|
23
|
-
*
|
|
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
|
|
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
|
-
*
|
|
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,
|
|
39
|
-
* clientSecret,
|
|
32
|
+
* clientId,
|
|
33
|
+
* clientSecret,
|
|
40
34
|
* options
|
|
41
|
-
* // No headers_provider parameter = OAuth authentication
|
|
42
35
|
* );
|
|
43
36
|
* ```
|
|
44
37
|
*
|
|
45
|
-
*
|
|
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
|
-
* '',
|
|
60
|
-
* '',
|
|
42
|
+
* '',
|
|
43
|
+
* '',
|
|
61
44
|
* options,
|
|
62
|
-
* {
|
|
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
|
|
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
|
|
79
|
-
'To use custom authentication:
|
|
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
|
}
|