snowpipe-streaming 1.4.0 → 1.6.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/package.json +7 -7
- package/src/channel.js +20 -17
- package/src/index.d.ts +5 -5
- package/src/loader.js +49 -23
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "snowpipe-streaming",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.0",
|
|
4
4
|
"description": "Snowflake Streaming Ingest SDK for Node.js",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"types": "src/index.d.ts",
|
|
@@ -27,11 +27,11 @@
|
|
|
27
27
|
"typedoc": "^0.28.19"
|
|
28
28
|
},
|
|
29
29
|
"optionalDependencies": {
|
|
30
|
-
"snowpipe-streaming-linux-x64-gnu": "1.
|
|
31
|
-
"snowpipe-streaming-linux-x64-musl": "1.
|
|
32
|
-
"snowpipe-streaming-linux-arm64-gnu": "1.
|
|
33
|
-
"snowpipe-streaming-linux-arm64-musl": "1.
|
|
34
|
-
"snowpipe-streaming-darwin-arm64": "1.
|
|
35
|
-
"snowpipe-streaming-win32-x64-msvc": "1.
|
|
30
|
+
"snowpipe-streaming-linux-x64-gnu": "1.6.0",
|
|
31
|
+
"snowpipe-streaming-linux-x64-musl": "1.6.0",
|
|
32
|
+
"snowpipe-streaming-linux-arm64-gnu": "1.6.0",
|
|
33
|
+
"snowpipe-streaming-linux-arm64-musl": "1.6.0",
|
|
34
|
+
"snowpipe-streaming-darwin-arm64": "1.6.0",
|
|
35
|
+
"snowpipe-streaming-win32-x64-msvc": "1.6.0"
|
|
36
36
|
}
|
|
37
37
|
}
|
package/src/channel.js
CHANGED
|
@@ -104,10 +104,11 @@ class StreamingIngestChannel {
|
|
|
104
104
|
* Append a single row. Convenience wrapper for appendRows.
|
|
105
105
|
* @param {Object} row - Row data as key-value pairs
|
|
106
106
|
* @param {string} [offsetToken] - Offset token for this row
|
|
107
|
-
* @returns {
|
|
107
|
+
* @returns {void}
|
|
108
|
+
* @throws {StreamingIngestError} If the row appending fails
|
|
108
109
|
*/
|
|
109
|
-
|
|
110
|
-
|
|
110
|
+
appendRow(row, offsetToken) {
|
|
111
|
+
this.appendRows([row], offsetToken, offsetToken);
|
|
111
112
|
}
|
|
112
113
|
|
|
113
114
|
/**
|
|
@@ -116,25 +117,27 @@ class StreamingIngestChannel {
|
|
|
116
117
|
* @param {Object[]} rows - Array of row objects
|
|
117
118
|
* @param {string} [startOffsetToken] - Start offset token for this batch
|
|
118
119
|
* @param {string} [endOffsetToken] - End offset token for this batch
|
|
119
|
-
* @returns {
|
|
120
|
+
* @returns {void}
|
|
121
|
+
* @throws {TypeError} If rows is not an array
|
|
122
|
+
* @throws {StreamingIngestError} If the rows appending fails
|
|
120
123
|
*/
|
|
121
|
-
|
|
124
|
+
appendRows(rows, startOffsetToken, endOffsetToken) {
|
|
122
125
|
if (!Array.isArray(rows)) {
|
|
123
126
|
throw new TypeError("rows must be an array");
|
|
124
127
|
}
|
|
125
|
-
// Empty arrays are handled by the Rust core, which returns a proper InvalidRequest error
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
128
|
+
// Empty arrays are handled by the Rust core, which returns a proper InvalidRequest error.
|
|
129
|
+
const buffer =
|
|
130
|
+
rows.length === 0
|
|
131
|
+
? Buffer.alloc(0)
|
|
132
|
+
: Buffer.from(
|
|
133
|
+
rows.map((r) => JSON.stringify(this._convertBytesInRow(r))).join("\n"),
|
|
134
|
+
"utf8",
|
|
135
|
+
);
|
|
136
|
+
try {
|
|
137
|
+
this._native.appendRows(buffer, rows.length, startOffsetToken, endOffsetToken);
|
|
138
|
+
} catch (e) {
|
|
139
|
+
throw StreamingIngestError.from(e);
|
|
131
140
|
}
|
|
132
|
-
// Convert binary values according to binaryInputFormat before JSON serialization
|
|
133
|
-
const ndjson = rows.map((r) => JSON.stringify(this._convertBytesInRow(r))).join("\n");
|
|
134
|
-
const buffer = Buffer.from(ndjson, "utf8");
|
|
135
|
-
return callFFI(() =>
|
|
136
|
-
this._native.appendRows(buffer, rows.length, startOffsetToken, endOffsetToken),
|
|
137
|
-
);
|
|
138
141
|
}
|
|
139
142
|
|
|
140
143
|
/**
|
package/src/index.d.ts
CHANGED
|
@@ -49,7 +49,9 @@ export function createClient(options: {
|
|
|
49
49
|
pipeName: string;
|
|
50
50
|
/** Path to a JSON file containing connection properties and authentication information. */
|
|
51
51
|
profilePath?: string;
|
|
52
|
-
/** Connection properties and authentication information. Common properties include account, user, private_key, url
|
|
52
|
+
/** Connection properties and authentication information. Common properties include account, user, private_key, url
|
|
53
|
+
* and application (partner app name for User-Agent; 1–64 visible ASCII chars (0x21–0x7E),
|
|
54
|
+
* excluding `(`, `)`, `\`; regex: `^[!-'*-\[-\]-~]{1,64}$`). */
|
|
53
55
|
properties?: Record<string, string>;
|
|
54
56
|
}): Promise<StreamingIngestClient>;
|
|
55
57
|
|
|
@@ -201,7 +203,7 @@ export class StreamingIngestChannel {
|
|
|
201
203
|
* progress and replay ingestion in case of failures
|
|
202
204
|
* @throws StreamingIngestError If the row appending fails
|
|
203
205
|
*/
|
|
204
|
-
appendRow(row: Record<string, any>, offsetToken?: string):
|
|
206
|
+
appendRow(row: Record<string, any>, offsetToken?: string): void;
|
|
205
207
|
|
|
206
208
|
/**
|
|
207
209
|
* Append multiple rows to the channel.
|
|
@@ -216,7 +218,7 @@ export class StreamingIngestChannel {
|
|
|
216
218
|
* @throws TypeError If rows is not an array
|
|
217
219
|
* @throws StreamingIngestError If the rows appending fails
|
|
218
220
|
*/
|
|
219
|
-
appendRows(rows: Record<string, any>[], startOffsetToken?: string, endOffsetToken?: string):
|
|
221
|
+
appendRows(rows: Record<string, any>[], startOffsetToken?: string, endOffsetToken?: string): void;
|
|
220
222
|
|
|
221
223
|
/**
|
|
222
224
|
* Close the channel.
|
|
@@ -347,5 +349,3 @@ export class StreamingIngestError extends Error {
|
|
|
347
349
|
/** HTTP status name (for example, "Bad Request"). */
|
|
348
350
|
readonly httpStatusName: string;
|
|
349
351
|
}
|
|
350
|
-
|
|
351
|
-
|
package/src/loader.js
CHANGED
|
@@ -22,6 +22,25 @@ const fs = require("node:fs");
|
|
|
22
22
|
|
|
23
23
|
const LOG_PREFIX = "[snowpipe-streaming]";
|
|
24
24
|
|
|
25
|
+
// Verbose loader breadcrumbs are gated on SS_LOG_LEVEL=debug|trace, matching
|
|
26
|
+
// the env var the Rust core and Java/Python SDKs already use (see
|
|
27
|
+
// rust/src/util/bootstrap_config.rs and FFIBootstrap.java). Default: silent on
|
|
28
|
+
// the success path. Fall-through and error paths still write to stderr.
|
|
29
|
+
// Match Rust's case-sensitive lowercase comparison so the same env var value
|
|
30
|
+
// is interpreted identically on both sides of the FFI boundary.
|
|
31
|
+
const VERBOSE = ["debug", "trace"].includes(process.env.SS_LOG_LEVEL || "");
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Emit a loader breadcrumb to stderr when SS_LOG_LEVEL=debug|trace.
|
|
35
|
+
* No-op otherwise — keeps the success path silent on both stdout and stderr.
|
|
36
|
+
* @param {string} msg
|
|
37
|
+
*/
|
|
38
|
+
function debug(msg) {
|
|
39
|
+
if (VERBOSE) {
|
|
40
|
+
console.error(`${LOG_PREFIX} ${msg}`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
25
44
|
/**
|
|
26
45
|
* Platform package name mappings.
|
|
27
46
|
* Maps (os, arch, libc) to the npm package suffix.
|
|
@@ -45,7 +64,7 @@ function detectLibc() {
|
|
|
45
64
|
try {
|
|
46
65
|
const libDir = fs.readdirSync("/lib");
|
|
47
66
|
if (libDir.some((f) => f.startsWith("ld-musl-"))) {
|
|
48
|
-
|
|
67
|
+
debug("Detected musl libc (found /lib/ld-musl-*)");
|
|
49
68
|
return "musl";
|
|
50
69
|
}
|
|
51
70
|
} catch {
|
|
@@ -57,7 +76,7 @@ function detectLibc() {
|
|
|
57
76
|
const { execSync } = require("node:child_process");
|
|
58
77
|
const lddOutput = execSync("ldd --version 2>&1", { encoding: "utf8" });
|
|
59
78
|
if (lddOutput.includes("musl")) {
|
|
60
|
-
|
|
79
|
+
debug("Detected musl libc (ldd --version)");
|
|
61
80
|
return "musl";
|
|
62
81
|
}
|
|
63
82
|
} catch {
|
|
@@ -68,15 +87,13 @@ function detectLibc() {
|
|
|
68
87
|
if (process.report && typeof process.report.getReport === "function") {
|
|
69
88
|
const report = process.report.getReport();
|
|
70
89
|
if (report?.header?.glibcVersionRuntime) {
|
|
71
|
-
|
|
72
|
-
`${LOG_PREFIX} Detected glibc ${report.header.glibcVersionRuntime} (process.report)`,
|
|
73
|
-
);
|
|
90
|
+
debug(`Detected glibc ${report.header.glibcVersionRuntime} (process.report)`);
|
|
74
91
|
return "glibc";
|
|
75
92
|
}
|
|
76
93
|
}
|
|
77
94
|
|
|
78
95
|
// Default to glibc (most common)
|
|
79
|
-
|
|
96
|
+
debug("Defaulting to glibc (no musl indicators found)");
|
|
80
97
|
return "glibc";
|
|
81
98
|
}
|
|
82
99
|
|
|
@@ -91,12 +108,12 @@ function getPlatformKey() {
|
|
|
91
108
|
if (platform === "linux") {
|
|
92
109
|
const libc = detectLibc();
|
|
93
110
|
const key = `${platform}-${arch}-${libc}`;
|
|
94
|
-
|
|
111
|
+
debug(`Platform: ${key} (os=${platform}, arch=${arch}, libc=${libc})`);
|
|
95
112
|
return key;
|
|
96
113
|
}
|
|
97
114
|
|
|
98
115
|
const key = `${platform}-${arch}`;
|
|
99
|
-
|
|
116
|
+
debug(`Platform: ${key} (os=${platform}, arch=${arch})`);
|
|
100
117
|
return key;
|
|
101
118
|
}
|
|
102
119
|
|
|
@@ -113,10 +130,15 @@ function loadNativeAddon() {
|
|
|
113
130
|
// Mode 1: Bazel development - ADDON_PATH points directly to the .node file
|
|
114
131
|
if (process.env.ADDON_PATH) {
|
|
115
132
|
const addonPath = path.resolve(process.env.ADDON_PATH);
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
133
|
+
debug(`Loading native addon from ADDON_PATH: ${addonPath}`);
|
|
134
|
+
try {
|
|
135
|
+
return require(addonPath);
|
|
136
|
+
} catch (err) {
|
|
137
|
+
console.error(
|
|
138
|
+
`${LOG_PREFIX} Failed to load native addon from ADDON_PATH ${addonPath}: ${err.message}`,
|
|
139
|
+
);
|
|
140
|
+
throw err;
|
|
141
|
+
}
|
|
120
142
|
}
|
|
121
143
|
|
|
122
144
|
// Mode 2: Production - load from platform-specific npm package
|
|
@@ -125,33 +147,37 @@ function loadNativeAddon() {
|
|
|
125
147
|
|
|
126
148
|
if (packageSuffix) {
|
|
127
149
|
const packageName = `snowpipe-streaming-${packageSuffix}`;
|
|
128
|
-
|
|
150
|
+
debug(`Attempting to load platform package: ${packageName}`);
|
|
129
151
|
try {
|
|
130
|
-
|
|
131
|
-
console.log(`${LOG_PREFIX} Native addon loaded successfully from ${packageName}`);
|
|
132
|
-
return addon;
|
|
152
|
+
return require(packageName);
|
|
133
153
|
} catch (err) {
|
|
134
154
|
// Package not installed - fall through to fallback
|
|
135
155
|
if (err.code !== "MODULE_NOT_FOUND") {
|
|
136
156
|
console.error(`${LOG_PREFIX} Failed to load ${packageName}: ${err.message}`);
|
|
137
157
|
throw err;
|
|
138
158
|
}
|
|
139
|
-
console.
|
|
159
|
+
console.error(
|
|
160
|
+
`${LOG_PREFIX} Platform package ${packageName} not found, trying local fallback`,
|
|
161
|
+
);
|
|
140
162
|
}
|
|
141
163
|
} else {
|
|
142
|
-
console.
|
|
164
|
+
console.error(
|
|
143
165
|
`${LOG_PREFIX} No platform package mapping for ${platformKey}, trying local fallback`,
|
|
144
166
|
);
|
|
145
167
|
}
|
|
146
168
|
|
|
147
169
|
// Mode 3: Fallback - local .node file (for testing or manual installation)
|
|
148
170
|
const localAddon = path.resolve(__dirname, "..", "snowpipe_streaming.node");
|
|
149
|
-
console.log(`${LOG_PREFIX} Checking for local addon: ${localAddon}`);
|
|
150
171
|
if (fs.existsSync(localAddon)) {
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
172
|
+
debug(`Loading native addon from local file: ${localAddon}`);
|
|
173
|
+
try {
|
|
174
|
+
return require(localAddon);
|
|
175
|
+
} catch (err) {
|
|
176
|
+
console.error(
|
|
177
|
+
`${LOG_PREFIX} Failed to load native addon from local file ${localAddon}: ${err.message}`,
|
|
178
|
+
);
|
|
179
|
+
throw err;
|
|
180
|
+
}
|
|
155
181
|
}
|
|
156
182
|
|
|
157
183
|
// No addon found - provide helpful error message
|