@databricks/zerobus-ingest-sdk 1.1.0 → 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 +82 -97
- package/Cargo.toml +5 -12
- package/README.md +147 -203
- package/index.d.ts +82 -38
- package/package.json +28 -13
- package/src/headers_provider.ts +24 -36
- package/src/lib.rs +113 -61
- package/utils/descriptor.d.ts +32 -0
- package/utils/descriptor.js +55 -0
- package/utils/descriptor.ts +2 -2
package/src/lib.rs
CHANGED
|
@@ -50,7 +50,7 @@ pub enum RecordType {
|
|
|
50
50
|
#[napi(object)]
|
|
51
51
|
pub struct StreamConfigurationOptions {
|
|
52
52
|
/// Maximum number of unacknowledged requests that can be in flight.
|
|
53
|
-
/// Default:
|
|
53
|
+
/// Default: 1,000,000
|
|
54
54
|
pub max_inflight_requests: Option<u32>,
|
|
55
55
|
|
|
56
56
|
/// Enable automatic stream recovery on transient failures.
|
|
@@ -103,7 +103,8 @@ pub struct TableProperties {
|
|
|
103
103
|
pub table_name: String,
|
|
104
104
|
|
|
105
105
|
/// Optional Protocol Buffer descriptor as a base64-encoded string.
|
|
106
|
-
///
|
|
106
|
+
/// Omitting this does not select JSON. The stream defaults to Protocol Buffers
|
|
107
|
+
/// unless `record_type` is set to JSON.
|
|
107
108
|
pub descriptor_proto: Option<String>,
|
|
108
109
|
}
|
|
109
110
|
|
|
@@ -220,8 +221,8 @@ fn convert_js_to_record_payload(env: &Env, payload: Unknown) -> Result<RustRecor
|
|
|
220
221
|
///
|
|
221
222
|
/// ```typescript
|
|
222
223
|
/// const stream = await sdk.createStream(tableProps, clientId, clientSecret, options);
|
|
223
|
-
/// const
|
|
224
|
-
///
|
|
224
|
+
/// const offset = await stream.ingestRecordOffset(Buffer.from([1, 2, 3]));
|
|
225
|
+
/// await stream.flush();
|
|
225
226
|
/// await stream.close();
|
|
226
227
|
/// ```
|
|
227
228
|
#[napi]
|
|
@@ -400,6 +401,13 @@ impl ZerobusStream {
|
|
|
400
401
|
/// This is the recommended API for high-throughput scenarios where you want to
|
|
401
402
|
/// decouple record ingestion from acknowledgment tracking.
|
|
402
403
|
///
|
|
404
|
+
/// **Acknowledgments:** the idiomatic flow is to ingest in a loop and then `flush()`
|
|
405
|
+
/// once to confirm everything queued so far. The returned offset, together with
|
|
406
|
+
/// `waitForOffset()`, lets you confirm a specific record when you need it (acks are
|
|
407
|
+
/// ordered, so the last offset confirms the whole run) — prefer `flush()` for bulk.
|
|
408
|
+
/// Avoid calling `waitForOffset()` after every record in a tight loop, since that
|
|
409
|
+
/// limits throughput to one record per round-trip.
|
|
410
|
+
///
|
|
403
411
|
/// # Arguments
|
|
404
412
|
///
|
|
405
413
|
/// * `payload` - The record data (Buffer, string, protobuf message, or plain object)
|
|
@@ -412,11 +420,14 @@ impl ZerobusStream {
|
|
|
412
420
|
/// # Example
|
|
413
421
|
///
|
|
414
422
|
/// ```typescript
|
|
415
|
-
/// //
|
|
416
|
-
///
|
|
417
|
-
/// const
|
|
418
|
-
/// //
|
|
419
|
-
///
|
|
423
|
+
/// // High-throughput pattern: ingest in a loop, wait once at the end.
|
|
424
|
+
/// let lastOffset: bigint | null = null;
|
|
425
|
+
/// for (const record of records) {
|
|
426
|
+
/// lastOffset = await stream.ingestRecordOffset(record); // resolves on queue, no round-trip
|
|
427
|
+
/// }
|
|
428
|
+
/// // The ack watermark is monotonic: waiting on the last offset confirms all prior records.
|
|
429
|
+
/// if (lastOffset !== null) await stream.waitForOffset(lastOffset);
|
|
430
|
+
/// // Or simply: await stream.flush();
|
|
420
431
|
/// ```
|
|
421
432
|
#[napi(ts_return_type = "Promise<bigint>")]
|
|
422
433
|
pub fn ingest_record_offset(&self, env: Env, payload: Unknown) -> Result<JsObject> {
|
|
@@ -454,6 +465,12 @@ impl ZerobusStream {
|
|
|
454
465
|
/// the batch is queued, without waiting for server acknowledgment. Use
|
|
455
466
|
/// `waitForOffset()` to wait for acknowledgment when needed.
|
|
456
467
|
///
|
|
468
|
+
/// **Acknowledgments:** the idiomatic flow is to ingest your batches in a loop and
|
|
469
|
+
/// then `flush()` once to confirm. The returned offset, together with `waitForOffset()`,
|
|
470
|
+
/// confirms a specific batch when you need it (acks are ordered, so the last offset
|
|
471
|
+
/// confirms the whole run) — prefer `flush()` for bulk. Avoid calling `waitForOffset()`
|
|
472
|
+
/// after every batch in a tight loop, since that limits throughput to one round-trip per batch.
|
|
473
|
+
///
|
|
457
474
|
/// # Arguments
|
|
458
475
|
///
|
|
459
476
|
/// * `records` - Array of record data
|
|
@@ -466,11 +483,14 @@ impl ZerobusStream {
|
|
|
466
483
|
/// # Example
|
|
467
484
|
///
|
|
468
485
|
/// ```typescript
|
|
469
|
-
/// //
|
|
470
|
-
///
|
|
471
|
-
///
|
|
472
|
-
/// await stream.
|
|
486
|
+
/// // Ingest many batches without waiting, then flush once.
|
|
487
|
+
/// let lastOffset = null;
|
|
488
|
+
/// for (const batch of batches) {
|
|
489
|
+
/// const offset = await stream.ingestRecordsOffset(batch); // resolves on queue
|
|
490
|
+
/// if (offset !== null) lastOffset = offset;
|
|
473
491
|
/// }
|
|
492
|
+
/// if (lastOffset !== null) await stream.waitForOffset(lastOffset);
|
|
493
|
+
/// // Or simply: await stream.flush();
|
|
474
494
|
/// ```
|
|
475
495
|
#[napi(ts_return_type = "Promise<bigint | null>")]
|
|
476
496
|
pub fn ingest_records_offset(&self, env: Env, records: Vec<Unknown>) -> Result<JsObject> {
|
|
@@ -511,9 +531,12 @@ impl ZerobusStream {
|
|
|
511
531
|
|
|
512
532
|
/// Waits for a specific offset to be acknowledged by the server.
|
|
513
533
|
///
|
|
514
|
-
/// Use this method with `ingestRecordOffset()` and `ingestRecordsOffset()` to
|
|
515
|
-
///
|
|
516
|
-
///
|
|
534
|
+
/// Use this method with `ingestRecordOffset()` and `ingestRecordsOffset()` to confirm
|
|
535
|
+
/// a specific record before continuing. Acks are ordered, so waiting on the LAST offset
|
|
536
|
+
/// confirms every prior record too — you never need to wait on intermediate offsets.
|
|
537
|
+
/// For confirming a bulk run, `flush()` is usually simpler; reach for `waitForOffset()`
|
|
538
|
+
/// when one particular record must be confirmed. Avoid calling it after every record in
|
|
539
|
+
/// a tight loop, since that limits throughput to one record per round-trip.
|
|
517
540
|
///
|
|
518
541
|
/// # Arguments
|
|
519
542
|
///
|
|
@@ -527,12 +550,12 @@ impl ZerobusStream {
|
|
|
527
550
|
/// # Example
|
|
528
551
|
///
|
|
529
552
|
/// ```typescript
|
|
530
|
-
///
|
|
553
|
+
/// let lastOffset: bigint | null = null;
|
|
531
554
|
/// for (const record of records) {
|
|
532
|
-
///
|
|
555
|
+
/// lastOffset = await stream.ingestRecordOffset(record); // no per-record wait
|
|
533
556
|
/// }
|
|
534
|
-
/// // Wait for the last offset (implies all previous are also acknowledged)
|
|
535
|
-
/// await stream.waitForOffset(
|
|
557
|
+
/// // Wait for the last offset only (implies all previous are also acknowledged).
|
|
558
|
+
/// if (lastOffset !== null) await stream.waitForOffset(lastOffset);
|
|
536
559
|
/// ```
|
|
537
560
|
#[napi(ts_return_type = "Promise<void>")]
|
|
538
561
|
pub fn wait_for_offset(&self, env: Env, offset_id: BigInt) -> Result<JsObject> {
|
|
@@ -560,6 +583,11 @@ impl ZerobusStream {
|
|
|
560
583
|
/// This method ensures all previously ingested records have been sent to the server
|
|
561
584
|
/// and acknowledged. It's useful for checkpointing or ensuring data durability.
|
|
562
585
|
///
|
|
586
|
+
/// This is the idiomatic way to confirm records ingested via `ingestRecordOffset()` /
|
|
587
|
+
/// `ingestRecordsOffset()`: ingest in a loop, then `flush()` once (for a bounded batch,
|
|
588
|
+
/// or periodically for a long-running stream). It resolves once everything queued so
|
|
589
|
+
/// far is acknowledged.
|
|
590
|
+
///
|
|
563
591
|
/// # Errors
|
|
564
592
|
///
|
|
565
593
|
/// - Timeout errors if flush takes longer than configured timeout
|
|
@@ -646,15 +674,12 @@ impl ZerobusStream {
|
|
|
646
674
|
///
|
|
647
675
|
/// ```typescript
|
|
648
676
|
/// try {
|
|
649
|
-
/// await stream.
|
|
650
|
-
/// await stream.
|
|
677
|
+
/// await stream.ingestRecordsOffset(batch1);
|
|
678
|
+
/// await stream.ingestRecordsOffset(batch2);
|
|
679
|
+
/// await stream.flush();
|
|
651
680
|
/// } catch (error) {
|
|
652
681
|
/// const unackedBatches = await stream.getUnackedBatches();
|
|
653
|
-
///
|
|
654
|
-
/// // Re-ingest with new stream
|
|
655
|
-
/// for (const batch of unackedBatches) {
|
|
656
|
-
/// await newStream.ingestRecords(batch);
|
|
657
|
-
/// }
|
|
682
|
+
/// console.log(`Batches available for recovery: ${unackedBatches.length}`);
|
|
658
683
|
/// }
|
|
659
684
|
/// ```
|
|
660
685
|
#[napi]
|
|
@@ -687,10 +712,10 @@ impl ZerobusStream {
|
|
|
687
712
|
/// JavaScript headers provider callback wrapper.
|
|
688
713
|
///
|
|
689
714
|
/// Allows TypeScript code to provide custom authentication headers
|
|
690
|
-
/// by implementing a
|
|
715
|
+
/// by implementing a getHeadersCallback() function.
|
|
691
716
|
#[napi(object)]
|
|
692
717
|
pub struct JsHeadersProvider {
|
|
693
|
-
/// JavaScript function: () =>
|
|
718
|
+
/// JavaScript function: () => Array<[string, string]>
|
|
694
719
|
pub get_headers_callback: JsFunction,
|
|
695
720
|
}
|
|
696
721
|
|
|
@@ -721,11 +746,6 @@ impl StaticHeadersProvider {
|
|
|
721
746
|
));
|
|
722
747
|
}
|
|
723
748
|
|
|
724
|
-
// Add TS user agent if not provided
|
|
725
|
-
if !map.contains_key("user-agent") {
|
|
726
|
-
map.insert("user-agent", TS_SDK_USER_AGENT.to_string());
|
|
727
|
-
}
|
|
728
|
-
|
|
729
749
|
Ok(Self { headers: map })
|
|
730
750
|
}
|
|
731
751
|
}
|
|
@@ -810,11 +830,17 @@ impl RustHeadersProvider for TsOAuthHeadersProvider {
|
|
|
810
830
|
let mut headers = HashMap::new();
|
|
811
831
|
headers.insert("authorization", format!("Bearer {}", token));
|
|
812
832
|
headers.insert("x-databricks-zerobus-table-name", self.table_name.clone());
|
|
813
|
-
headers.insert("user-agent", TS_SDK_USER_AGENT.to_string());
|
|
814
833
|
Ok(headers)
|
|
815
834
|
}
|
|
816
835
|
}
|
|
817
836
|
|
|
837
|
+
#[napi(object)]
|
|
838
|
+
#[derive(Default)]
|
|
839
|
+
pub struct ZerobusSdkOptions {
|
|
840
|
+
/// Identifier appended to the `user-agent` header
|
|
841
|
+
pub application_name: Option<String>,
|
|
842
|
+
}
|
|
843
|
+
|
|
818
844
|
/// The main SDK for interacting with the Databricks Zerobus service.
|
|
819
845
|
///
|
|
820
846
|
/// This is the entry point for creating ingestion streams to Delta tables.
|
|
@@ -824,7 +850,8 @@ impl RustHeadersProvider for TsOAuthHeadersProvider {
|
|
|
824
850
|
/// ```typescript
|
|
825
851
|
/// const sdk = new ZerobusSdk(
|
|
826
852
|
/// "https://workspace-id.zerobus.region.cloud.databricks.com",
|
|
827
|
-
/// "https://workspace.cloud.databricks.com"
|
|
853
|
+
/// "https://workspace.cloud.databricks.com",
|
|
854
|
+
/// { applicationName: "my-app/1.0" }
|
|
828
855
|
/// );
|
|
829
856
|
///
|
|
830
857
|
/// const stream = await sdk.createStream(
|
|
@@ -852,13 +879,19 @@ impl ZerobusSdk {
|
|
|
852
879
|
/// (e.g., "https://workspace-id.zerobus.region.cloud.databricks.com")
|
|
853
880
|
/// * `unity_catalog_url` - The Unity Catalog endpoint URL
|
|
854
881
|
/// (e.g., "https://workspace.cloud.databricks.com")
|
|
882
|
+
/// * `options` - Optional SDK configuration (see `ZerobusSdkOptions`),
|
|
883
|
+
/// including `applicationName` for server-side attribution.
|
|
855
884
|
///
|
|
856
885
|
/// # Errors
|
|
857
886
|
///
|
|
858
887
|
/// - Invalid endpoint URLs
|
|
859
888
|
/// - Failed to extract workspace ID from the endpoint
|
|
860
889
|
#[napi(constructor)]
|
|
861
|
-
pub fn new(
|
|
890
|
+
pub fn new(
|
|
891
|
+
zerobus_endpoint: String,
|
|
892
|
+
unity_catalog_url: String,
|
|
893
|
+
options: Option<ZerobusSdkOptions>,
|
|
894
|
+
) -> Result<Self> {
|
|
862
895
|
let workspace_id = zerobus_endpoint
|
|
863
896
|
.strip_prefix("https://")
|
|
864
897
|
.or_else(|| zerobus_endpoint.strip_prefix("http://"))
|
|
@@ -870,9 +903,17 @@ impl ZerobusSdk {
|
|
|
870
903
|
)
|
|
871
904
|
})?;
|
|
872
905
|
|
|
873
|
-
let
|
|
906
|
+
let options = options.unwrap_or_default();
|
|
907
|
+
|
|
908
|
+
let builder = RustZerobusSdk::builder()
|
|
874
909
|
.endpoint(&zerobus_endpoint)
|
|
875
910
|
.unity_catalog_url(&unity_catalog_url)
|
|
911
|
+
.sdk_identifier(TS_SDK_USER_AGENT);
|
|
912
|
+
let builder = match options.application_name {
|
|
913
|
+
Some(name) => builder.application_name(name),
|
|
914
|
+
None => builder,
|
|
915
|
+
};
|
|
916
|
+
let inner = builder
|
|
876
917
|
.build()
|
|
877
918
|
.map_err(|e| Error::from_reason(format!("Failed to create SDK: {}", e)))?;
|
|
878
919
|
|
|
@@ -927,7 +968,7 @@ impl ZerobusSdk {
|
|
|
927
968
|
/// "", // ignored
|
|
928
969
|
/// undefined,
|
|
929
970
|
/// {
|
|
930
|
-
/// getHeadersCallback:
|
|
971
|
+
/// getHeadersCallback: () => [
|
|
931
972
|
/// ["authorization", `Bearer ${myToken}`],
|
|
932
973
|
/// ["x-databricks-zerobus-table-name", tableName]
|
|
933
974
|
/// ]
|
|
@@ -947,19 +988,19 @@ impl ZerobusSdk {
|
|
|
947
988
|
// Decode the optional protobuf descriptor up-front so we can hand it
|
|
948
989
|
// to the builder's `.compiled_proto()` setter; the builder constructs
|
|
949
990
|
// the (now-private) `TableProperties` itself.
|
|
950
|
-
let descriptor_proto: Option<prost_types::DescriptorProto> =
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
991
|
+
let descriptor_proto: Option<prost_types::DescriptorProto> = if let Some(ref desc_str) =
|
|
992
|
+
table_properties.descriptor_proto
|
|
993
|
+
{
|
|
994
|
+
let bytes = base64_decode(desc_str)
|
|
995
|
+
.map_err(|e| Error::from_reason(format!("Failed to decode descriptor: {}", e)))?;
|
|
996
|
+
let dp: prost_types::DescriptorProto =
|
|
997
|
+
prost::Message::decode(&bytes[..]).map_err(|e| {
|
|
998
|
+
Error::from_reason(format!("Failed to parse descriptor proto: {}", e))
|
|
954
999
|
})?;
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
Some(dp)
|
|
960
|
-
} else {
|
|
961
|
-
None
|
|
962
|
-
};
|
|
1000
|
+
Some(dp)
|
|
1001
|
+
} else {
|
|
1002
|
+
None
|
|
1003
|
+
};
|
|
963
1004
|
|
|
964
1005
|
let opts = options.unwrap_or(StreamConfigurationOptions {
|
|
965
1006
|
max_inflight_requests: None,
|
|
@@ -1076,7 +1117,8 @@ impl ZerobusSdk {
|
|
|
1076
1117
|
///
|
|
1077
1118
|
/// # Arguments
|
|
1078
1119
|
///
|
|
1079
|
-
/// * `stream` - The failed
|
|
1120
|
+
/// * `stream` - The terminally failed stream to recreate. The TypeScript wrapper
|
|
1121
|
+
/// must not have been closed because `close()` releases its native handle.
|
|
1080
1122
|
///
|
|
1081
1123
|
/// # Returns
|
|
1082
1124
|
///
|
|
@@ -1092,12 +1134,23 @@ impl ZerobusSdk {
|
|
|
1092
1134
|
///
|
|
1093
1135
|
/// ```typescript
|
|
1094
1136
|
/// try {
|
|
1095
|
-
/// await stream.
|
|
1137
|
+
/// await stream.ingestRecordsOffset(batch);
|
|
1138
|
+
/// await stream.flush();
|
|
1096
1139
|
/// } catch (error) {
|
|
1097
|
-
///
|
|
1098
|
-
///
|
|
1099
|
-
///
|
|
1100
|
-
///
|
|
1140
|
+
/// try {
|
|
1141
|
+
/// const newStream = await sdk.recreateStream(stream);
|
|
1142
|
+
/// try {
|
|
1143
|
+
/// await newStream.flush();
|
|
1144
|
+
/// } finally {
|
|
1145
|
+
/// await newStream.close();
|
|
1146
|
+
/// }
|
|
1147
|
+
/// } finally {
|
|
1148
|
+
/// try {
|
|
1149
|
+
/// await stream.close();
|
|
1150
|
+
/// } catch (closeError) {
|
|
1151
|
+
/// console.error("Failed stream released:", closeError);
|
|
1152
|
+
/// }
|
|
1153
|
+
/// }
|
|
1101
1154
|
/// }
|
|
1102
1155
|
/// ```
|
|
1103
1156
|
#[napi]
|
|
@@ -1438,9 +1491,7 @@ impl ZerobusArrowStream {
|
|
|
1438
1491
|
stream_ref
|
|
1439
1492
|
.ingest_ipc_batch(Bytes::from(buffer_vec))
|
|
1440
1493
|
.await
|
|
1441
|
-
.map_err(|e| {
|
|
1442
|
-
napi::Error::from_reason(format!("Failed to ingest batch: {}", e))
|
|
1443
|
-
})
|
|
1494
|
+
.map_err(|e| napi::Error::from_reason(format!("Failed to ingest batch: {}", e)))
|
|
1444
1495
|
},
|
|
1445
1496
|
|env, offset_id| {
|
|
1446
1497
|
let global: JsGlobal = env.get_global()?;
|
|
@@ -1686,7 +1737,8 @@ impl ZerobusSdk {
|
|
|
1686
1737
|
///
|
|
1687
1738
|
/// # Arguments
|
|
1688
1739
|
///
|
|
1689
|
-
/// * `stream` - The failed
|
|
1740
|
+
/// * `stream` - The terminally failed Arrow stream to recreate. The TypeScript wrapper
|
|
1741
|
+
/// must not have been closed because `close()` releases its native handle.
|
|
1690
1742
|
///
|
|
1691
1743
|
/// # Returns
|
|
1692
1744
|
///
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Options for loading a descriptor from a FileDescriptorSet.
|
|
3
|
+
*/
|
|
4
|
+
export interface LoadDescriptorOptions {
|
|
5
|
+
/**
|
|
6
|
+
* Path to the descriptor file generated by protoc.
|
|
7
|
+
* Example: 'schemas/my_schema_descriptor.pb'
|
|
8
|
+
*/
|
|
9
|
+
descriptorPath: string;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Name of the proto file within the FileDescriptorSet.
|
|
13
|
+
* This should match the filename in your .proto file.
|
|
14
|
+
* Example: 'schemas/air_quality.proto' or 'air_quality.proto'
|
|
15
|
+
*/
|
|
16
|
+
protoFileName: string;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Name of the message type to extract.
|
|
20
|
+
* Example: 'AirQuality'
|
|
21
|
+
*/
|
|
22
|
+
messageName: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Loads a specific message's DescriptorProto from a FileDescriptorSet.
|
|
27
|
+
*
|
|
28
|
+
* @param options - Configuration specifying which message to extract
|
|
29
|
+
* @returns Base64-encoded DescriptorProto string for the specified message
|
|
30
|
+
* @throws Error if the descriptor file, proto file, or message cannot be found
|
|
31
|
+
*/
|
|
32
|
+
export function loadDescriptorProto(options: LoadDescriptorOptions): string;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Utility functions for working with Protocol Buffer descriptors.
|
|
5
|
+
*
|
|
6
|
+
* These utilities help extract message descriptors from FileDescriptorSets
|
|
7
|
+
* generated by protoc, providing flexibility in choosing which message to use.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const fs = require("fs");
|
|
11
|
+
const descriptor = require("protobufjs/ext/descriptor");
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Loads a specific message's DescriptorProto from a FileDescriptorSet.
|
|
15
|
+
*
|
|
16
|
+
* @param {object} options
|
|
17
|
+
* @param {string} options.descriptorPath Path to the descriptor file generated by protoc.
|
|
18
|
+
* @param {string} options.protoFileName Name of the proto file within the FileDescriptorSet.
|
|
19
|
+
* @param {string} options.messageName Name of the message type to extract.
|
|
20
|
+
* @returns {string} Base64-encoded DescriptorProto string for the specified message.
|
|
21
|
+
*/
|
|
22
|
+
function loadDescriptorProto(options) {
|
|
23
|
+
const { descriptorPath, protoFileName, messageName } = options;
|
|
24
|
+
|
|
25
|
+
const descriptorBytes = fs.readFileSync(descriptorPath);
|
|
26
|
+
const fileDescriptorSet = descriptor.FileDescriptorSet.decode(descriptorBytes);
|
|
27
|
+
const fileDescriptor = fileDescriptorSet.file.find((file) =>
|
|
28
|
+
file.name === protoFileName ||
|
|
29
|
+
file.name.endsWith("/" + protoFileName)
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
if (!fileDescriptor) {
|
|
33
|
+
throw new Error(
|
|
34
|
+
`Proto file '${protoFileName}' not found in descriptor. Available files: ${
|
|
35
|
+
fileDescriptorSet.file.map((file) => file.name).join(", ")
|
|
36
|
+
}`
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const messageType = fileDescriptor.messageType?.find((message) => message.name === messageName);
|
|
41
|
+
|
|
42
|
+
if (!messageType) {
|
|
43
|
+
const availableMessages = fileDescriptor.messageType?.map((message) => message.name).join(", ") || "none";
|
|
44
|
+
throw new Error(
|
|
45
|
+
`Message '${messageName}' not found in ${protoFileName}. Available messages: ${availableMessages}`
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const descriptorProtoBytes = descriptor.DescriptorProto.encode(messageType).finish();
|
|
50
|
+
return Buffer.from(descriptorProtoBytes).toString("base64");
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
module.exports = {
|
|
54
|
+
loadDescriptorProto,
|
|
55
|
+
};
|
package/utils/descriptor.ts
CHANGED
|
@@ -49,7 +49,7 @@ export interface LoadDescriptorOptions {
|
|
|
49
49
|
*
|
|
50
50
|
* @example
|
|
51
51
|
* ```typescript
|
|
52
|
-
* import { loadDescriptorProto } from '@databricks/zerobus-sdk/utils/descriptor';
|
|
52
|
+
* import { loadDescriptorProto } from '@databricks/zerobus-ingest-sdk/utils/descriptor.js';
|
|
53
53
|
*
|
|
54
54
|
* const descriptorBase64 = loadDescriptorProto({
|
|
55
55
|
* descriptorPath: 'schemas/air_quality_descriptor.pb',
|
|
@@ -74,7 +74,7 @@ export function loadDescriptorProto(options: LoadDescriptorOptions): string {
|
|
|
74
74
|
|
|
75
75
|
// Find the file descriptor matching the proto file name
|
|
76
76
|
const fileDescriptor = fileDescriptorSet.file.find((f: any) =>
|
|
77
|
-
f.name === protoFileName || f.name.endsWith('/' + protoFileName)
|
|
77
|
+
f.name === protoFileName || f.name.endsWith('/' + protoFileName)
|
|
78
78
|
);
|
|
79
79
|
|
|
80
80
|
if (!fileDescriptor) {
|