snowpipe-streaming 1.4.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/README.md +104 -0
- package/package.json +37 -0
- package/src/channel.js +241 -0
- package/src/channel_status.js +179 -0
- package/src/client.js +216 -0
- package/src/errors.js +110 -0
- package/src/index.d.ts +351 -0
- package/src/index.js +51 -0
- package/src/loader.js +167 -0
package/src/client.js
ADDED
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
// Copyright 2026 Snowflake Inc.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
//
|
|
4
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
// you may not use this file except in compliance with the License.
|
|
6
|
+
// You may obtain a copy of the License at
|
|
7
|
+
//
|
|
8
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
//
|
|
10
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
// See the License for the specific language governing permissions and
|
|
14
|
+
// limitations under the License.
|
|
15
|
+
|
|
16
|
+
"use strict";
|
|
17
|
+
|
|
18
|
+
const { callFFI, StreamingIngestError } = require("./errors.js");
|
|
19
|
+
const { ChannelStatus } = require("./channel_status.js");
|
|
20
|
+
const { StreamingIngestChannel } = require("./channel.js");
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Streaming ingest client for creating channels to ingest data into Snowflake.
|
|
24
|
+
*
|
|
25
|
+
* Use {@link createClient} to create an instance — the constructor is not public.
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* const client = await createClient({
|
|
29
|
+
* clientName: "my-client",
|
|
30
|
+
* dbName: "MY_DB",
|
|
31
|
+
* schemaName: "MY_SCHEMA",
|
|
32
|
+
* pipeName: "MY_PIPE",
|
|
33
|
+
* properties: {
|
|
34
|
+
* account: "myaccount",
|
|
35
|
+
* user: "myuser",
|
|
36
|
+
* private_key: "-----BEGIN PRIVATE KEY-----\n...",
|
|
37
|
+
* },
|
|
38
|
+
* });
|
|
39
|
+
*
|
|
40
|
+
* const { channel, status } = await client.openChannel({ name: "ch1" });
|
|
41
|
+
* // ... insert rows via channel ...
|
|
42
|
+
* await client.close();
|
|
43
|
+
*/
|
|
44
|
+
class StreamingIngestClient {
|
|
45
|
+
/** @param {object} nativeClient - Internal JsClient from FFI */
|
|
46
|
+
constructor(nativeClient) {
|
|
47
|
+
this._native = nativeClient;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** @returns {string} The client name */
|
|
51
|
+
get clientName() {
|
|
52
|
+
return this._native.clientName;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** @returns {string} The database name */
|
|
56
|
+
get dbName() {
|
|
57
|
+
return this._native.dbName;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** @returns {string} The schema name */
|
|
61
|
+
get schemaName() {
|
|
62
|
+
return this._native.schemaName;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** @returns {string} The pipe name */
|
|
66
|
+
get pipeName() {
|
|
67
|
+
return this._native.pipeName;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** @returns {boolean} True if the client is closed */
|
|
71
|
+
get isClosed() {
|
|
72
|
+
return this._native.isClosed;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Get the binary input format for this client.
|
|
77
|
+
* @returns {string} One of: "BASE64", "HEX", "UTF-8"
|
|
78
|
+
*/
|
|
79
|
+
get binaryInputFormat() {
|
|
80
|
+
return this._native.binaryInputFormat;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Close the client.
|
|
85
|
+
* @param {{waitForFlush?: boolean, timeoutMs?: number}} [options]
|
|
86
|
+
* @returns {Promise<void>}
|
|
87
|
+
*/
|
|
88
|
+
async close(options) {
|
|
89
|
+
const nativeOpts = options
|
|
90
|
+
? { waitForFlush: options.waitForFlush, timeoutMs: options.timeoutMs }
|
|
91
|
+
: undefined;
|
|
92
|
+
return callFFI(() => this._native.close(nativeOpts));
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Open a channel for streaming data.
|
|
97
|
+
* @param {{name: string, offsetToken?: string}} options
|
|
98
|
+
* @returns {Promise<{channel: StreamingIngestChannel, status: ChannelStatus}>}
|
|
99
|
+
*/
|
|
100
|
+
async openChannel(options) {
|
|
101
|
+
const nativeChannel = await callFFI(() =>
|
|
102
|
+
this._native.openChannel(options.name, options.offsetToken),
|
|
103
|
+
);
|
|
104
|
+
const rawStatus = nativeChannel.channelStatus;
|
|
105
|
+
return {
|
|
106
|
+
channel: new StreamingIngestChannel(nativeChannel),
|
|
107
|
+
status: rawStatus ? new ChannelStatus(rawStatus) : null,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Drop a channel by name (server-side removal).
|
|
113
|
+
* @param {string} channelName
|
|
114
|
+
* @returns {Promise<void>}
|
|
115
|
+
*/
|
|
116
|
+
async dropChannel(channelName) {
|
|
117
|
+
return callFFI(() => this._native.dropChannel(channelName));
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Get the status of multiple channels.
|
|
122
|
+
* @param {string[]} channelNames
|
|
123
|
+
* @returns {Promise<Object.<string, ChannelStatus>>}
|
|
124
|
+
*/
|
|
125
|
+
async getChannelStatuses(channelNames) {
|
|
126
|
+
const raw = await callFFI(() => this._native.getChannelStatuses(channelNames));
|
|
127
|
+
const result = {};
|
|
128
|
+
for (const [name, data] of Object.entries(raw)) {
|
|
129
|
+
result[name] = new ChannelStatus(data);
|
|
130
|
+
}
|
|
131
|
+
return result;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Get the latest committed offset tokens for multiple channels.
|
|
136
|
+
* @param {string[]} channelNames
|
|
137
|
+
* @returns {Promise<Object.<string, string|null>>}
|
|
138
|
+
*/
|
|
139
|
+
async getLatestCommittedOffsetTokens(channelNames) {
|
|
140
|
+
const statuses = await this.getChannelStatuses(channelNames);
|
|
141
|
+
const result = {};
|
|
142
|
+
for (const [name, status] of Object.entries(statuses)) {
|
|
143
|
+
result[name] = status.latestCommittedOffsetToken;
|
|
144
|
+
}
|
|
145
|
+
return result;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Trigger a flush of all channels (non-blocking).
|
|
150
|
+
* @throws {StreamingIngestError} If the flush cannot be initiated
|
|
151
|
+
*/
|
|
152
|
+
initiateFlush() {
|
|
153
|
+
try {
|
|
154
|
+
this._native.initiateFlush();
|
|
155
|
+
} catch (e) {
|
|
156
|
+
throw StreamingIngestError.from(e);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Wait for all channels to flush.
|
|
162
|
+
* @param {{timeoutMs?: number}} [options]
|
|
163
|
+
* @returns {Promise<void>}
|
|
164
|
+
*/
|
|
165
|
+
async waitForFlush(options) {
|
|
166
|
+
return callFFI(() => this._native.waitForFlush(options?.timeoutMs));
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/** Support `await using client = ...` (Node 20+) */
|
|
170
|
+
async [Symbol.asyncDispose]() {
|
|
171
|
+
if (!this.isClosed) {
|
|
172
|
+
await this.close();
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Create a new streaming ingest client.
|
|
179
|
+
*
|
|
180
|
+
* @param {object} native - The native addon instance (provided by index.js)
|
|
181
|
+
* @param {{clientName: string, dbName: string, schemaName: string, pipeName: string, profilePath?: string, properties?: Object.<string, string>}} options
|
|
182
|
+
* @returns {Promise<StreamingIngestClient>}
|
|
183
|
+
*/
|
|
184
|
+
async function createClient(native, options) {
|
|
185
|
+
// Coerce property values to strings (matching Python)
|
|
186
|
+
let properties = options.properties;
|
|
187
|
+
if (properties) {
|
|
188
|
+
properties = Object.fromEntries(Object.entries(properties).map(([k, v]) => [k, String(v)]));
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Windows workaround: SNOW-2345722
|
|
192
|
+
if (process.platform === "win32") {
|
|
193
|
+
properties = properties || {};
|
|
194
|
+
if (!("mem_throttle_by_process" in properties)) {
|
|
195
|
+
properties.mem_throttle_by_process = "false";
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const nativeClient = await callFFI(() =>
|
|
200
|
+
native.createClient({
|
|
201
|
+
clientName: options.clientName,
|
|
202
|
+
dbName: options.dbName,
|
|
203
|
+
schemaName: options.schemaName,
|
|
204
|
+
pipeName: options.pipeName,
|
|
205
|
+
profilePath: options.profilePath,
|
|
206
|
+
properties,
|
|
207
|
+
}),
|
|
208
|
+
);
|
|
209
|
+
|
|
210
|
+
return new StreamingIngestClient(nativeClient);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
module.exports = {
|
|
214
|
+
StreamingIngestClient,
|
|
215
|
+
createClient,
|
|
216
|
+
};
|
package/src/errors.js
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
// Copyright 2026 Snowflake Inc.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
//
|
|
4
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
// you may not use this file except in compliance with the License.
|
|
6
|
+
// You may obtain a copy of the License at
|
|
7
|
+
//
|
|
8
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
//
|
|
10
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
// See the License for the specific language governing permissions and
|
|
14
|
+
// limitations under the License.
|
|
15
|
+
|
|
16
|
+
"use strict";
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Error class for Snowpipe Streaming SDK errors.
|
|
20
|
+
*
|
|
21
|
+
* This error is thrown when the Rust core encounters an error condition.
|
|
22
|
+
* It includes structured error information including an error code,
|
|
23
|
+
* HTTP status code, and descriptive message.
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* try {
|
|
27
|
+
* await client.openChannel({ name: "ch1" });
|
|
28
|
+
* } catch (e) {
|
|
29
|
+
* if (e instanceof StreamingIngestError) {
|
|
30
|
+
* console.log(e.errorCode, e.httpStatusCode, e.message);
|
|
31
|
+
* }
|
|
32
|
+
* }
|
|
33
|
+
*
|
|
34
|
+
* @extends Error
|
|
35
|
+
*/
|
|
36
|
+
class StreamingIngestError extends Error {
|
|
37
|
+
/**
|
|
38
|
+
* Create a StreamingIngestError.
|
|
39
|
+
* @param {string} errorCode - The error code (e.g., "ConfigError", "ChannelNotFound")
|
|
40
|
+
* @param {string} message - The error message
|
|
41
|
+
* @param {number} httpStatusCode - HTTP status code indicating error category
|
|
42
|
+
* @param {string} httpStatusName - HTTP status name (e.g., "Bad Request")
|
|
43
|
+
*/
|
|
44
|
+
constructor(errorCode, message, httpStatusCode, httpStatusName) {
|
|
45
|
+
super(message);
|
|
46
|
+
this.name = "StreamingIngestError";
|
|
47
|
+
this.errorCode = errorCode;
|
|
48
|
+
this.httpStatusCode = httpStatusCode;
|
|
49
|
+
this.httpStatusName = httpStatusName;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Create a StreamingIngestError from an FFI error.
|
|
54
|
+
*
|
|
55
|
+
* FFI errors from the Rust core have their message encoded as JSON with
|
|
56
|
+
* error_code, message, http_status_code, and http_status_name fields.
|
|
57
|
+
* This method parses that JSON and constructs a StreamingIngestError.
|
|
58
|
+
*
|
|
59
|
+
* @param {Error} e - The error from FFI
|
|
60
|
+
* @returns {StreamingIngestError|Error} Parsed error or original if not parseable
|
|
61
|
+
*/
|
|
62
|
+
static from(e) {
|
|
63
|
+
try {
|
|
64
|
+
const data = JSON.parse(e.message);
|
|
65
|
+
if (data.error_code && data.message && data.http_status_code !== undefined) {
|
|
66
|
+
return new StreamingIngestError(
|
|
67
|
+
data.error_code,
|
|
68
|
+
data.message,
|
|
69
|
+
data.http_status_code,
|
|
70
|
+
data.http_status_name || "Unknown",
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
} catch {
|
|
74
|
+
// Not JSON, return original
|
|
75
|
+
}
|
|
76
|
+
return e;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Returns a string representation of the error.
|
|
81
|
+
* @returns {string} Formatted error string
|
|
82
|
+
*/
|
|
83
|
+
toString() {
|
|
84
|
+
return `${this.errorCode}: ${this.message} (HTTP ${this.httpStatusCode} ${this.httpStatusName})`;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Wrap an FFI call with error conversion.
|
|
90
|
+
*
|
|
91
|
+
* Equivalent to Python's @_rethrow_ffi_errors decorator.
|
|
92
|
+
* Catches napi errors, parses the JSON message, and throws
|
|
93
|
+
* a StreamingIngestError.
|
|
94
|
+
*
|
|
95
|
+
* @param {Function} fn - The FFI function to call
|
|
96
|
+
* @returns {Promise<*>} The result of the FFI call
|
|
97
|
+
* @throws {StreamingIngestError} If the FFI call fails
|
|
98
|
+
*/
|
|
99
|
+
async function callFFI(fn) {
|
|
100
|
+
try {
|
|
101
|
+
return await fn();
|
|
102
|
+
} catch (e) {
|
|
103
|
+
throw StreamingIngestError.from(e);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
module.exports = {
|
|
108
|
+
StreamingIngestError,
|
|
109
|
+
callFFI,
|
|
110
|
+
};
|
package/src/index.d.ts
ADDED
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2026 Snowflake Inc.
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
//
|
|
19
|
+
// TypeScript declaration file for the Snowpipe Streaming Node.js SDK.
|
|
20
|
+
//
|
|
21
|
+
// This file serves two purposes:
|
|
22
|
+
// 1. Provides TypeScript/JavaScript users with autocomplete and type checking
|
|
23
|
+
// 2. Used by TypeDoc (see docs/typedoc.json) to generate API reference HTML
|
|
24
|
+
//
|
|
25
|
+
// This file mirrors the public API surface defined in src/*.js.
|
|
26
|
+
// The CI lint step (`npm run lint`) runs check-types-sync.js which verifies
|
|
27
|
+
// that this file stays consistent with the JavaScript source.
|
|
28
|
+
//
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Create a new streaming ingest client. Each client is tied to a single
|
|
32
|
+
* database, schema, and pipe. To ingest into multiple pipes, create
|
|
33
|
+
* multiple clients.
|
|
34
|
+
*
|
|
35
|
+
* Either `profilePath` or `properties` must be provided for authentication.
|
|
36
|
+
* If both are provided, `properties` takes precedence for conflicting keys.
|
|
37
|
+
*
|
|
38
|
+
* @param options - Client configuration
|
|
39
|
+
* @throws StreamingIngestError If client creation fails
|
|
40
|
+
*/
|
|
41
|
+
export function createClient(options: {
|
|
42
|
+
/** A unique name to identify this client instance, used for tracking and debugging purposes. */
|
|
43
|
+
clientName: string;
|
|
44
|
+
/** The name of the Snowflake database where data will be ingested. */
|
|
45
|
+
dbName: string;
|
|
46
|
+
/** The name of the schema within the database. */
|
|
47
|
+
schemaName: string;
|
|
48
|
+
/** The name of the pipe that will be used for streaming ingestion. */
|
|
49
|
+
pipeName: string;
|
|
50
|
+
/** Path to a JSON file containing connection properties and authentication information. */
|
|
51
|
+
profilePath?: string;
|
|
52
|
+
/** Connection properties and authentication information. Common properties include account, user, private_key, url. */
|
|
53
|
+
properties?: Record<string, string>;
|
|
54
|
+
}): Promise<StreamingIngestClient>;
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* A client that is the starting point for using the Streaming Ingest client APIs.
|
|
58
|
+
*
|
|
59
|
+
* A single client maps to exactly one account/database/schema/pipe in Snowflake;
|
|
60
|
+
* however, multiple clients can point to the same account/database/schema/pipe.
|
|
61
|
+
* Each client contains information for Snowflake authentication and authorization,
|
|
62
|
+
* and it is used to create one or more StreamingIngestChannel instances for data
|
|
63
|
+
* ingestion.
|
|
64
|
+
*
|
|
65
|
+
* Create instances using the `createClient()` function.
|
|
66
|
+
*/
|
|
67
|
+
export class StreamingIngestClient {
|
|
68
|
+
private constructor();
|
|
69
|
+
|
|
70
|
+
/** The client name. */
|
|
71
|
+
readonly clientName: string;
|
|
72
|
+
/** The database name. */
|
|
73
|
+
readonly dbName: string;
|
|
74
|
+
/** The schema name. */
|
|
75
|
+
readonly schemaName: string;
|
|
76
|
+
/** The pipe name. */
|
|
77
|
+
readonly pipeName: string;
|
|
78
|
+
/** Whether the client is closed. */
|
|
79
|
+
readonly isClosed: boolean;
|
|
80
|
+
/** The binary input format. One of: "BASE64", "HEX", "UTF-8". */
|
|
81
|
+
readonly binaryInputFormat: string;
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Close the client. If waitForFlush is true, the client waits for all data in every
|
|
85
|
+
* channel to be flushed to Snowflake before shutting down.
|
|
86
|
+
*
|
|
87
|
+
* @param options - Close options
|
|
88
|
+
* @throws StreamingIngestError If closing the client fails or the timeout is reached
|
|
89
|
+
*/
|
|
90
|
+
close(options?: {
|
|
91
|
+
/** Whether to wait for the flush to complete, defaults to true. */
|
|
92
|
+
waitForFlush?: boolean;
|
|
93
|
+
/** Optional timeout in milliseconds for the flush operation. */
|
|
94
|
+
timeoutMs?: number;
|
|
95
|
+
}): Promise<void>;
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Open a channel with the given name. The channel is opened on the
|
|
99
|
+
* database/schema/pipe defined by this client. If the channel already exists,
|
|
100
|
+
* Snowflake reuses the latest persisted offset token unless a new one is provided.
|
|
101
|
+
*
|
|
102
|
+
* @param options - Channel options
|
|
103
|
+
* @throws StreamingIngestError If opening the channel fails
|
|
104
|
+
*/
|
|
105
|
+
openChannel(options: {
|
|
106
|
+
/** The name of the channel to open. */
|
|
107
|
+
name: string;
|
|
108
|
+
/** Optional offset token to set on the channel. If not provided and this reopens an existing channel, the latest persisted offset token is reused. */
|
|
109
|
+
offsetToken?: string;
|
|
110
|
+
}): Promise<{
|
|
111
|
+
/** The opened channel instance. */
|
|
112
|
+
channel: StreamingIngestChannel;
|
|
113
|
+
/** The channel status at the time of opening, or null if not available. */
|
|
114
|
+
status: ChannelStatus | null;
|
|
115
|
+
}>;
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Drop a channel by name (server-side removal).
|
|
119
|
+
*
|
|
120
|
+
* @param channelName - Name of the channel to drop
|
|
121
|
+
* @throws StreamingIngestError If dropping the channel fails
|
|
122
|
+
*/
|
|
123
|
+
dropChannel(channelName: string): Promise<void>;
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Get the status of multiple channels. Can fetch status for channels not
|
|
127
|
+
* opened by this client.
|
|
128
|
+
*
|
|
129
|
+
* @param channelNames - Names of channels to query
|
|
130
|
+
* @throws StreamingIngestError If retrieving statuses fails
|
|
131
|
+
*/
|
|
132
|
+
getChannelStatuses(channelNames: string[]): Promise<Record<string, ChannelStatus>>;
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Get the latest committed offset tokens for multiple channels.
|
|
136
|
+
*
|
|
137
|
+
* @param channelNames - Names of channels to query
|
|
138
|
+
* @throws StreamingIngestError If retrieving offset tokens fails
|
|
139
|
+
*/
|
|
140
|
+
getLatestCommittedOffsetTokens(channelNames: string[]): Promise<Record<string, string | null>>;
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Initiate a flush of the client. Causes all outstanding buffered data to be
|
|
144
|
+
* flushed to Snowflake. Data can still be accepted by the client after calling
|
|
145
|
+
* this method — this is an asynchronous call that returns after the flush is
|
|
146
|
+
* initiated for all channels.
|
|
147
|
+
*
|
|
148
|
+
* @throws StreamingIngestError If initiating the flush fails
|
|
149
|
+
*/
|
|
150
|
+
initiateFlush(): void;
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Wait for all buffered data in all channels managed by this client to be flushed
|
|
154
|
+
* to Snowflake. This method triggers a flush of all pending data across all channels
|
|
155
|
+
* and waits for the flush operations to complete.
|
|
156
|
+
*
|
|
157
|
+
* @param options - Wait options
|
|
158
|
+
* @throws StreamingIngestError If waiting for the flush fails
|
|
159
|
+
* @throws Error If the timeout is reached
|
|
160
|
+
*/
|
|
161
|
+
waitForFlush(options?: {
|
|
162
|
+
/** Optional timeout in milliseconds. */
|
|
163
|
+
timeoutMs?: number;
|
|
164
|
+
}): Promise<void>;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* A channel for streaming data into a Snowflake table.
|
|
169
|
+
*
|
|
170
|
+
* A channel represents a logical stream of data into a single table through a pipe.
|
|
171
|
+
* Multiple channels can be opened on the same pipe for parallel ingestion. Each channel
|
|
172
|
+
* maintains its own offset token for exactly-once delivery guarantees.
|
|
173
|
+
*
|
|
174
|
+
* Create instances using `client.openChannel()`.
|
|
175
|
+
*/
|
|
176
|
+
export class StreamingIngestChannel {
|
|
177
|
+
private constructor();
|
|
178
|
+
|
|
179
|
+
/** The channel name. */
|
|
180
|
+
readonly channelName: string;
|
|
181
|
+
/** The database name. */
|
|
182
|
+
readonly dbName: string;
|
|
183
|
+
/** The schema name. */
|
|
184
|
+
readonly schemaName: string;
|
|
185
|
+
/** The pipe name. */
|
|
186
|
+
readonly pipeName: string;
|
|
187
|
+
/** Whether the channel is closed. */
|
|
188
|
+
readonly isClosed: boolean;
|
|
189
|
+
/** The binary input format. One of: "BASE64", "HEX", "UTF-8". */
|
|
190
|
+
readonly binaryInputFormat: string;
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Append a single row into the channel.
|
|
194
|
+
*
|
|
195
|
+
* The row is an object with keys as column names and values as column values.
|
|
196
|
+
* Supported value types: null, undefined, boolean, number, string, BigInt,
|
|
197
|
+
* Buffer, Uint8Array, Date, Array, and Object.
|
|
198
|
+
*
|
|
199
|
+
* @param row - Row data as column-name to value pairs
|
|
200
|
+
* @param offsetToken - Optional offset token, used to track the ingestion
|
|
201
|
+
* progress and replay ingestion in case of failures
|
|
202
|
+
* @throws StreamingIngestError If the row appending fails
|
|
203
|
+
*/
|
|
204
|
+
appendRow(row: Record<string, any>, offsetToken?: string): Promise<void>;
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Append multiple rows to the channel.
|
|
208
|
+
*
|
|
209
|
+
* Each row is an object with keys as column names and values as column values.
|
|
210
|
+
* Supported value types: null, undefined, boolean, number, string, BigInt,
|
|
211
|
+
* Buffer, Uint8Array, Date, Array, and Object.
|
|
212
|
+
*
|
|
213
|
+
* @param rows - Array of row objects (column-name to value pairs)
|
|
214
|
+
* @param startOffsetToken - Optional start offset token of the batch
|
|
215
|
+
* @param endOffsetToken - Optional end offset token of the batch
|
|
216
|
+
* @throws TypeError If rows is not an array
|
|
217
|
+
* @throws StreamingIngestError If the rows appending fails
|
|
218
|
+
*/
|
|
219
|
+
appendRows(rows: Record<string, any>[], startOffsetToken?: string, endOffsetToken?: string): Promise<void>;
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Close the channel.
|
|
223
|
+
*
|
|
224
|
+
* @param options - Close options
|
|
225
|
+
* @throws StreamingIngestError If closing the channel fails
|
|
226
|
+
*/
|
|
227
|
+
close(options?: {
|
|
228
|
+
/** Whether to drop the channel on the server, defaults to false. */
|
|
229
|
+
drop?: boolean;
|
|
230
|
+
/** Whether to wait for the flush to complete, defaults to true. */
|
|
231
|
+
waitForFlush?: boolean;
|
|
232
|
+
/** Optional timeout in milliseconds for the flush operation. */
|
|
233
|
+
timeoutMs?: number;
|
|
234
|
+
}): Promise<void>;
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Initiate a flush of all buffered data for this channel but do not wait for
|
|
238
|
+
* the flush to complete. Calls to appendRow/appendRows are still allowed on
|
|
239
|
+
* the channel after calling this method.
|
|
240
|
+
*
|
|
241
|
+
* @throws StreamingIngestError If the flush cannot be initiated
|
|
242
|
+
*/
|
|
243
|
+
initiateFlush(): void;
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Wait for all buffered data in this channel to be flushed to Snowflake.
|
|
247
|
+
*
|
|
248
|
+
* @param options - Wait options
|
|
249
|
+
* @throws StreamingIngestError If waiting for the flush fails
|
|
250
|
+
* @throws Error If the timeout is reached
|
|
251
|
+
*/
|
|
252
|
+
waitForFlush(options?: {
|
|
253
|
+
/** Optional timeout in milliseconds. */
|
|
254
|
+
timeoutMs?: number;
|
|
255
|
+
}): Promise<void>;
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Get the current status of this channel.
|
|
259
|
+
*
|
|
260
|
+
* @throws StreamingIngestError If getting the channel status fails
|
|
261
|
+
*/
|
|
262
|
+
getChannelStatus(): Promise<ChannelStatus>;
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Get the latest committed offset token for this channel.
|
|
266
|
+
*
|
|
267
|
+
* @returns The latest committed offset token, or null if the channel is brand new
|
|
268
|
+
* @throws StreamingIngestError If getting the latest committed offset token fails
|
|
269
|
+
*/
|
|
270
|
+
getLatestCommittedOffsetToken(): Promise<string | null>;
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Wait for a specific offset token to be committed.
|
|
274
|
+
*
|
|
275
|
+
* Snowflake commits offset tokens in batches, so the latest committed offset token
|
|
276
|
+
* may jump past the one you are waiting for. The tokenChecker should handle this
|
|
277
|
+
* case by doing a range check (greater than or equal) rather than an exact match.
|
|
278
|
+
*
|
|
279
|
+
* @param tokenChecker - A callable that receives the latest committed offset token
|
|
280
|
+
* (which may be null) and should return true when the wait condition is satisfied
|
|
281
|
+
* @param options - Wait options
|
|
282
|
+
* @throws StreamingIngestError If waiting for the commit fails
|
|
283
|
+
* @throws Error If the timeout is reached
|
|
284
|
+
* @throws TypeError If tokenChecker is not a function
|
|
285
|
+
* @throws RangeError If timeoutMs is negative
|
|
286
|
+
*/
|
|
287
|
+
waitForCommit(tokenChecker: (token: string | null) => boolean, options?: {
|
|
288
|
+
/** Optional timeout in milliseconds. */
|
|
289
|
+
timeoutMs?: number;
|
|
290
|
+
}): Promise<void>;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Status information for a streaming ingest channel.
|
|
295
|
+
*
|
|
296
|
+
* Contains metadata about the channel's current state including the latest
|
|
297
|
+
* committed offset token, row counts, error information, and processing latency.
|
|
298
|
+
* Returned by `client.getChannelStatuses()` and `channel.getChannelStatus()`.
|
|
299
|
+
*/
|
|
300
|
+
export class ChannelStatus {
|
|
301
|
+
private constructor();
|
|
302
|
+
|
|
303
|
+
/** The database name. */
|
|
304
|
+
readonly databaseName: string;
|
|
305
|
+
/** The schema name. */
|
|
306
|
+
readonly schemaName: string;
|
|
307
|
+
/** The pipe name. */
|
|
308
|
+
readonly pipeName: string;
|
|
309
|
+
/** The channel name. */
|
|
310
|
+
readonly channelName: string;
|
|
311
|
+
/** The channel status code. */
|
|
312
|
+
readonly statusCode: string;
|
|
313
|
+
/** The latest committed offset token, or null if the channel is brand new. */
|
|
314
|
+
readonly latestCommittedOffsetToken: string | null;
|
|
315
|
+
/** When the channel was created. */
|
|
316
|
+
readonly createdOn: Date | null;
|
|
317
|
+
/** Total number of rows successfully inserted. */
|
|
318
|
+
readonly rowsInsertedCount: number;
|
|
319
|
+
/** Total number of rows parsed. */
|
|
320
|
+
readonly rowsParsedCount: number;
|
|
321
|
+
/** Total number of rows that encountered errors. */
|
|
322
|
+
readonly rowsErrorCount: number;
|
|
323
|
+
/** Upper bound offset token for the last error, or null if no errors. */
|
|
324
|
+
readonly lastErrorOffsetTokenUpperBound: string | null;
|
|
325
|
+
/** The message of the last error, or null if no errors. */
|
|
326
|
+
readonly lastErrorMessage: string | null;
|
|
327
|
+
/** Timestamp of the last error, or null if no errors. */
|
|
328
|
+
readonly lastErrorTimestamp: Date | null;
|
|
329
|
+
/** Average processing latency in milliseconds, or null if not available. */
|
|
330
|
+
readonly serverAvgProcessingLatencyMs: number | null;
|
|
331
|
+
/** When the status was last refreshed from the server. */
|
|
332
|
+
readonly lastRefreshedOn: Date | null;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Error thrown by the Snowpipe Streaming SDK.
|
|
337
|
+
*
|
|
338
|
+
* Includes structured error information: an error code, HTTP status code,
|
|
339
|
+
* and descriptive message. Use `errorCode` and `httpStatusCode` to determine
|
|
340
|
+
* the appropriate recovery action.
|
|
341
|
+
*/
|
|
342
|
+
export class StreamingIngestError extends Error {
|
|
343
|
+
/** The error code (for example, "ConfigError", "ChannelNotFound"). */
|
|
344
|
+
readonly errorCode: string;
|
|
345
|
+
/** HTTP status code indicating error category. */
|
|
346
|
+
readonly httpStatusCode: number;
|
|
347
|
+
/** HTTP status name (for example, "Bad Request"). */
|
|
348
|
+
readonly httpStatusName: string;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
|