@qualithm/arrow-flight-client 0.1.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/LICENSE +190 -0
- package/README.md +258 -0
- package/dist/client/create-flight-client.d.ts +28 -0
- package/dist/client/create-flight-client.d.ts.map +1 -0
- package/dist/client/create-flight-client.js +29 -0
- package/dist/client/create-flight-client.js.map +1 -0
- package/dist/client/create-flight-sql-client.d.ts +28 -0
- package/dist/client/create-flight-sql-client.d.ts.map +1 -0
- package/dist/client/create-flight-sql-client.js +29 -0
- package/dist/client/create-flight-sql-client.js.map +1 -0
- package/dist/client/errors.d.ts +81 -0
- package/dist/client/errors.d.ts.map +1 -0
- package/dist/client/errors.js +106 -0
- package/dist/client/errors.js.map +1 -0
- package/dist/client/flight-client.d.ts +161 -0
- package/dist/client/flight-client.d.ts.map +1 -0
- package/dist/client/flight-client.js +403 -0
- package/dist/client/flight-client.js.map +1 -0
- package/dist/client/flight-sql-client.d.ts +348 -0
- package/dist/client/flight-sql-client.d.ts.map +1 -0
- package/dist/client/flight-sql-client.js +689 -0
- package/dist/client/flight-sql-client.js.map +1 -0
- package/dist/client/index.d.ts +11 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/index.js +10 -0
- package/dist/client/index.js.map +1 -0
- package/dist/client/ipc.d.ts +131 -0
- package/dist/client/ipc.d.ts.map +1 -0
- package/dist/client/ipc.js +246 -0
- package/dist/client/ipc.js.map +1 -0
- package/dist/client/types.d.ts +153 -0
- package/dist/client/types.d.ts.map +1 -0
- package/dist/client/types.js +26 -0
- package/dist/client/types.js.map +1 -0
- package/dist/gen/arrow/flight/FlightSql_pb.d.ts +3173 -0
- package/dist/gen/arrow/flight/FlightSql_pb.d.ts.map +1 -0
- package/dist/gen/arrow/flight/FlightSql_pb.js +2258 -0
- package/dist/gen/arrow/flight/FlightSql_pb.js.map +1 -0
- package/dist/gen/arrow/flight/Flight_pb.d.ts +1159 -0
- package/dist/gen/arrow/flight/Flight_pb.d.ts.map +1 -0
- package/dist/gen/arrow/flight/Flight_pb.js +397 -0
- package/dist/gen/arrow/flight/Flight_pb.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -0
- package/package.json +96 -0
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base error class for all Flight client errors.
|
|
3
|
+
*/
|
|
4
|
+
export class FlightError extends Error {
|
|
5
|
+
/** Error name for identification. */
|
|
6
|
+
name = "FlightError";
|
|
7
|
+
/** Original error that caused this error, if any. */
|
|
8
|
+
cause;
|
|
9
|
+
constructor(message, cause) {
|
|
10
|
+
super(message, { cause });
|
|
11
|
+
this.cause = cause;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Type guard to check if an error is a FlightError.
|
|
15
|
+
*/
|
|
16
|
+
static isError(error) {
|
|
17
|
+
return error instanceof FlightError;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Error thrown when a connection to the Flight server fails.
|
|
22
|
+
*/
|
|
23
|
+
export class FlightConnectionError extends FlightError {
|
|
24
|
+
/** Error name for identification. */
|
|
25
|
+
name = "FlightConnectionError";
|
|
26
|
+
/** URL that failed to connect. */
|
|
27
|
+
url;
|
|
28
|
+
constructor(message, url, cause) {
|
|
29
|
+
super(message, cause);
|
|
30
|
+
this.url = url;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Type guard to check if an error is a FlightConnectionError.
|
|
34
|
+
*/
|
|
35
|
+
static isError(error) {
|
|
36
|
+
return error instanceof FlightConnectionError;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Error thrown when authentication fails.
|
|
41
|
+
*/
|
|
42
|
+
export class FlightAuthError extends FlightError {
|
|
43
|
+
/** Error name for identification. */
|
|
44
|
+
name = "FlightAuthError";
|
|
45
|
+
/**
|
|
46
|
+
* Type guard to check if an error is a FlightAuthError.
|
|
47
|
+
*/
|
|
48
|
+
static isError(error) {
|
|
49
|
+
return error instanceof FlightAuthError;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Error thrown when a request times out.
|
|
54
|
+
*/
|
|
55
|
+
export class FlightTimeoutError extends FlightError {
|
|
56
|
+
/** Error name for identification. */
|
|
57
|
+
name = "FlightTimeoutError";
|
|
58
|
+
/** Timeout duration in milliseconds that was exceeded. */
|
|
59
|
+
timeoutMs;
|
|
60
|
+
constructor(message, timeoutMs, cause) {
|
|
61
|
+
super(message, cause);
|
|
62
|
+
this.timeoutMs = timeoutMs;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Type guard to check if an error is a FlightTimeoutError.
|
|
66
|
+
*/
|
|
67
|
+
static isError(error) {
|
|
68
|
+
return error instanceof FlightTimeoutError;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Error thrown when the server returns an error response.
|
|
73
|
+
*/
|
|
74
|
+
export class FlightServerError extends FlightError {
|
|
75
|
+
/** Error name for identification. */
|
|
76
|
+
name = "FlightServerError";
|
|
77
|
+
/** gRPC status code from the server. */
|
|
78
|
+
code;
|
|
79
|
+
/** Additional error details from the server, if provided. */
|
|
80
|
+
details;
|
|
81
|
+
constructor(message, code, details, cause) {
|
|
82
|
+
super(message, cause);
|
|
83
|
+
this.code = code;
|
|
84
|
+
this.details = details;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Type guard to check if an error is a FlightServerError.
|
|
88
|
+
*/
|
|
89
|
+
static isError(error) {
|
|
90
|
+
return error instanceof FlightServerError;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Error thrown when an operation is cancelled.
|
|
95
|
+
*/
|
|
96
|
+
export class FlightCancelledError extends FlightError {
|
|
97
|
+
/** Error name for identification. */
|
|
98
|
+
name = "FlightCancelledError";
|
|
99
|
+
/**
|
|
100
|
+
* Type guard to check if an error is a FlightCancelledError.
|
|
101
|
+
*/
|
|
102
|
+
static isError(error) {
|
|
103
|
+
return error instanceof FlightCancelledError;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/client/errors.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,OAAO,WAAY,SAAQ,KAAK;IACpC,qCAAqC;IACnB,IAAI,GAAW,aAAa,CAAA;IAC9C,qDAAqD;IACnC,KAAK,CAAS;IAEhC,YAAY,OAAe,EAAE,KAAe;QAC1C,KAAK,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,CAAC,CAAA;QACzB,IAAI,CAAC,KAAK,GAAG,KAAK,CAAA;IACpB,CAAC;IAED;;OAEG;IACH,MAAM,CAAC,OAAO,CAAC,KAAc;QAC3B,OAAO,KAAK,YAAY,WAAW,CAAA;IACrC,CAAC;CACF;AAED;;GAEG;AACH,MAAM,OAAO,qBAAsB,SAAQ,WAAW;IACpD,qCAAqC;IACnB,IAAI,GAAW,uBAAuB,CAAA;IACxD,kCAAkC;IACzB,GAAG,CAAQ;IAEpB,YAAY,OAAe,EAAE,GAAW,EAAE,KAAe;QACvD,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,CAAA;QACrB,IAAI,CAAC,GAAG,GAAG,GAAG,CAAA;IAChB,CAAC;IAED;;OAEG;IACH,MAAM,CAAU,OAAO,CAAC,KAAc;QACpC,OAAO,KAAK,YAAY,qBAAqB,CAAA;IAC/C,CAAC;CACF;AAED;;GAEG;AACH,MAAM,OAAO,eAAgB,SAAQ,WAAW;IAC9C,qCAAqC;IACnB,IAAI,GAAW,iBAAiB,CAAA;IAElD;;OAEG;IACH,MAAM,CAAU,OAAO,CAAC,KAAc;QACpC,OAAO,KAAK,YAAY,eAAe,CAAA;IACzC,CAAC;CACF;AAED;;GAEG;AACH,MAAM,OAAO,kBAAmB,SAAQ,WAAW;IACjD,qCAAqC;IACnB,IAAI,GAAW,oBAAoB,CAAA;IACrD,0DAA0D;IACjD,SAAS,CAAQ;IAE1B,YAAY,OAAe,EAAE,SAAiB,EAAE,KAAe;QAC7D,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,CAAA;QACrB,IAAI,CAAC,SAAS,GAAG,SAAS,CAAA;IAC5B,CAAC;IAED;;OAEG;IACH,MAAM,CAAU,OAAO,CAAC,KAAc;QACpC,OAAO,KAAK,YAAY,kBAAkB,CAAA;IAC5C,CAAC;CACF;AAED;;GAEG;AACH,MAAM,OAAO,iBAAkB,SAAQ,WAAW;IAChD,qCAAqC;IACnB,IAAI,GAAW,mBAAmB,CAAA;IACpD,wCAAwC;IAC/B,IAAI,CAAQ;IACrB,6DAA6D;IACpD,OAAO,CAAoB;IAEpC,YAAY,OAAe,EAAE,IAAY,EAAE,OAAgB,EAAE,KAAe;QAC1E,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,CAAA;QACrB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAA;QAChB,IAAI,CAAC,OAAO,GAAG,OAAO,CAAA;IACxB,CAAC;IAED;;OAEG;IACH,MAAM,CAAU,OAAO,CAAC,KAAc;QACpC,OAAO,KAAK,YAAY,iBAAiB,CAAA;IAC3C,CAAC;CACF;AAED;;GAEG;AACH,MAAM,OAAO,oBAAqB,SAAQ,WAAW;IACnD,qCAAqC;IACnB,IAAI,GAAW,sBAAsB,CAAA;IAEvD;;OAEG;IACH,MAAM,CAAU,OAAO,CAAC,KAAc;QACpC,OAAO,KAAK,YAAY,oBAAoB,CAAA;IAC9C,CAAC;CACF"}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { type ActionType, type FlightData, type FlightInfo, type PollInfo, type PutResult, type Result, type SchemaResult } from "../gen/arrow/flight/Flight_pb.js";
|
|
2
|
+
import { type FlightAction, type FlightClientOptions, type FlightCriteria, type FlightDescriptorInput, type FlightTicket } from "./types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Low-level Arrow Flight client for communicating with Flight servers.
|
|
5
|
+
*
|
|
6
|
+
* This client provides access to all core Flight RPC methods.
|
|
7
|
+
* For SQL operations, use `FlightSqlClient` instead.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```ts
|
|
11
|
+
* const client = new FlightClient({ url: "https://flight.example.com:8815" })
|
|
12
|
+
*
|
|
13
|
+
* const info = await client.getFlightInfo({ type: "cmd", cmd: myCommand })
|
|
14
|
+
* for await (const flight of client.listFlights()) {
|
|
15
|
+
* console.log(flight)
|
|
16
|
+
* }
|
|
17
|
+
*
|
|
18
|
+
* client.close()
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
export declare class FlightClient {
|
|
22
|
+
#private;
|
|
23
|
+
constructor(options: FlightClientOptions);
|
|
24
|
+
/**
|
|
25
|
+
* The base URL of the Flight server.
|
|
26
|
+
*/
|
|
27
|
+
get url(): string;
|
|
28
|
+
/**
|
|
29
|
+
* Whether the client has been closed.
|
|
30
|
+
*/
|
|
31
|
+
get closed(): boolean;
|
|
32
|
+
/**
|
|
33
|
+
* Whether the client has been authenticated via handshake.
|
|
34
|
+
*/
|
|
35
|
+
get authenticated(): boolean;
|
|
36
|
+
/**
|
|
37
|
+
* Close the client and release resources.
|
|
38
|
+
* After calling close, the client should not be used.
|
|
39
|
+
*/
|
|
40
|
+
close(): void;
|
|
41
|
+
/**
|
|
42
|
+
* Perform Flight Handshake authentication.
|
|
43
|
+
*
|
|
44
|
+
* This method is automatically called for clients configured with `auth: { type: "basic" }`.
|
|
45
|
+
* For custom handshake payloads, call this method directly with raw bytes.
|
|
46
|
+
*
|
|
47
|
+
* @param payload - Raw handshake payload (defaults to BasicAuth if auth.type is "basic")
|
|
48
|
+
* @returns The authentication token from the server
|
|
49
|
+
*/
|
|
50
|
+
handshake(payload?: Uint8Array): Promise<string>;
|
|
51
|
+
/**
|
|
52
|
+
* Authenticate with the server using configured credentials.
|
|
53
|
+
*
|
|
54
|
+
* For basic auth, this calls the Handshake RPC.
|
|
55
|
+
* For bearer auth, no action is needed (token is sent in headers).
|
|
56
|
+
*
|
|
57
|
+
* @returns The authentication token (if applicable)
|
|
58
|
+
*/
|
|
59
|
+
authenticate(): Promise<string | undefined>;
|
|
60
|
+
/**
|
|
61
|
+
* Get information about a specific flight.
|
|
62
|
+
*
|
|
63
|
+
* @param descriptor - Flight descriptor identifying the dataset
|
|
64
|
+
* @returns Flight information including schema and endpoints
|
|
65
|
+
*/
|
|
66
|
+
getFlightInfo(descriptor: FlightDescriptorInput): Promise<FlightInfo>;
|
|
67
|
+
/**
|
|
68
|
+
* Poll for updated flight information (useful for long-running queries).
|
|
69
|
+
*
|
|
70
|
+
* @param descriptor - Flight descriptor identifying the dataset
|
|
71
|
+
* @returns Poll information with progress and updated flight info
|
|
72
|
+
*/
|
|
73
|
+
pollFlightInfo(descriptor: FlightDescriptorInput): Promise<PollInfo>;
|
|
74
|
+
/**
|
|
75
|
+
* Get the schema for a flight.
|
|
76
|
+
*
|
|
77
|
+
* @param descriptor - Flight descriptor identifying the dataset
|
|
78
|
+
* @returns Schema result containing the Arrow schema bytes
|
|
79
|
+
*/
|
|
80
|
+
getSchema(descriptor: FlightDescriptorInput): Promise<SchemaResult>;
|
|
81
|
+
/**
|
|
82
|
+
* List available flights matching the given criteria.
|
|
83
|
+
*
|
|
84
|
+
* @param criteria - Optional filter criteria for listing flights
|
|
85
|
+
* @yields FlightInfo for each matching flight
|
|
86
|
+
*/
|
|
87
|
+
listFlights(criteria?: FlightCriteria): AsyncIterable<FlightInfo>;
|
|
88
|
+
/**
|
|
89
|
+
* List available actions supported by the server.
|
|
90
|
+
*
|
|
91
|
+
* @yields ActionType describing each available action
|
|
92
|
+
*/
|
|
93
|
+
listActions(): AsyncIterable<ActionType>;
|
|
94
|
+
/**
|
|
95
|
+
* Execute a custom action on the server.
|
|
96
|
+
*
|
|
97
|
+
* @param action - The action to execute (type and optional body)
|
|
98
|
+
* @yields Result messages from the server
|
|
99
|
+
*/
|
|
100
|
+
doAction(action: FlightAction): AsyncIterable<Result>;
|
|
101
|
+
/**
|
|
102
|
+
* Retrieve flight data for the given ticket.
|
|
103
|
+
* Returns an async iterable of FlightData messages.
|
|
104
|
+
*
|
|
105
|
+
* Use the IPC decoding utilities to convert FlightData to Arrow RecordBatches:
|
|
106
|
+
* - `decodeFlightDataStream()` - decode to RecordBatch stream
|
|
107
|
+
* - `decodeFlightDataToTable()` - decode to a single Table
|
|
108
|
+
*
|
|
109
|
+
* @param ticket - The ticket identifying the data to retrieve
|
|
110
|
+
* @yields FlightData messages containing Arrow IPC data
|
|
111
|
+
*
|
|
112
|
+
* @example
|
|
113
|
+
* ```ts
|
|
114
|
+
* import { decodeFlightDataStream } from "@qualithm/arrow-flight-client"
|
|
115
|
+
*
|
|
116
|
+
* const stream = client.doGet(ticket)
|
|
117
|
+
* for await (const batch of decodeFlightDataStream(stream)) {
|
|
118
|
+
* console.log(`Received batch with ${batch.numRows} rows`)
|
|
119
|
+
* }
|
|
120
|
+
* ```
|
|
121
|
+
*/
|
|
122
|
+
doGet(ticket: FlightTicket): AsyncIterable<FlightData>;
|
|
123
|
+
/**
|
|
124
|
+
* Upload data to the server.
|
|
125
|
+
* Returns an async iterable of PutResult messages containing server acknowledgements.
|
|
126
|
+
*
|
|
127
|
+
* Use the IPC encoding utilities to create FlightData from Arrow data:
|
|
128
|
+
* - `encodeRecordBatchesToFlightData()` - encode RecordBatch stream
|
|
129
|
+
* - `encodeTableToFlightData()` - encode a Table
|
|
130
|
+
*
|
|
131
|
+
* @param data - Async iterable of FlightData messages to upload (include descriptor in first message)
|
|
132
|
+
* @yields PutResult messages from the server
|
|
133
|
+
*
|
|
134
|
+
* @example
|
|
135
|
+
* ```ts
|
|
136
|
+
* import { encodeTableToFlightData } from "@qualithm/arrow-flight-client"
|
|
137
|
+
*
|
|
138
|
+
* const descriptor = { type: "path", path: ["my", "table"] }
|
|
139
|
+
* const flightData = encodeTableToFlightData(table)
|
|
140
|
+
*
|
|
141
|
+
* // Add descriptor to first message
|
|
142
|
+
* async function* withDescriptor() {
|
|
143
|
+
* let first = true
|
|
144
|
+
* for await (const data of flightData) {
|
|
145
|
+
* if (first) {
|
|
146
|
+
* yield { ...data, flightDescriptor: descriptor }
|
|
147
|
+
* first = false
|
|
148
|
+
* } else {
|
|
149
|
+
* yield data
|
|
150
|
+
* }
|
|
151
|
+
* }
|
|
152
|
+
* }
|
|
153
|
+
*
|
|
154
|
+
* for await (const result of client.doPut(withDescriptor())) {
|
|
155
|
+
* console.log("Server acknowledged:", result.appMetadata)
|
|
156
|
+
* }
|
|
157
|
+
* ```
|
|
158
|
+
*/
|
|
159
|
+
doPut(data: AsyncIterable<FlightData>): AsyncIterable<PutResult>;
|
|
160
|
+
}
|
|
161
|
+
//# sourceMappingURL=flight-client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"flight-client.d.ts","sourceRoot":"","sources":["../../src/client/flight-client.ts"],"names":[],"mappings":"AAOA,OAAO,EACL,KAAK,UAAU,EAEf,KAAK,UAAU,EAEf,KAAK,UAAU,EAGf,KAAK,QAAQ,EACb,KAAK,SAAS,EACd,KAAK,MAAM,EACX,KAAK,YAAY,EAClB,MAAM,kCAAkC,CAAA;AAEzC,OAAO,EACL,KAAK,YAAY,EACjB,KAAK,mBAAmB,EACxB,KAAK,cAAc,EACnB,KAAK,qBAAqB,EAC1B,KAAK,YAAY,EAGlB,MAAM,YAAY,CAAA;AAEnB;;;;;;;;;;;;;;;;;GAiBG;AACH,qBAAa,YAAY;;gBAQX,OAAO,EAAE,mBAAmB;IAcxC;;OAEG;IACH,IAAI,GAAG,IAAI,MAAM,CAEhB;IAED;;OAEG;IACH,IAAI,MAAM,IAAI,OAAO,CAEpB;IAED;;OAEG;IACH,IAAI,aAAa,IAAI,OAAO,CAE3B;IAED;;;OAGG;IACH,KAAK,IAAI,IAAI;IAIb;;;;;;;;OAQG;IACG,SAAS,CAAC,OAAO,CAAC,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC;IA6DtD;;;;;;;OAOG;IACG,YAAY,IAAI,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC;IAgBjD;;;;;OAKG;IACG,aAAa,CAAC,UAAU,EAAE,qBAAqB,GAAG,OAAO,CAAC,UAAU,CAAC;IAW3E;;;;;OAKG;IACG,cAAc,CAAC,UAAU,EAAE,qBAAqB,GAAG,OAAO,CAAC,QAAQ,CAAC;IAW1E;;;;;OAKG;IACG,SAAS,CAAC,UAAU,EAAE,qBAAqB,GAAG,OAAO,CAAC,YAAY,CAAC;IAWzE;;;;;OAKG;IACI,WAAW,CAAC,QAAQ,CAAC,EAAE,cAAc,GAAG,aAAa,CAAC,UAAU,CAAC;IAexE;;;;OAIG;IACI,WAAW,IAAI,aAAa,CAAC,UAAU,CAAC;IAY/C;;;;;OAKG;IACI,QAAQ,CAAC,MAAM,EAAE,YAAY,GAAG,aAAa,CAAC,MAAM,CAAC;IAe5D;;;;;;;;;;;;;;;;;;;;OAoBG;IACI,KAAK,CAAC,MAAM,EAAE,YAAY,GAAG,aAAa,CAAC,UAAU,CAAC;IAc7D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAmCG;IACI,KAAK,CAAC,IAAI,EAAE,aAAa,CAAC,UAAU,CAAC,GAAG,aAAa,CAAC,SAAS,CAAC;CA4GxE"}
|
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access */
|
|
2
|
+
// Disabled due to generated proto types using @ts-nocheck
|
|
3
|
+
import { create, toBinary } from "@bufbuild/protobuf";
|
|
4
|
+
import { createClient } from "@connectrpc/connect";
|
|
5
|
+
import { createGrpcTransport } from "@connectrpc/connect-node";
|
|
6
|
+
import { BasicAuthSchema, FlightService } from "../gen/arrow/flight/Flight_pb.js";
|
|
7
|
+
import { FlightAuthError, FlightConnectionError, FlightError, FlightServerError } from "./errors.js";
|
|
8
|
+
import { resolveOptions } from "./types.js";
|
|
9
|
+
/**
|
|
10
|
+
* Low-level Arrow Flight client for communicating with Flight servers.
|
|
11
|
+
*
|
|
12
|
+
* This client provides access to all core Flight RPC methods.
|
|
13
|
+
* For SQL operations, use `FlightSqlClient` instead.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```ts
|
|
17
|
+
* const client = new FlightClient({ url: "https://flight.example.com:8815" })
|
|
18
|
+
*
|
|
19
|
+
* const info = await client.getFlightInfo({ type: "cmd", cmd: myCommand })
|
|
20
|
+
* for await (const flight of client.listFlights()) {
|
|
21
|
+
* console.log(flight)
|
|
22
|
+
* }
|
|
23
|
+
*
|
|
24
|
+
* client.close()
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export class FlightClient {
|
|
28
|
+
#options;
|
|
29
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Generated proto types use @ts-nocheck
|
|
30
|
+
#client;
|
|
31
|
+
#closed = false;
|
|
32
|
+
#authenticated = false;
|
|
33
|
+
#authToken;
|
|
34
|
+
constructor(options) {
|
|
35
|
+
this.#options = resolveOptions(options);
|
|
36
|
+
// Build node options with TLS configuration if provided
|
|
37
|
+
const nodeOptions = this.#buildNodeOptions();
|
|
38
|
+
const transport = createGrpcTransport({
|
|
39
|
+
baseUrl: this.#options.url,
|
|
40
|
+
nodeOptions
|
|
41
|
+
});
|
|
42
|
+
this.#client = createClient(FlightService, transport);
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* The base URL of the Flight server.
|
|
46
|
+
*/
|
|
47
|
+
get url() {
|
|
48
|
+
return this.#options.url;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Whether the client has been closed.
|
|
52
|
+
*/
|
|
53
|
+
get closed() {
|
|
54
|
+
return this.#closed;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Whether the client has been authenticated via handshake.
|
|
58
|
+
*/
|
|
59
|
+
get authenticated() {
|
|
60
|
+
return this.#authenticated;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Close the client and release resources.
|
|
64
|
+
* After calling close, the client should not be used.
|
|
65
|
+
*/
|
|
66
|
+
close() {
|
|
67
|
+
this.#closed = true;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Perform Flight Handshake authentication.
|
|
71
|
+
*
|
|
72
|
+
* This method is automatically called for clients configured with `auth: { type: "basic" }`.
|
|
73
|
+
* For custom handshake payloads, call this method directly with raw bytes.
|
|
74
|
+
*
|
|
75
|
+
* @param payload - Raw handshake payload (defaults to BasicAuth if auth.type is "basic")
|
|
76
|
+
* @returns The authentication token from the server
|
|
77
|
+
*/
|
|
78
|
+
async handshake(payload) {
|
|
79
|
+
this.#assertOpen();
|
|
80
|
+
// Use provided payload or build from basic auth credentials
|
|
81
|
+
let handshakePayload = payload;
|
|
82
|
+
if (!handshakePayload && this.#options.auth?.type === "basic") {
|
|
83
|
+
const basicAuth = create(BasicAuthSchema, {
|
|
84
|
+
username: this.#options.auth.credentials.username,
|
|
85
|
+
password: this.#options.auth.credentials.password
|
|
86
|
+
});
|
|
87
|
+
handshakePayload = toBinary(BasicAuthSchema, basicAuth);
|
|
88
|
+
}
|
|
89
|
+
if (!handshakePayload) {
|
|
90
|
+
throw new FlightError("no handshake payload provided and no basic auth credentials configured");
|
|
91
|
+
}
|
|
92
|
+
try {
|
|
93
|
+
// Create async iterable with single handshake request
|
|
94
|
+
// eslint-disable-next-line @typescript-eslint/require-await
|
|
95
|
+
const requests = async function* () {
|
|
96
|
+
yield {
|
|
97
|
+
protocolVersion: 0n,
|
|
98
|
+
payload: handshakePayload
|
|
99
|
+
};
|
|
100
|
+
};
|
|
101
|
+
const stream = this.#client.handshake(requests(), {
|
|
102
|
+
headers: this.#getRequestHeaders()
|
|
103
|
+
});
|
|
104
|
+
let response;
|
|
105
|
+
for await (const msg of stream) {
|
|
106
|
+
response = msg;
|
|
107
|
+
break; // Only need first response
|
|
108
|
+
}
|
|
109
|
+
if (!response) {
|
|
110
|
+
throw new FlightAuthError("handshake failed: no response from server");
|
|
111
|
+
}
|
|
112
|
+
// Extract token from response payload (typically Bearer token)
|
|
113
|
+
const token = new TextDecoder().decode(response.payload);
|
|
114
|
+
this.#authToken = token;
|
|
115
|
+
this.#authenticated = true;
|
|
116
|
+
return token;
|
|
117
|
+
}
|
|
118
|
+
catch (error) {
|
|
119
|
+
if (FlightError.isError(error)) {
|
|
120
|
+
throw error;
|
|
121
|
+
}
|
|
122
|
+
throw this.#wrapError(error, "handshake");
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Authenticate with the server using configured credentials.
|
|
127
|
+
*
|
|
128
|
+
* For basic auth, this calls the Handshake RPC.
|
|
129
|
+
* For bearer auth, no action is needed (token is sent in headers).
|
|
130
|
+
*
|
|
131
|
+
* @returns The authentication token (if applicable)
|
|
132
|
+
*/
|
|
133
|
+
async authenticate() {
|
|
134
|
+
this.#assertOpen();
|
|
135
|
+
if (this.#options.auth?.type === "basic") {
|
|
136
|
+
return this.handshake();
|
|
137
|
+
}
|
|
138
|
+
if (this.#options.auth?.type === "bearer") {
|
|
139
|
+
this.#authenticated = true;
|
|
140
|
+
return this.#options.auth.token;
|
|
141
|
+
}
|
|
142
|
+
// No auth configured
|
|
143
|
+
return undefined;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Get information about a specific flight.
|
|
147
|
+
*
|
|
148
|
+
* @param descriptor - Flight descriptor identifying the dataset
|
|
149
|
+
* @returns Flight information including schema and endpoints
|
|
150
|
+
*/
|
|
151
|
+
async getFlightInfo(descriptor) {
|
|
152
|
+
this.#assertOpen();
|
|
153
|
+
try {
|
|
154
|
+
return await this.#client.getFlightInfo(this.#toFlightDescriptor(descriptor), {
|
|
155
|
+
headers: this.#getRequestHeaders()
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
catch (error) {
|
|
159
|
+
throw this.#wrapError(error, "getFlightInfo");
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Poll for updated flight information (useful for long-running queries).
|
|
164
|
+
*
|
|
165
|
+
* @param descriptor - Flight descriptor identifying the dataset
|
|
166
|
+
* @returns Poll information with progress and updated flight info
|
|
167
|
+
*/
|
|
168
|
+
async pollFlightInfo(descriptor) {
|
|
169
|
+
this.#assertOpen();
|
|
170
|
+
try {
|
|
171
|
+
return await this.#client.pollFlightInfo(this.#toFlightDescriptor(descriptor), {
|
|
172
|
+
headers: this.#getRequestHeaders()
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
catch (error) {
|
|
176
|
+
throw this.#wrapError(error, "pollFlightInfo");
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Get the schema for a flight.
|
|
181
|
+
*
|
|
182
|
+
* @param descriptor - Flight descriptor identifying the dataset
|
|
183
|
+
* @returns Schema result containing the Arrow schema bytes
|
|
184
|
+
*/
|
|
185
|
+
async getSchema(descriptor) {
|
|
186
|
+
this.#assertOpen();
|
|
187
|
+
try {
|
|
188
|
+
return await this.#client.getSchema(this.#toFlightDescriptor(descriptor), {
|
|
189
|
+
headers: this.#getRequestHeaders()
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
catch (error) {
|
|
193
|
+
throw this.#wrapError(error, "getSchema");
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* List available flights matching the given criteria.
|
|
198
|
+
*
|
|
199
|
+
* @param criteria - Optional filter criteria for listing flights
|
|
200
|
+
* @yields FlightInfo for each matching flight
|
|
201
|
+
*/
|
|
202
|
+
async *listFlights(criteria) {
|
|
203
|
+
this.#assertOpen();
|
|
204
|
+
try {
|
|
205
|
+
const stream = this.#client.listFlights({ expression: criteria?.expression ?? new Uint8Array() }, { headers: this.#getRequestHeaders() });
|
|
206
|
+
for await (const info of stream) {
|
|
207
|
+
yield info;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
catch (error) {
|
|
211
|
+
throw this.#wrapError(error, "listFlights");
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* List available actions supported by the server.
|
|
216
|
+
*
|
|
217
|
+
* @yields ActionType describing each available action
|
|
218
|
+
*/
|
|
219
|
+
async *listActions() {
|
|
220
|
+
this.#assertOpen();
|
|
221
|
+
try {
|
|
222
|
+
const stream = this.#client.listActions({}, { headers: this.#getRequestHeaders() });
|
|
223
|
+
for await (const action of stream) {
|
|
224
|
+
yield action;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
catch (error) {
|
|
228
|
+
throw this.#wrapError(error, "listActions");
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Execute a custom action on the server.
|
|
233
|
+
*
|
|
234
|
+
* @param action - The action to execute (type and optional body)
|
|
235
|
+
* @yields Result messages from the server
|
|
236
|
+
*/
|
|
237
|
+
async *doAction(action) {
|
|
238
|
+
this.#assertOpen();
|
|
239
|
+
try {
|
|
240
|
+
const stream = this.#client.doAction({ type: action.type, body: action.body ?? new Uint8Array() }, { headers: this.#getRequestHeaders() });
|
|
241
|
+
for await (const result of stream) {
|
|
242
|
+
yield result;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
catch (error) {
|
|
246
|
+
throw this.#wrapError(error, "doAction");
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Retrieve flight data for the given ticket.
|
|
251
|
+
* Returns an async iterable of FlightData messages.
|
|
252
|
+
*
|
|
253
|
+
* Use the IPC decoding utilities to convert FlightData to Arrow RecordBatches:
|
|
254
|
+
* - `decodeFlightDataStream()` - decode to RecordBatch stream
|
|
255
|
+
* - `decodeFlightDataToTable()` - decode to a single Table
|
|
256
|
+
*
|
|
257
|
+
* @param ticket - The ticket identifying the data to retrieve
|
|
258
|
+
* @yields FlightData messages containing Arrow IPC data
|
|
259
|
+
*
|
|
260
|
+
* @example
|
|
261
|
+
* ```ts
|
|
262
|
+
* import { decodeFlightDataStream } from "@qualithm/arrow-flight-client"
|
|
263
|
+
*
|
|
264
|
+
* const stream = client.doGet(ticket)
|
|
265
|
+
* for await (const batch of decodeFlightDataStream(stream)) {
|
|
266
|
+
* console.log(`Received batch with ${batch.numRows} rows`)
|
|
267
|
+
* }
|
|
268
|
+
* ```
|
|
269
|
+
*/
|
|
270
|
+
async *doGet(ticket) {
|
|
271
|
+
this.#assertOpen();
|
|
272
|
+
try {
|
|
273
|
+
const stream = this.#client.doGet(ticket, {
|
|
274
|
+
headers: this.#getRequestHeaders()
|
|
275
|
+
});
|
|
276
|
+
for await (const data of stream) {
|
|
277
|
+
yield data;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
catch (error) {
|
|
281
|
+
throw this.#wrapError(error, "doGet");
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* Upload data to the server.
|
|
286
|
+
* Returns an async iterable of PutResult messages containing server acknowledgements.
|
|
287
|
+
*
|
|
288
|
+
* Use the IPC encoding utilities to create FlightData from Arrow data:
|
|
289
|
+
* - `encodeRecordBatchesToFlightData()` - encode RecordBatch stream
|
|
290
|
+
* - `encodeTableToFlightData()` - encode a Table
|
|
291
|
+
*
|
|
292
|
+
* @param data - Async iterable of FlightData messages to upload (include descriptor in first message)
|
|
293
|
+
* @yields PutResult messages from the server
|
|
294
|
+
*
|
|
295
|
+
* @example
|
|
296
|
+
* ```ts
|
|
297
|
+
* import { encodeTableToFlightData } from "@qualithm/arrow-flight-client"
|
|
298
|
+
*
|
|
299
|
+
* const descriptor = { type: "path", path: ["my", "table"] }
|
|
300
|
+
* const flightData = encodeTableToFlightData(table)
|
|
301
|
+
*
|
|
302
|
+
* // Add descriptor to first message
|
|
303
|
+
* async function* withDescriptor() {
|
|
304
|
+
* let first = true
|
|
305
|
+
* for await (const data of flightData) {
|
|
306
|
+
* if (first) {
|
|
307
|
+
* yield { ...data, flightDescriptor: descriptor }
|
|
308
|
+
* first = false
|
|
309
|
+
* } else {
|
|
310
|
+
* yield data
|
|
311
|
+
* }
|
|
312
|
+
* }
|
|
313
|
+
* }
|
|
314
|
+
*
|
|
315
|
+
* for await (const result of client.doPut(withDescriptor())) {
|
|
316
|
+
* console.log("Server acknowledged:", result.appMetadata)
|
|
317
|
+
* }
|
|
318
|
+
* ```
|
|
319
|
+
*/
|
|
320
|
+
async *doPut(data) {
|
|
321
|
+
this.#assertOpen();
|
|
322
|
+
try {
|
|
323
|
+
const stream = this.#client.doPut(data, {
|
|
324
|
+
headers: this.#getRequestHeaders()
|
|
325
|
+
});
|
|
326
|
+
for await (const result of stream) {
|
|
327
|
+
yield result;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
catch (error) {
|
|
331
|
+
throw this.#wrapError(error, "doPut");
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
// ── Private helpers ──────────────────────────────────────────────────
|
|
335
|
+
/** Throws if the client has been closed. */
|
|
336
|
+
#assertOpen() {
|
|
337
|
+
if (this.#closed) {
|
|
338
|
+
throw new FlightError("client is closed");
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
/** Converts a FlightDescriptorInput to the proto FlightDescriptor format. */
|
|
342
|
+
#toFlightDescriptor(input) {
|
|
343
|
+
if (input.type === "path") {
|
|
344
|
+
return { type: 1, path: input.path }; // PATH = 1
|
|
345
|
+
}
|
|
346
|
+
return { type: 2, cmd: input.cmd }; // CMD = 2
|
|
347
|
+
}
|
|
348
|
+
/** Builds Node.js HTTP/2 options including TLS configuration. */
|
|
349
|
+
#buildNodeOptions() {
|
|
350
|
+
const nodeOptions = { ...this.#options.nodeOptions };
|
|
351
|
+
// Apply TLS options if configured
|
|
352
|
+
if (this.#options.tls) {
|
|
353
|
+
const { tls } = this.#options;
|
|
354
|
+
if (tls.cert !== undefined) {
|
|
355
|
+
nodeOptions.cert = tls.cert;
|
|
356
|
+
}
|
|
357
|
+
if (tls.key !== undefined) {
|
|
358
|
+
nodeOptions.key = tls.key;
|
|
359
|
+
}
|
|
360
|
+
if (tls.ca !== undefined) {
|
|
361
|
+
nodeOptions.ca = tls.ca;
|
|
362
|
+
}
|
|
363
|
+
if (tls.passphrase !== undefined && tls.passphrase !== "") {
|
|
364
|
+
nodeOptions.passphrase = tls.passphrase;
|
|
365
|
+
}
|
|
366
|
+
if (tls.rejectUnauthorized !== undefined) {
|
|
367
|
+
nodeOptions.rejectUnauthorized = tls.rejectUnauthorized;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
return nodeOptions;
|
|
371
|
+
}
|
|
372
|
+
/** Returns headers for requests, including auth token if authenticated. */
|
|
373
|
+
#getRequestHeaders() {
|
|
374
|
+
const headers = { ...this.#options.headers };
|
|
375
|
+
// Add auth token if authenticated via handshake
|
|
376
|
+
if (this.#authToken !== undefined && this.#authToken !== "") {
|
|
377
|
+
headers.Authorization = `Bearer ${this.#authToken}`;
|
|
378
|
+
}
|
|
379
|
+
return headers;
|
|
380
|
+
}
|
|
381
|
+
/** Wraps errors in appropriate FlightError subclasses based on error type. */
|
|
382
|
+
#wrapError(error, operation) {
|
|
383
|
+
if (FlightError.isError(error)) {
|
|
384
|
+
return error;
|
|
385
|
+
}
|
|
386
|
+
// Handle ConnectRPC errors
|
|
387
|
+
if (error instanceof Error && "code" in error) {
|
|
388
|
+
const connectError = error;
|
|
389
|
+
// Check for authentication-related errors
|
|
390
|
+
if (connectError.code === "UNAUTHENTICATED" || connectError.code === "PERMISSION_DENIED") {
|
|
391
|
+
return new FlightAuthError(`${operation} failed: ${connectError.message}`, error);
|
|
392
|
+
}
|
|
393
|
+
return new FlightServerError(`${operation} failed: ${connectError.message}`, connectError.code, connectError.rawMessage, error);
|
|
394
|
+
}
|
|
395
|
+
// Handle connection errors
|
|
396
|
+
if (error instanceof Error && error.message.includes("ECONNREFUSED")) {
|
|
397
|
+
return new FlightConnectionError(`failed to connect to ${this.#options.url}`, this.#options.url, error);
|
|
398
|
+
}
|
|
399
|
+
// Generic error wrapping
|
|
400
|
+
return new FlightError(`${operation} failed: ${error instanceof Error ? error.message : String(error)}`, error);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
//# sourceMappingURL=flight-client.js.map
|