@keystrokehq/snowflake 0.0.1
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 +247 -0
- package/dist/_official/index.d.mts +2 -0
- package/dist/_official/index.mjs +3 -0
- package/dist/_runtime/index.d.mts +1 -0
- package/dist/_runtime/index.mjs +1 -0
- package/dist/accounts.d.mts +40 -0
- package/dist/accounts.mjs +51 -0
- package/dist/bulk.d.mts +71 -0
- package/dist/bulk.mjs +65 -0
- package/dist/catalog.d.mts +144 -0
- package/dist/catalog.mjs +158 -0
- package/dist/client.d.mts +44 -0
- package/dist/client.mjs +273 -0
- package/dist/common-BnKTPQXc.mjs +92 -0
- package/dist/connection.d.mts +2 -0
- package/dist/connection.mjs +3 -0
- package/dist/databases.d.mts +2 -0
- package/dist/databases.mjs +3 -0
- package/dist/errors-DKnc9o_a.mjs +95 -0
- package/dist/events.d.mts +382 -0
- package/dist/events.mjs +291 -0
- package/dist/file-formats.d.mts +39 -0
- package/dist/file-formats.mjs +48 -0
- package/dist/functions.d.mts +53 -0
- package/dist/functions.mjs +56 -0
- package/dist/grants.d.mts +93 -0
- package/dist/grants.mjs +102 -0
- package/dist/index.d.mts +1 -0
- package/dist/index.mjs +1 -0
- package/dist/integration-CCWMSTlO.d.mts +170 -0
- package/dist/integration-Dptv8L0L.mjs +207 -0
- package/dist/org-admin.d.mts +80 -0
- package/dist/org-admin.mjs +79 -0
- package/dist/pipes.d.mts +119 -0
- package/dist/pipes.mjs +106 -0
- package/dist/procedures.d.mts +61 -0
- package/dist/procedures.mjs +79 -0
- package/dist/results.d.mts +16 -0
- package/dist/results.mjs +64 -0
- package/dist/retry-0hMfb8cC.mjs +164 -0
- package/dist/retry-w7cTp1QL.d.mts +10 -0
- package/dist/roles.d.mts +60 -0
- package/dist/roles.mjs +74 -0
- package/dist/rows.d.mts +106 -0
- package/dist/rows.mjs +223 -0
- package/dist/schemas/index.d.mts +2 -0
- package/dist/schemas/index.mjs +4 -0
- package/dist/schemas-catalog.d.mts +2 -0
- package/dist/schemas-catalog.mjs +3 -0
- package/dist/shares.d.mts +56 -0
- package/dist/shares.mjs +77 -0
- package/dist/sql-options-2k5xQ-oS.d.mts +32 -0
- package/dist/sql-options-DI-OmLU6.mjs +79 -0
- package/dist/sql-safety-CywdR3EP.mjs +56 -0
- package/dist/sql.d.mts +84 -0
- package/dist/sql.mjs +209 -0
- package/dist/stages.d.mts +64 -0
- package/dist/stages.mjs +81 -0
- package/dist/statements-B2ThF_4b.mjs +81 -0
- package/dist/statements-DJL0qVNA.d.mts +238 -0
- package/dist/status-page.d.mts +510 -0
- package/dist/status-page.mjs +261 -0
- package/dist/streaming.d.mts +70 -0
- package/dist/streaming.mjs +56 -0
- package/dist/streams.d.mts +71 -0
- package/dist/streams.mjs +78 -0
- package/dist/tables.d.mts +2 -0
- package/dist/tables.mjs +3 -0
- package/dist/tasks.d.mts +79 -0
- package/dist/tasks.mjs +104 -0
- package/dist/triggers.d.mts +578 -0
- package/dist/triggers.mjs +1363 -0
- package/dist/users.d.mts +61 -0
- package/dist/users.mjs +67 -0
- package/dist/verification.d.mts +201 -0
- package/dist/verification.mjs +512 -0
- package/dist/views.d.mts +2 -0
- package/dist/views.mjs +3 -0
- package/dist/warehouses.d.mts +68 -0
- package/dist/warehouses.mjs +101 -0
- package/package.json +190 -0
package/dist/catalog.mjs
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { executeSql } from "./sql.mjs";
|
|
2
|
+
import { i as quoteIdentifier, n as inClause, r as likeClause, t as buildFqn } from "./sql-safety-CywdR3EP.mjs";
|
|
3
|
+
|
|
4
|
+
//#region src/catalog.ts
|
|
5
|
+
function limitClause(limit) {
|
|
6
|
+
return limit && limit > 0 ? ` LIMIT ${Math.floor(limit)}` : "";
|
|
7
|
+
}
|
|
8
|
+
function toScope(options) {
|
|
9
|
+
if (options.inAccount) return " IN ACCOUNT";
|
|
10
|
+
if (options.inSchema) return inClause("SCHEMA", buildFqn(options.inSchema.database, options.inSchema.schema));
|
|
11
|
+
if (options.inDatabase) return inClause("DATABASE", quoteIdentifier(options.inDatabase));
|
|
12
|
+
return "";
|
|
13
|
+
}
|
|
14
|
+
async function runShow(client, sql, ctx) {
|
|
15
|
+
const result = await executeSql(client, {
|
|
16
|
+
statement: sql,
|
|
17
|
+
warehouse: ctx.warehouse,
|
|
18
|
+
role: ctx.role,
|
|
19
|
+
database: ctx.database,
|
|
20
|
+
schema: ctx.schema
|
|
21
|
+
});
|
|
22
|
+
return {
|
|
23
|
+
rows: result.rows,
|
|
24
|
+
columns: result.columns.map((c) => ({
|
|
25
|
+
name: c.name,
|
|
26
|
+
type: c.type
|
|
27
|
+
}))
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
async function showDatabases(client, options = {}) {
|
|
31
|
+
return runShow(client, `SHOW DATABASES${likeClause(options.like)}${limitClause(options.limit)}`, options);
|
|
32
|
+
}
|
|
33
|
+
async function showSchemas(client, options = {}) {
|
|
34
|
+
return runShow(client, `SHOW${options.history ? " HISTORY" : ""} SCHEMAS${toScope(options)}${likeClause(options.like)}${limitClause(options.limit)}`, options);
|
|
35
|
+
}
|
|
36
|
+
async function showTables(client, options = {}) {
|
|
37
|
+
return runShow(client, `SHOW${options.terse ? " TERSE" : ""}${options.history ? " HISTORY" : ""} TABLES${toScope(options)}${likeClause(options.like)}${limitClause(options.limit)}`, options);
|
|
38
|
+
}
|
|
39
|
+
async function showViews(client, options = {}) {
|
|
40
|
+
return runShow(client, `SHOW${options.terse ? " TERSE" : ""} VIEWS${toScope(options)}${likeClause(options.like)}${limitClause(options.limit)}`, options);
|
|
41
|
+
}
|
|
42
|
+
async function showMaterializedViews(client, options = {}) {
|
|
43
|
+
return runShow(client, `SHOW MATERIALIZED VIEWS${toScope(options)}${likeClause(options.like)}${limitClause(options.limit)}`, options);
|
|
44
|
+
}
|
|
45
|
+
async function showColumns(client, options = {}) {
|
|
46
|
+
let scope = "";
|
|
47
|
+
if (options.inTable) scope = ` IN TABLE ${buildFqn(options.inTable.database, options.inTable.schema, options.inTable.table)}`;
|
|
48
|
+
else if (options.inView) scope = ` IN VIEW ${buildFqn(options.inView.database, options.inView.schema, options.inView.view)}`;
|
|
49
|
+
else if (options.inSchema) scope = ` IN SCHEMA ${buildFqn(options.inSchema.database, options.inSchema.schema)}`;
|
|
50
|
+
else if (options.inDatabase) scope = ` IN DATABASE ${quoteIdentifier(options.inDatabase)}`;
|
|
51
|
+
return runShow(client, `SHOW COLUMNS${scope}${likeClause(options.like)}${limitClause(options.limit)}`, options);
|
|
52
|
+
}
|
|
53
|
+
async function showWarehouses(client, options = {}) {
|
|
54
|
+
return runShow(client, `SHOW WAREHOUSES${likeClause(options.like)}${limitClause(options.limit)}`, options);
|
|
55
|
+
}
|
|
56
|
+
async function showRoles(client, options = {}) {
|
|
57
|
+
return runShow(client, `SHOW ROLES${likeClause(options.like)}${limitClause(options.limit)}`, options);
|
|
58
|
+
}
|
|
59
|
+
async function showUsers(client, options = {}) {
|
|
60
|
+
return runShow(client, `SHOW USERS${likeClause(options.like)}${limitClause(options.limit)}`, options);
|
|
61
|
+
}
|
|
62
|
+
async function showStages(client, options = {}) {
|
|
63
|
+
return runShow(client, `SHOW STAGES${toScope(options)}${likeClause(options.like)}${limitClause(options.limit)}`, options);
|
|
64
|
+
}
|
|
65
|
+
async function showPipes(client, options = {}) {
|
|
66
|
+
return runShow(client, `SHOW PIPES${toScope(options)}${likeClause(options.like)}${limitClause(options.limit)}`, options);
|
|
67
|
+
}
|
|
68
|
+
async function showTasks(client, options = {}) {
|
|
69
|
+
return runShow(client, `SHOW${options.terse ? " TERSE" : ""} TASKS${toScope(options)}${likeClause(options.like)}${limitClause(options.limit)}`, options);
|
|
70
|
+
}
|
|
71
|
+
async function showStreams(client, options = {}) {
|
|
72
|
+
return runShow(client, `SHOW STREAMS${toScope(options)}${likeClause(options.like)}${limitClause(options.limit)}`, options);
|
|
73
|
+
}
|
|
74
|
+
async function showShares(client, options = {}) {
|
|
75
|
+
return runShow(client, `SHOW SHARES${likeClause(options.like)}${limitClause(options.limit)}`, options);
|
|
76
|
+
}
|
|
77
|
+
async function showFunctions(client, options = {}) {
|
|
78
|
+
return runShow(client, `SHOW FUNCTIONS${toScope(options)}${likeClause(options.like)}${limitClause(options.limit)}`, options);
|
|
79
|
+
}
|
|
80
|
+
async function showProcedures(client, options = {}) {
|
|
81
|
+
return runShow(client, `SHOW PROCEDURES${toScope(options)}${likeClause(options.like)}${limitClause(options.limit)}`, options);
|
|
82
|
+
}
|
|
83
|
+
async function showFileFormats(client, options = {}) {
|
|
84
|
+
return runShow(client, `SHOW FILE FORMATS${toScope(options)}${likeClause(options.like)}${limitClause(options.limit)}`, options);
|
|
85
|
+
}
|
|
86
|
+
async function showGrants(client, options = {}) {
|
|
87
|
+
let tail = "";
|
|
88
|
+
if (options.on) {
|
|
89
|
+
const fqn = buildFqn(options.on.database, options.on.schema, options.on.name);
|
|
90
|
+
tail = ` ON ${options.on.objectType.toUpperCase()} ${fqn}`;
|
|
91
|
+
} else if (options.toRole) tail = ` TO ROLE ${quoteIdentifier(options.toRole)}`;
|
|
92
|
+
else if (options.toUser) tail = ` TO USER ${quoteIdentifier(options.toUser)}`;
|
|
93
|
+
else if (options.toShare) tail = ` TO SHARE ${quoteIdentifier(options.toShare)}`;
|
|
94
|
+
else if (options.ofRole) tail = ` OF ROLE ${quoteIdentifier(options.ofRole)}`;
|
|
95
|
+
else if (options.ofShare) tail = ` OF SHARE ${quoteIdentifier(options.ofShare)}`;
|
|
96
|
+
return runShow(client, `SHOW GRANTS${tail}${likeClause(options.like)}`, options);
|
|
97
|
+
}
|
|
98
|
+
async function describeTable(client, options) {
|
|
99
|
+
return runShow(client, `DESCRIBE TABLE ${buildFqn(options.database, options.schema, options.table)}${options.type ? ` TYPE = ${options.type}` : ""}`, options);
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Composio `SNOWFLAKE_BASIC_EXPLORE_COLUMNS` parity — pull a small distinct
|
|
103
|
+
* sample from each requested column to help LLMs disambiguate column
|
|
104
|
+
* semantics.
|
|
105
|
+
*/
|
|
106
|
+
async function exploreColumns(client, options) {
|
|
107
|
+
const fqn = buildFqn(options.database, options.schema, options.table);
|
|
108
|
+
const columns = options.columns && options.columns.length > 0 ? options.columns : (await describeTable(client, options)).rows.map((row) => row.name ?? row.NAME).filter((name) => Boolean(name));
|
|
109
|
+
const limit = options.limit ?? 100;
|
|
110
|
+
const samples = [];
|
|
111
|
+
for (const column of columns) {
|
|
112
|
+
const distinctValues = (await executeSql(client, {
|
|
113
|
+
statement: `SELECT DISTINCT ${quoteIdentifier(column)} AS v FROM ${fqn} LIMIT ${Math.max(1, Math.floor(limit))}`,
|
|
114
|
+
warehouse: options.warehouse,
|
|
115
|
+
role: options.role
|
|
116
|
+
})).rows.map((row) => row.V ?? row.v);
|
|
117
|
+
samples.push({
|
|
118
|
+
column,
|
|
119
|
+
distinctValues
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
return {
|
|
123
|
+
table: fqn,
|
|
124
|
+
samples
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
function applyNameFilter(rows, substring) {
|
|
128
|
+
if (!substring) return [...rows];
|
|
129
|
+
const needle = substring.toLowerCase();
|
|
130
|
+
return rows.filter((row) => {
|
|
131
|
+
const name = row.name ?? row.NAME;
|
|
132
|
+
return typeof name === "string" && name.toLowerCase().includes(needle);
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
async function findDatabases(client, options = {}) {
|
|
136
|
+
const result = await showDatabases(client, options);
|
|
137
|
+
return {
|
|
138
|
+
columns: result.columns,
|
|
139
|
+
rows: applyNameFilter(result.rows, options.nameContains)
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
async function findSchemas(client, options = {}) {
|
|
143
|
+
const result = await showSchemas(client, options);
|
|
144
|
+
return {
|
|
145
|
+
columns: result.columns,
|
|
146
|
+
rows: applyNameFilter(result.rows, options.nameContains)
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
async function findTables(client, options = {}) {
|
|
150
|
+
const result = await showTables(client, options);
|
|
151
|
+
return {
|
|
152
|
+
columns: result.columns,
|
|
153
|
+
rows: applyNameFilter(result.rows, options.nameContains)
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
//#endregion
|
|
158
|
+
export { describeTable, exploreColumns, findDatabases, findSchemas, findTables, showColumns, showDatabases, showFileFormats, showFunctions, showGrants, showMaterializedViews, showPipes, showProcedures, showRoles, showSchemas, showShares, showStages, showStreams, showTables, showTasks, showUsers, showViews, showWarehouses };
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { n as SnowflakeCredentials } from "./integration-CCWMSTlO.mjs";
|
|
2
|
+
import { t as RetryPolicy } from "./retry-w7cTp1QL.mjs";
|
|
3
|
+
|
|
4
|
+
//#region src/client.d.ts
|
|
5
|
+
type SnowflakeAuthTokenType = 'KEYPAIR_JWT' | 'OAUTH' | 'PROGRAMMATIC_ACCESS_TOKEN' | 'BASIC';
|
|
6
|
+
type SnowflakeHttpMethod = 'DELETE' | 'GET' | 'PATCH' | 'POST' | 'PUT';
|
|
7
|
+
type QueryPrimitive = string | number | boolean;
|
|
8
|
+
type SnowflakeQueryValue = QueryPrimitive | readonly QueryPrimitive[] | null | undefined;
|
|
9
|
+
interface SnowflakeQuery {
|
|
10
|
+
readonly [key: string]: SnowflakeQueryValue;
|
|
11
|
+
}
|
|
12
|
+
interface SnowflakeRequestInit {
|
|
13
|
+
readonly method?: SnowflakeHttpMethod;
|
|
14
|
+
readonly body?: unknown;
|
|
15
|
+
readonly query?: SnowflakeQuery;
|
|
16
|
+
readonly headers?: Readonly<Record<string, string>>;
|
|
17
|
+
readonly timeoutMs?: number;
|
|
18
|
+
readonly requestId?: string;
|
|
19
|
+
/** Raw body; when true the response object is returned verbatim. */
|
|
20
|
+
readonly raw?: boolean;
|
|
21
|
+
/** Disable retries for this call (statement cancel, etc.). */
|
|
22
|
+
readonly retry?: boolean | RetryPolicy;
|
|
23
|
+
}
|
|
24
|
+
interface SnowflakeClientOptions {
|
|
25
|
+
readonly fetchImpl?: typeof fetch;
|
|
26
|
+
readonly timeoutMs?: number;
|
|
27
|
+
readonly retry?: RetryPolicy;
|
|
28
|
+
readonly sleep?: (ms: number) => Promise<void>;
|
|
29
|
+
readonly now?: () => number;
|
|
30
|
+
readonly userAgent?: string;
|
|
31
|
+
readonly randomUUID?: () => string;
|
|
32
|
+
}
|
|
33
|
+
interface SnowflakeClient {
|
|
34
|
+
readonly host: string;
|
|
35
|
+
readonly account: string;
|
|
36
|
+
readonly credentials: SnowflakeCredentials;
|
|
37
|
+
request<T = unknown>(path: string, init?: SnowflakeRequestInit): Promise<T>;
|
|
38
|
+
requestRaw(path: string, init?: SnowflakeRequestInit): Promise<Response>;
|
|
39
|
+
buildUrl(path: string, query?: SnowflakeQuery): string;
|
|
40
|
+
}
|
|
41
|
+
declare function createSnowflakeClient(rawCredentials: SnowflakeCredentials | unknown, options?: SnowflakeClientOptions): SnowflakeClient;
|
|
42
|
+
declare function resolveHost(credentials: SnowflakeCredentials): string;
|
|
43
|
+
//#endregion
|
|
44
|
+
export { SnowflakeAuthTokenType, SnowflakeClient, SnowflakeClientOptions, SnowflakeHttpMethod, SnowflakeQuery, SnowflakeQueryValue, SnowflakeRequestInit, createSnowflakeClient, resolveHost };
|
package/dist/client.mjs
ADDED
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
import { t as parseSnowflakeCredentials } from "./integration-Dptv8L0L.mjs";
|
|
2
|
+
import { a as extractSnowflakeMessage, c as parseRetryAfterHeader, i as extractSnowflakeCode, n as SnowflakeSqlError, o as extractSnowflakeSqlState, r as classifySnowflakeHttpStatus, s as extractSnowflakeStatementHandle, t as SnowflakeApiError } from "./errors-DKnc9o_a.mjs";
|
|
3
|
+
import { a as createSnowflakeJwt, i as shouldRetry, n as computeBackoffMs, r as defaultSleep, t as DEFAULT_RETRY_POLICY } from "./retry-0hMfb8cC.mjs";
|
|
4
|
+
|
|
5
|
+
//#region src/client.ts
|
|
6
|
+
/**
|
|
7
|
+
* Core HTTP client for the Snowflake integration.
|
|
8
|
+
*
|
|
9
|
+
* Shape — kept intentionally generic so the same client backs every domain
|
|
10
|
+
* module (SQL API v2, Snowpipe REST, org-admin REST). Higher-level helpers
|
|
11
|
+
* (`sql.ts`, `pipes.ts`, ...) consume `SnowflakeClient.request` and layer
|
|
12
|
+
* their own schemas on top.
|
|
13
|
+
*
|
|
14
|
+
* Responsibilities:
|
|
15
|
+
* - resolve credentials -> `Authorization` header + auth-token-type header
|
|
16
|
+
* - sign JWTs for `mode=keypair` and cache them until expiry
|
|
17
|
+
* - apply default retry policy with exponential backoff + `Retry-After`
|
|
18
|
+
* - classify non-2xx responses into structured errors (see `errors.ts`)
|
|
19
|
+
* - attach a deterministic `requestId` for idempotent statement submission
|
|
20
|
+
*/
|
|
21
|
+
const DEFAULT_TIMEOUT_MS = 3e4;
|
|
22
|
+
const JWT_REFRESH_SKEW_SECONDS = 60;
|
|
23
|
+
const USER_AGENT = "keystroke-integration-snowflake/0.0.0";
|
|
24
|
+
function createSnowflakeClient(rawCredentials, options = {}) {
|
|
25
|
+
const credentials = isSnowflakeCredentials(rawCredentials) ? rawCredentials : parseSnowflakeCredentials(rawCredentials);
|
|
26
|
+
const fetchImpl = options.fetchImpl ?? fetch;
|
|
27
|
+
const sleep = options.sleep ?? defaultSleep;
|
|
28
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
29
|
+
const retryPolicy = options.retry ?? DEFAULT_RETRY_POLICY;
|
|
30
|
+
const now = options.now ?? (() => Date.now());
|
|
31
|
+
const userAgent = options.userAgent ?? USER_AGENT;
|
|
32
|
+
const randomUUID = options.randomUUID ?? (() => typeof crypto !== "undefined" && typeof crypto.randomUUID === "function" ? crypto.randomUUID() : pseudoRandomUuid());
|
|
33
|
+
const host = resolveHost(credentials);
|
|
34
|
+
const account = credentials.SNOWFLAKE_ACCOUNT.trim();
|
|
35
|
+
let cachedJwt;
|
|
36
|
+
async function authorization() {
|
|
37
|
+
if (credentials.mode === "keypair") {
|
|
38
|
+
const nowMs = now();
|
|
39
|
+
if (!cachedJwt || cachedJwt.expiresAtMs - JWT_REFRESH_SKEW_SECONDS * 1e3 <= nowMs) {
|
|
40
|
+
if (!credentials.SNOWFLAKE_USER || !credentials.SNOWFLAKE_PRIVATE_KEY) throw new SnowflakeApiError({
|
|
41
|
+
kind: "auth",
|
|
42
|
+
status: 0,
|
|
43
|
+
message: "SNOWFLAKE_USER and SNOWFLAKE_PRIVATE_KEY are required for keypair mode"
|
|
44
|
+
});
|
|
45
|
+
const jwt = await createSnowflakeJwt({
|
|
46
|
+
account,
|
|
47
|
+
user: credentials.SNOWFLAKE_USER,
|
|
48
|
+
privateKeyPem: credentials.SNOWFLAKE_PRIVATE_KEY,
|
|
49
|
+
publicKeyFingerprint: credentials.SNOWFLAKE_PUBLIC_KEY_FP,
|
|
50
|
+
now: () => nowMs
|
|
51
|
+
});
|
|
52
|
+
cachedJwt = {
|
|
53
|
+
token: jwt.token,
|
|
54
|
+
expiresAtMs: jwt.expiresAt * 1e3
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
return {
|
|
58
|
+
header: `Bearer ${cachedJwt.token}`,
|
|
59
|
+
tokenType: "KEYPAIR_JWT"
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
if (credentials.mode === "oauth") {
|
|
63
|
+
if (!credentials.SNOWFLAKE_OAUTH_ACCESS_TOKEN) throw new SnowflakeApiError({
|
|
64
|
+
kind: "auth",
|
|
65
|
+
status: 0,
|
|
66
|
+
message: "SNOWFLAKE_OAUTH_ACCESS_TOKEN is required for oauth mode"
|
|
67
|
+
});
|
|
68
|
+
return {
|
|
69
|
+
header: `Bearer ${credentials.SNOWFLAKE_OAUTH_ACCESS_TOKEN}`,
|
|
70
|
+
tokenType: "OAUTH"
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
if (credentials.mode === "pat") {
|
|
74
|
+
if (!credentials.SNOWFLAKE_PAT) throw new SnowflakeApiError({
|
|
75
|
+
kind: "auth",
|
|
76
|
+
status: 0,
|
|
77
|
+
message: "SNOWFLAKE_PAT is required for pat mode"
|
|
78
|
+
});
|
|
79
|
+
return {
|
|
80
|
+
header: `Bearer ${credentials.SNOWFLAKE_PAT}`,
|
|
81
|
+
tokenType: "PROGRAMMATIC_ACCESS_TOKEN"
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
if (!credentials.SNOWFLAKE_USER || !credentials.SNOWFLAKE_PASSWORD) throw new SnowflakeApiError({
|
|
85
|
+
kind: "auth",
|
|
86
|
+
status: 0,
|
|
87
|
+
message: "SNOWFLAKE_USER and SNOWFLAKE_PASSWORD are required for basic mode"
|
|
88
|
+
});
|
|
89
|
+
return {
|
|
90
|
+
header: `Basic ${base64EncodeString(`${credentials.SNOWFLAKE_USER}:${credentials.SNOWFLAKE_PASSWORD}`)}`,
|
|
91
|
+
tokenType: "BASIC"
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
function buildUrl(path, query) {
|
|
95
|
+
return `${path.startsWith("http://") || path.startsWith("https://") ? path : `https://${host}${path.startsWith("/") ? path : `/${path}`}`}${toQueryString(query)}`;
|
|
96
|
+
}
|
|
97
|
+
async function requestRaw(path, init = {}) {
|
|
98
|
+
const method = init.method ?? "GET";
|
|
99
|
+
const policy = resolveRetryPolicy(init.retry, retryPolicy);
|
|
100
|
+
const attemptTimeoutMs = init.timeoutMs ?? timeoutMs;
|
|
101
|
+
const requestId = init.requestId ?? randomUUID();
|
|
102
|
+
const url = buildUrl(path, {
|
|
103
|
+
...init.query ?? {},
|
|
104
|
+
...method === "POST" && path.includes("/api/v2/statements") ? { requestId } : {}
|
|
105
|
+
});
|
|
106
|
+
let body;
|
|
107
|
+
const headers = {
|
|
108
|
+
Accept: "application/json",
|
|
109
|
+
"User-Agent": userAgent,
|
|
110
|
+
...init.headers
|
|
111
|
+
};
|
|
112
|
+
if (init.body !== void 0) {
|
|
113
|
+
headers["Content-Type"] = "application/json";
|
|
114
|
+
body = JSON.stringify(init.body);
|
|
115
|
+
}
|
|
116
|
+
let attempt = 0;
|
|
117
|
+
for (;;) {
|
|
118
|
+
const auth = await authorization();
|
|
119
|
+
headers.Authorization = auth.header;
|
|
120
|
+
headers["X-Snowflake-Authorization-Token-Type"] = auth.tokenType;
|
|
121
|
+
const controller = new AbortController();
|
|
122
|
+
const timer = setTimeout(() => controller.abort(), attemptTimeoutMs);
|
|
123
|
+
let response;
|
|
124
|
+
try {
|
|
125
|
+
response = await fetchImpl(url, {
|
|
126
|
+
method,
|
|
127
|
+
headers,
|
|
128
|
+
...body !== void 0 ? { body } : {},
|
|
129
|
+
signal: controller.signal
|
|
130
|
+
});
|
|
131
|
+
} catch (error) {
|
|
132
|
+
clearTimeout(timer);
|
|
133
|
+
const isAbort = error instanceof Error && (error.name === "AbortError" || /aborted|abort/iu.test(error.message));
|
|
134
|
+
const kind = isAbort ? "timeout" : "network";
|
|
135
|
+
if (policy && shouldRetry(kind, attempt, policy)) {
|
|
136
|
+
attempt += 1;
|
|
137
|
+
await sleep(computeBackoffMs({
|
|
138
|
+
attempt,
|
|
139
|
+
policy
|
|
140
|
+
}));
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
throw new SnowflakeApiError({
|
|
144
|
+
kind,
|
|
145
|
+
status: 0,
|
|
146
|
+
message: isAbort ? `Request to \`${path}\` timed out after ${attemptTimeoutMs}ms` : `Request to \`${path}\` failed: ${error?.message ?? String(error)}`,
|
|
147
|
+
requestId,
|
|
148
|
+
cause: error
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
clearTimeout(timer);
|
|
152
|
+
if (response.ok || response.status === 202) return response;
|
|
153
|
+
const parsed = await readResponseBody(response);
|
|
154
|
+
const retryAfterMs = parseRetryAfterHeader(response.headers.get("retry-after"));
|
|
155
|
+
const kind = classifySnowflakeHttpStatus(response.status);
|
|
156
|
+
if (policy && shouldRetry(kind, attempt, policy)) {
|
|
157
|
+
attempt += 1;
|
|
158
|
+
await sleep(computeBackoffMs({
|
|
159
|
+
attempt,
|
|
160
|
+
retryAfterMs,
|
|
161
|
+
policy
|
|
162
|
+
}));
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
throw toStructuredError({
|
|
166
|
+
response,
|
|
167
|
+
body: parsed,
|
|
168
|
+
requestId,
|
|
169
|
+
retryAfterMs
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
async function request(path, init = {}) {
|
|
174
|
+
const response = await requestRaw(path, init);
|
|
175
|
+
if (init.raw) return response;
|
|
176
|
+
if (response.status === 204) return void 0;
|
|
177
|
+
return await readResponseBody(response);
|
|
178
|
+
}
|
|
179
|
+
return {
|
|
180
|
+
host,
|
|
181
|
+
account,
|
|
182
|
+
credentials,
|
|
183
|
+
request,
|
|
184
|
+
requestRaw,
|
|
185
|
+
buildUrl
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
function resolveRetryPolicy(override, fallback) {
|
|
189
|
+
if (override === false) return void 0;
|
|
190
|
+
if (override === true || override === void 0) return fallback;
|
|
191
|
+
return override;
|
|
192
|
+
}
|
|
193
|
+
async function readResponseBody(response) {
|
|
194
|
+
if ((response.headers.get("content-type") ?? "").includes("application/json")) return response.json().catch(() => void 0);
|
|
195
|
+
return response.text().catch(() => void 0);
|
|
196
|
+
}
|
|
197
|
+
function toStructuredError(args) {
|
|
198
|
+
const { response, body, requestId, retryAfterMs } = args;
|
|
199
|
+
const kind = classifySnowflakeHttpStatus(response.status);
|
|
200
|
+
const message = extractSnowflakeMessage(body) ?? `Snowflake HTTP ${response.status} (${response.statusText})`;
|
|
201
|
+
const providerCode = extractSnowflakeCode(body);
|
|
202
|
+
const snowflakeRequestId = response.headers.get("x-snowflake-request-id") ?? void 0;
|
|
203
|
+
if (response.status === 422) return new SnowflakeSqlError({
|
|
204
|
+
kind: "sql",
|
|
205
|
+
status: response.status,
|
|
206
|
+
message,
|
|
207
|
+
body,
|
|
208
|
+
providerCode,
|
|
209
|
+
requestId: snowflakeRequestId ?? requestId,
|
|
210
|
+
retryAfterMs,
|
|
211
|
+
sqlState: extractSnowflakeSqlState(body),
|
|
212
|
+
code: providerCode,
|
|
213
|
+
statementHandle: extractSnowflakeStatementHandle(body)
|
|
214
|
+
});
|
|
215
|
+
return new SnowflakeApiError({
|
|
216
|
+
kind,
|
|
217
|
+
status: response.status,
|
|
218
|
+
message,
|
|
219
|
+
body,
|
|
220
|
+
providerCode,
|
|
221
|
+
requestId: snowflakeRequestId ?? requestId,
|
|
222
|
+
retryAfterMs
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
function resolveHost(credentials) {
|
|
226
|
+
if (credentials.SNOWFLAKE_HOST) return stripProtocol(credentials.SNOWFLAKE_HOST).replace(/\/+$/u, "");
|
|
227
|
+
const account = credentials.SNOWFLAKE_ACCOUNT.trim();
|
|
228
|
+
const region = credentials.SNOWFLAKE_REGION?.trim();
|
|
229
|
+
if (region && !account.includes(".") && !account.includes("-")) return `${account}.${region}.snowflakecomputing.com`;
|
|
230
|
+
if (/snowflakecomputing\.com$/u.test(account)) return account;
|
|
231
|
+
return `${account}.snowflakecomputing.com`;
|
|
232
|
+
}
|
|
233
|
+
function stripProtocol(host) {
|
|
234
|
+
return host.replace(/^https?:\/\//u, "");
|
|
235
|
+
}
|
|
236
|
+
function toQueryString(query) {
|
|
237
|
+
if (!query) return "";
|
|
238
|
+
const params = new URLSearchParams();
|
|
239
|
+
for (const [key, value] of Object.entries(query)) {
|
|
240
|
+
if (value === void 0 || value === null) continue;
|
|
241
|
+
if (Array.isArray(value)) {
|
|
242
|
+
for (const item of value) params.append(key, String(item));
|
|
243
|
+
continue;
|
|
244
|
+
}
|
|
245
|
+
params.set(key, String(value));
|
|
246
|
+
}
|
|
247
|
+
const qs = params.toString();
|
|
248
|
+
return qs.length > 0 ? `?${qs}` : "";
|
|
249
|
+
}
|
|
250
|
+
function isSnowflakeCredentials(value) {
|
|
251
|
+
if (typeof value !== "object" || value === null) return false;
|
|
252
|
+
const mode = value.mode;
|
|
253
|
+
return mode === "keypair" || mode === "oauth" || mode === "pat" || mode === "basic";
|
|
254
|
+
}
|
|
255
|
+
function base64EncodeString(input) {
|
|
256
|
+
if (typeof btoa === "function") return btoa(unescape(encodeURIComponent(input)));
|
|
257
|
+
/* c8 ignore next */
|
|
258
|
+
return Buffer.from(input, "utf8").toString("base64");
|
|
259
|
+
}
|
|
260
|
+
function pseudoRandomUuid() {
|
|
261
|
+
/* c8 ignore start */
|
|
262
|
+
const bytes = new Uint8Array(16);
|
|
263
|
+
for (let i = 0; i < 16; i += 1) bytes[i] = Math.floor(Math.random() * 256);
|
|
264
|
+
bytes[6] = (bytes[6] ?? 0) & 15 | 64;
|
|
265
|
+
bytes[8] = (bytes[8] ?? 0) & 63 | 128;
|
|
266
|
+
const hex = [];
|
|
267
|
+
for (const b of bytes) hex.push(b.toString(16).padStart(2, "0"));
|
|
268
|
+
return `${hex.slice(0, 4).join("")}-${hex.slice(4, 6).join("")}-${hex.slice(6, 8).join("")}-${hex.slice(8, 10).join("")}-${hex.slice(10, 16).join("")}`;
|
|
269
|
+
/* c8 ignore end */
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
//#endregion
|
|
273
|
+
export { createSnowflakeClient, resolveHost };
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
//#region src/schemas/common.ts
|
|
4
|
+
/**
|
|
5
|
+
* Cross-domain Zod primitives for the Snowflake integration.
|
|
6
|
+
*
|
|
7
|
+
* These schemas are the single source of truth for identifier shapes,
|
|
8
|
+
* fully-qualified names, statement context, and row / column metadata.
|
|
9
|
+
* Downstream domain files (sql.ts, results.ts, rows.ts, tables.ts, ...)
|
|
10
|
+
* compose them to avoid drift.
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* Snowflake identifiers are 1-255 chars. Unquoted identifiers are normalized
|
|
14
|
+
* to upper-case; quoted identifiers preserve case. We keep raw validation
|
|
15
|
+
* permissive and let the server reject bad names — a strict regex would
|
|
16
|
+
* forbid unicode-quoted identifiers legitimately used in the wild.
|
|
17
|
+
*/
|
|
18
|
+
const snowflakeIdentifier = z.string().min(1, "Snowflake identifier must not be empty").max(255, "Snowflake identifier must be 255 chars or less");
|
|
19
|
+
/**
|
|
20
|
+
* Fully-qualified name: `DATABASE.SCHEMA.NAME` (or subsets). Stored as a
|
|
21
|
+
* single string; we do *not* split/rejoin so callers can pass quoted
|
|
22
|
+
* segments through verbatim.
|
|
23
|
+
*/
|
|
24
|
+
const snowflakeFqn = z.string().min(1);
|
|
25
|
+
/**
|
|
26
|
+
* Optional context overrides applied per-statement.
|
|
27
|
+
* Every field is optional — absent values fall back to the connection's
|
|
28
|
+
* session defaults and then to account-level defaults.
|
|
29
|
+
*/
|
|
30
|
+
const statementContextSchema = z.object({
|
|
31
|
+
database: snowflakeIdentifier.optional(),
|
|
32
|
+
schema: snowflakeIdentifier.optional(),
|
|
33
|
+
warehouse: snowflakeIdentifier.optional(),
|
|
34
|
+
role: snowflakeIdentifier.optional(),
|
|
35
|
+
timeout: z.number().int().nonnegative().optional(),
|
|
36
|
+
parameters: z.record(z.string(), z.unknown()).optional()
|
|
37
|
+
}).strict();
|
|
38
|
+
/**
|
|
39
|
+
* SQL API v2 supports typed bindings: each binding is an object with a
|
|
40
|
+
* `type` and a `value`. We allow the simple positional shape and the
|
|
41
|
+
* keyed-map shape Snowflake accepts.
|
|
42
|
+
*/
|
|
43
|
+
const sqlBindingValueSchema = z.object({
|
|
44
|
+
type: z.string(),
|
|
45
|
+
value: z.union([
|
|
46
|
+
z.string(),
|
|
47
|
+
z.number(),
|
|
48
|
+
z.boolean(),
|
|
49
|
+
z.null()
|
|
50
|
+
])
|
|
51
|
+
});
|
|
52
|
+
const sqlBindingsSchema = z.record(z.string(), sqlBindingValueSchema).or(z.array(sqlBindingValueSchema));
|
|
53
|
+
/**
|
|
54
|
+
* Loosely-typed record shape for objects returned by `SHOW ...` commands.
|
|
55
|
+
* Snowflake returns positional arrays with a `rowType[]` describing each
|
|
56
|
+
* column — callers either consume the tuple form directly or `hydrateRow`
|
|
57
|
+
* into a `Record<string, unknown>`.
|
|
58
|
+
*/
|
|
59
|
+
const snowflakeObjectRowSchema = z.record(z.string(), z.unknown());
|
|
60
|
+
/**
|
|
61
|
+
* Column metadata entry as returned under `resultSetMetaData.rowType`.
|
|
62
|
+
*/
|
|
63
|
+
const columnMetaSchema = z.object({
|
|
64
|
+
name: z.string(),
|
|
65
|
+
type: z.string(),
|
|
66
|
+
nullable: z.boolean().optional(),
|
|
67
|
+
length: z.number().optional(),
|
|
68
|
+
precision: z.number().optional(),
|
|
69
|
+
scale: z.number().optional(),
|
|
70
|
+
byteLength: z.number().optional(),
|
|
71
|
+
collation: z.string().nullable().optional(),
|
|
72
|
+
database: z.string().optional(),
|
|
73
|
+
schema: z.string().optional(),
|
|
74
|
+
table: z.string().optional()
|
|
75
|
+
}).catchall(z.unknown());
|
|
76
|
+
/**
|
|
77
|
+
* Partition entry describing a shard of the total result set.
|
|
78
|
+
*/
|
|
79
|
+
const partitionInfoSchema = z.object({
|
|
80
|
+
rowCount: z.number(),
|
|
81
|
+
uncompressedSize: z.number().optional(),
|
|
82
|
+
compressedSize: z.number().optional()
|
|
83
|
+
}).catchall(z.unknown());
|
|
84
|
+
const resultSetMetaDataSchema = z.object({
|
|
85
|
+
numRows: z.number().optional(),
|
|
86
|
+
format: z.string().optional(),
|
|
87
|
+
partitionInfo: z.array(partitionInfoSchema).optional(),
|
|
88
|
+
rowType: z.array(columnMetaSchema).optional()
|
|
89
|
+
}).catchall(z.unknown());
|
|
90
|
+
|
|
91
|
+
//#endregion
|
|
92
|
+
export { snowflakeIdentifier as a, sqlBindingsSchema as c, snowflakeFqn as i, statementContextSchema as l, partitionInfoSchema as n, snowflakeObjectRowSchema as o, resultSetMetaDataSchema as r, sqlBindingValueSchema as s, columnMetaSchema as t };
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { a as snowflakeAuthModeSchema, c as snowflakeOfficialIntegration, i as snowflake, n as SnowflakeCredentials, o as snowflakeAuthSchema, r as parseSnowflakeCredentials, s as snowflakeBundle, t as SnowflakeAuthMode } from "./integration-CCWMSTlO.mjs";
|
|
2
|
+
export { type SnowflakeAuthMode, type SnowflakeCredentials, parseSnowflakeCredentials, snowflake, snowflakeAuthModeSchema, snowflakeAuthSchema, snowflakeBundle, snowflakeOfficialIntegration };
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { a as snowflakeBundle, i as snowflakeAuthSchema, n as snowflake, o as snowflakeOfficialIntegration, r as snowflakeAuthModeSchema, t as parseSnowflakeCredentials } from "./integration-Dptv8L0L.mjs";
|
|
2
|
+
|
|
3
|
+
export { parseSnowflakeCredentials, snowflake, snowflakeAuthModeSchema, snowflakeAuthSchema, snowflakeBundle, snowflakeOfficialIntegration };
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
//#region src/errors.ts
|
|
2
|
+
/**
|
|
3
|
+
* Structured Snowflake error. Always thrown by the client on transport or
|
|
4
|
+
* HTTP-level failure, and by higher-level helpers when they wrap those
|
|
5
|
+
* failures with extra context.
|
|
6
|
+
*/
|
|
7
|
+
var SnowflakeApiError = class extends Error {
|
|
8
|
+
kind;
|
|
9
|
+
status;
|
|
10
|
+
body;
|
|
11
|
+
providerCode;
|
|
12
|
+
requestId;
|
|
13
|
+
retryAfterMs;
|
|
14
|
+
retryable;
|
|
15
|
+
constructor(init) {
|
|
16
|
+
super(init.message, init.cause !== void 0 ? { cause: init.cause } : void 0);
|
|
17
|
+
this.name = "SnowflakeApiError";
|
|
18
|
+
this.kind = init.kind;
|
|
19
|
+
this.status = init.status;
|
|
20
|
+
this.body = init.body;
|
|
21
|
+
this.providerCode = init.providerCode;
|
|
22
|
+
this.requestId = init.requestId;
|
|
23
|
+
this.retryAfterMs = init.retryAfterMs;
|
|
24
|
+
this.retryable = init.retryable ?? defaultRetryability(init.kind);
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* SQL-level failure emitted by the SQL API on status 422. Callers should
|
|
29
|
+
* branch on `sqlState` / `code` when they need to recover from known
|
|
30
|
+
* SQL-layer errors (for example `42S02` table-not-found).
|
|
31
|
+
*/
|
|
32
|
+
var SnowflakeSqlError = class extends SnowflakeApiError {
|
|
33
|
+
sqlState;
|
|
34
|
+
code;
|
|
35
|
+
statementHandle;
|
|
36
|
+
constructor(init) {
|
|
37
|
+
super({
|
|
38
|
+
...init,
|
|
39
|
+
kind: "sql"
|
|
40
|
+
});
|
|
41
|
+
this.name = "SnowflakeSqlError";
|
|
42
|
+
this.sqlState = init.sqlState;
|
|
43
|
+
this.code = init.code;
|
|
44
|
+
this.statementHandle = init.statementHandle;
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
function defaultRetryability(kind) {
|
|
48
|
+
return kind === "rate_limit" || kind === "server_error" || kind === "timeout" || kind === "network";
|
|
49
|
+
}
|
|
50
|
+
function classifySnowflakeHttpStatus(status) {
|
|
51
|
+
if (status === 401) return "auth";
|
|
52
|
+
if (status === 403) return "auth";
|
|
53
|
+
if (status === 404) return "not_found";
|
|
54
|
+
if (status === 408) return "timeout";
|
|
55
|
+
if (status === 409) return "conflict";
|
|
56
|
+
if (status === 422) return "sql";
|
|
57
|
+
if (status === 429) return "rate_limit";
|
|
58
|
+
if (status >= 500 && status <= 599) return "server_error";
|
|
59
|
+
if (status >= 400 && status <= 499) return "validation";
|
|
60
|
+
return "unknown";
|
|
61
|
+
}
|
|
62
|
+
function isRecord(value) {
|
|
63
|
+
return typeof value === "object" && value !== null;
|
|
64
|
+
}
|
|
65
|
+
function extractSnowflakeMessage(body) {
|
|
66
|
+
if (!isRecord(body)) return void 0;
|
|
67
|
+
if (typeof body.message === "string") return body.message;
|
|
68
|
+
if (typeof body.error === "string") return body.error;
|
|
69
|
+
if (typeof body.errorMessage === "string") return body.errorMessage;
|
|
70
|
+
}
|
|
71
|
+
function extractSnowflakeCode(body) {
|
|
72
|
+
if (!isRecord(body)) return void 0;
|
|
73
|
+
if (typeof body.code === "string") return body.code;
|
|
74
|
+
if (typeof body.errorCode === "string") return body.errorCode;
|
|
75
|
+
}
|
|
76
|
+
function extractSnowflakeSqlState(body) {
|
|
77
|
+
if (!isRecord(body)) return void 0;
|
|
78
|
+
if (typeof body.sqlState === "string") return body.sqlState;
|
|
79
|
+
if (typeof body.sql_state === "string") return body.sql_state;
|
|
80
|
+
}
|
|
81
|
+
function extractSnowflakeStatementHandle(body) {
|
|
82
|
+
if (!isRecord(body)) return void 0;
|
|
83
|
+
if (typeof body.statementHandle === "string") return body.statementHandle;
|
|
84
|
+
if (typeof body.statement_handle === "string") return body.statement_handle;
|
|
85
|
+
}
|
|
86
|
+
function parseRetryAfterHeader(headerValue) {
|
|
87
|
+
if (!headerValue) return void 0;
|
|
88
|
+
const asNumber = Number(headerValue);
|
|
89
|
+
if (Number.isFinite(asNumber) && asNumber >= 0) return asNumber * 1e3;
|
|
90
|
+
const asDate = Date.parse(headerValue);
|
|
91
|
+
if (!Number.isNaN(asDate)) return Math.max(0, asDate - Date.now());
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
//#endregion
|
|
95
|
+
export { extractSnowflakeMessage as a, parseRetryAfterHeader as c, extractSnowflakeCode as i, SnowflakeSqlError as n, extractSnowflakeSqlState as o, classifySnowflakeHttpStatus as r, extractSnowflakeStatementHandle as s, SnowflakeApiError as t };
|