@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
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { executeSql } from "./sql.mjs";
|
|
2
|
+
import { a as quoteLiteral, t as buildFqn } from "./sql-safety-CywdR3EP.mjs";
|
|
3
|
+
import { BindingBuilder } from "./rows.mjs";
|
|
4
|
+
import { t as formatOptions } from "./sql-options-DI-OmLU6.mjs";
|
|
5
|
+
import { showProcedures } from "./catalog.mjs";
|
|
6
|
+
|
|
7
|
+
//#region src/procedures.ts
|
|
8
|
+
function renderArguments(args) {
|
|
9
|
+
if (!args || args.length === 0) return "()";
|
|
10
|
+
return `(${args.map((a) => {
|
|
11
|
+
const suffix = a.defaultExpression ? ` DEFAULT ${a.defaultExpression}` : "";
|
|
12
|
+
return `${a.name} ${a.type.toUpperCase()}${suffix}`;
|
|
13
|
+
}).join(", ")})`;
|
|
14
|
+
}
|
|
15
|
+
function renderArgumentTypes(args) {
|
|
16
|
+
if (!args || args.length === 0) return "()";
|
|
17
|
+
return `(${args.map((a) => a.type.toUpperCase()).join(", ")})`;
|
|
18
|
+
}
|
|
19
|
+
function renderBody(body, forceDollarQuoted, language) {
|
|
20
|
+
if (forceDollarQuoted ?? (language !== void 0 && language !== "SQL")) {
|
|
21
|
+
if (body.includes("$$")) throw new Error("Procedure body contains \"$$\"; cannot safely dollar-quote.");
|
|
22
|
+
return `$$${body}$$`;
|
|
23
|
+
}
|
|
24
|
+
return body;
|
|
25
|
+
}
|
|
26
|
+
async function createProcedure(client, options) {
|
|
27
|
+
const prefix = `CREATE${options.orReplace ? " OR REPLACE" : ""}${options.secure ? " SECURE" : ""} PROCEDURE${options.ifNotExists ? " IF NOT EXISTS" : ""}`;
|
|
28
|
+
const args = renderArguments(options.arguments);
|
|
29
|
+
const language = options.language ? ` LANGUAGE ${options.language}` : "";
|
|
30
|
+
const executeAs = options.executeAs ? ` EXECUTE AS ${options.executeAs}` : "";
|
|
31
|
+
const modifiers = options.modifiers && options.modifiers.length > 0 ? ` ${options.modifiers.map((m) => m.toUpperCase()).join(" ")}` : "";
|
|
32
|
+
const body = options.options && Object.keys(options.options).length > 0 ? ` ${formatOptions(options.options)}` : "";
|
|
33
|
+
const comment = options.comment ? ` COMMENT = ${quoteLiteral(options.comment)}` : "";
|
|
34
|
+
const rendered = renderBody(options.as, options.dollarQuotedBody, options.language);
|
|
35
|
+
await executeSql(client, {
|
|
36
|
+
statement: `${prefix} ${buildFqn(options.database, options.schema, options.name)}${args} RETURNS ${options.returns}${language}${executeAs}${modifiers}${body}${comment} AS ${rendered}`,
|
|
37
|
+
warehouse: options.warehouse,
|
|
38
|
+
role: options.role
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
async function dropProcedure(client, options) {
|
|
42
|
+
const fqn = buildFqn(options.database, options.schema, options.name);
|
|
43
|
+
await executeSql(client, {
|
|
44
|
+
statement: `DROP PROCEDURE${options.ifExists ? " IF EXISTS" : ""} ${fqn}${renderArgumentTypes(options.arguments)}`,
|
|
45
|
+
warehouse: options.warehouse,
|
|
46
|
+
role: options.role
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
async function describeProcedure(client, options) {
|
|
50
|
+
return (await executeSql(client, {
|
|
51
|
+
statement: `DESCRIBE PROCEDURE ${buildFqn(options.database, options.schema, options.name)}${renderArgumentTypes(options.arguments)}`,
|
|
52
|
+
warehouse: options.warehouse,
|
|
53
|
+
role: options.role
|
|
54
|
+
})).rows;
|
|
55
|
+
}
|
|
56
|
+
async function callProcedure(client, options) {
|
|
57
|
+
const fqn = buildFqn(options.database, options.schema, options.name);
|
|
58
|
+
if (options.rawArguments) return executeSql(client, {
|
|
59
|
+
statement: `CALL ${fqn}(${options.rawArguments.join(", ")})`,
|
|
60
|
+
warehouse: options.warehouse,
|
|
61
|
+
role: options.role,
|
|
62
|
+
bindings: options.bindings
|
|
63
|
+
});
|
|
64
|
+
const builder = new BindingBuilder();
|
|
65
|
+
const placeholders = (options.arguments ?? []).map((a) => builder.add(a));
|
|
66
|
+
const bindings = options.bindings ? {
|
|
67
|
+
...builder.toRecord(),
|
|
68
|
+
...options.bindings
|
|
69
|
+
} : builder.size() > 0 ? builder.toRecord() : void 0;
|
|
70
|
+
return executeSql(client, {
|
|
71
|
+
statement: `CALL ${fqn}(${placeholders.join(", ")})`,
|
|
72
|
+
warehouse: options.warehouse,
|
|
73
|
+
role: options.role,
|
|
74
|
+
bindings
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
//#endregion
|
|
79
|
+
export { callProcedure, createProcedure, describeProcedure, dropProcedure, showProcedures };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { d as HydratedRow, m as ColumnMeta } from "./statements-DJL0qVNA.mjs";
|
|
2
|
+
//#region src/results.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* Hydrate a single positional row into an object keyed by column name.
|
|
5
|
+
* Values are coerced per Snowflake type; unknown / unrepresentable values
|
|
6
|
+
* are passed through verbatim.
|
|
7
|
+
*/
|
|
8
|
+
declare function hydrateRow(row: readonly unknown[], columns: readonly ColumnMeta[]): HydratedRow;
|
|
9
|
+
declare function hydrateRows(rows: readonly (readonly unknown[])[], columns: readonly ColumnMeta[]): HydratedRow[];
|
|
10
|
+
/**
|
|
11
|
+
* Coerce a single raw value (already in whatever shape Snowflake sent it
|
|
12
|
+
* over the wire) into the nearest safe JS primitive / object.
|
|
13
|
+
*/
|
|
14
|
+
declare function hydrateValue(raw: unknown, column: ColumnMeta): unknown;
|
|
15
|
+
//#endregion
|
|
16
|
+
export { hydrateRow, hydrateRows, hydrateValue };
|
package/dist/results.mjs
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
//#region src/results.ts
|
|
2
|
+
const MAX_SAFE_INT_AS_STRING = String(Number.MAX_SAFE_INTEGER);
|
|
3
|
+
const MIN_SAFE_INT_AS_STRING = String(Number.MIN_SAFE_INTEGER);
|
|
4
|
+
/**
|
|
5
|
+
* Hydrate a single positional row into an object keyed by column name.
|
|
6
|
+
* Values are coerced per Snowflake type; unknown / unrepresentable values
|
|
7
|
+
* are passed through verbatim.
|
|
8
|
+
*/
|
|
9
|
+
function hydrateRow(row, columns) {
|
|
10
|
+
const hydrated = {};
|
|
11
|
+
for (let i = 0; i < columns.length; i += 1) {
|
|
12
|
+
const column = columns[i];
|
|
13
|
+
if (!column) continue;
|
|
14
|
+
hydrated[column.name] = hydrateValue(row[i], column);
|
|
15
|
+
}
|
|
16
|
+
return hydrated;
|
|
17
|
+
}
|
|
18
|
+
function hydrateRows(rows, columns) {
|
|
19
|
+
const hydrated = [];
|
|
20
|
+
for (const row of rows) hydrated.push(hydrateRow(row, columns));
|
|
21
|
+
return hydrated;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Coerce a single raw value (already in whatever shape Snowflake sent it
|
|
25
|
+
* over the wire) into the nearest safe JS primitive / object.
|
|
26
|
+
*/
|
|
27
|
+
function hydrateValue(raw, column) {
|
|
28
|
+
if (raw === null || raw === void 0) return null;
|
|
29
|
+
const type = column.type.toUpperCase();
|
|
30
|
+
if (typeof raw === "string") return coerceString(raw, type, column);
|
|
31
|
+
return raw;
|
|
32
|
+
}
|
|
33
|
+
function coerceString(raw, type, column) {
|
|
34
|
+
if (type === "BOOLEAN") return raw === "true" || raw === "1";
|
|
35
|
+
if (type === "FIXED") return coerceFixed(raw, column);
|
|
36
|
+
if (type === "REAL") {
|
|
37
|
+
const parsed = Number(raw);
|
|
38
|
+
return Number.isFinite(parsed) ? parsed : raw;
|
|
39
|
+
}
|
|
40
|
+
if (type === "BINARY") return raw;
|
|
41
|
+
if (type === "DATE" || type === "TIME" || type.startsWith("TIMESTAMP")) return raw;
|
|
42
|
+
if (type === "OBJECT" || type === "ARRAY" || type === "VARIANT") try {
|
|
43
|
+
return JSON.parse(raw);
|
|
44
|
+
} catch {
|
|
45
|
+
return raw;
|
|
46
|
+
}
|
|
47
|
+
return raw;
|
|
48
|
+
}
|
|
49
|
+
function coerceFixed(raw, column) {
|
|
50
|
+
if ((column.scale ?? 0) > 0) {
|
|
51
|
+
const parsed = Number(raw);
|
|
52
|
+
return Number.isFinite(parsed) ? parsed : raw;
|
|
53
|
+
}
|
|
54
|
+
if (isSafeInteger(raw)) return Number(raw);
|
|
55
|
+
return raw;
|
|
56
|
+
}
|
|
57
|
+
function isSafeInteger(value) {
|
|
58
|
+
if (!/^-?\d+$/u.test(value)) return false;
|
|
59
|
+
if (value.startsWith("-")) return value.length < MIN_SAFE_INT_AS_STRING.length || value.length === MIN_SAFE_INT_AS_STRING.length && value <= MIN_SAFE_INT_AS_STRING;
|
|
60
|
+
return value.length < MAX_SAFE_INT_AS_STRING.length || value.length === MAX_SAFE_INT_AS_STRING.length && value <= MAX_SAFE_INT_AS_STRING;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
//#endregion
|
|
64
|
+
export { hydrateRow, hydrateRows, hydrateValue };
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
//#region src/jwt.ts
|
|
2
|
+
/**
|
|
3
|
+
* Snowflake JWT signing + public-key fingerprint (RS256, key-pair auth).
|
|
4
|
+
*
|
|
5
|
+
* Isolate-safe — built on `globalThis.crypto.subtle`, no Node `crypto`
|
|
6
|
+
* imports, no native SDKs. Works in Cloudflare Workers, Node 19+,
|
|
7
|
+
* and any modern V8/JSC isolate.
|
|
8
|
+
*
|
|
9
|
+
* Reference: https://docs.snowflake.com/en/user-guide/key-pair-auth
|
|
10
|
+
*/
|
|
11
|
+
const DEFAULT_JWT_LIFETIME_SECONDS = 3300;
|
|
12
|
+
async function createSnowflakeJwt(input) {
|
|
13
|
+
const now = Math.floor((input.now?.() ?? Date.now()) / 1e3);
|
|
14
|
+
const exp = now + (input.lifetimeSeconds ?? DEFAULT_JWT_LIFETIME_SECONDS);
|
|
15
|
+
const account = normalizeAccountForJwt(input.account).toUpperCase();
|
|
16
|
+
const user = input.user.toUpperCase();
|
|
17
|
+
const privateKey = await importRsaPrivateKey(input.privateKeyPem);
|
|
18
|
+
const fingerprint = input.publicKeyFingerprint ?? await derivePublicKeyFingerprint(privateKey);
|
|
19
|
+
const issuer = `${account}.${user}.${fingerprint}`;
|
|
20
|
+
const subject = `${account}.${user}`;
|
|
21
|
+
const header = {
|
|
22
|
+
alg: "RS256",
|
|
23
|
+
typ: "JWT"
|
|
24
|
+
};
|
|
25
|
+
const payload = {
|
|
26
|
+
iss: issuer,
|
|
27
|
+
sub: subject,
|
|
28
|
+
iat: now,
|
|
29
|
+
exp
|
|
30
|
+
};
|
|
31
|
+
const signingInput = `${base64UrlEncode(JSON.stringify(header))}.${base64UrlEncode(JSON.stringify(payload))}`;
|
|
32
|
+
const signature = await crypto.subtle.sign({ name: "RSASSA-PKCS1-v1_5" }, privateKey, new TextEncoder().encode(signingInput));
|
|
33
|
+
return {
|
|
34
|
+
token: `${signingInput}.${base64UrlEncode(new Uint8Array(signature))}`,
|
|
35
|
+
expiresAt: exp,
|
|
36
|
+
fingerprint,
|
|
37
|
+
issuer,
|
|
38
|
+
subject
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Compute `SHA256:<base64>` fingerprint over the DER-encoded SPKI public key
|
|
43
|
+
* derived from the PKCS#8 private key. Matches Snowflake's
|
|
44
|
+
* `RSA_PUBLIC_KEY_FP` value shown by `DESC USER`.
|
|
45
|
+
*/
|
|
46
|
+
async function derivePublicKeyFingerprint(privateKey) {
|
|
47
|
+
const publicKey = await extractPublicKey(privateKey);
|
|
48
|
+
const spkiDer = await crypto.subtle.exportKey("spki", publicKey);
|
|
49
|
+
const digest = await crypto.subtle.digest("SHA-256", spkiDer);
|
|
50
|
+
return `SHA256:${base64Encode(new Uint8Array(digest))}`;
|
|
51
|
+
}
|
|
52
|
+
async function extractPublicKey(privateKey) {
|
|
53
|
+
const jwk = await crypto.subtle.exportKey("jwk", privateKey);
|
|
54
|
+
const publicJwk = {
|
|
55
|
+
kty: jwk.kty,
|
|
56
|
+
n: jwk.n,
|
|
57
|
+
e: jwk.e,
|
|
58
|
+
alg: jwk.alg,
|
|
59
|
+
ext: true
|
|
60
|
+
};
|
|
61
|
+
return crypto.subtle.importKey("jwk", publicJwk, {
|
|
62
|
+
name: "RSASSA-PKCS1-v1_5",
|
|
63
|
+
hash: "SHA-256"
|
|
64
|
+
}, true, ["verify"]);
|
|
65
|
+
}
|
|
66
|
+
async function importRsaPrivateKey(pem) {
|
|
67
|
+
const armor = peekPemArmor(pem);
|
|
68
|
+
if (!armor) throw new TypeError("SNOWFLAKE_PRIVATE_KEY is not a PEM-encoded key.");
|
|
69
|
+
if (armor.type !== "PRIVATE KEY") throw new TypeError(`Unsupported PEM type "${armor.type}". Expected PKCS#8 PEM ("-----BEGIN PRIVATE KEY-----"). Convert PKCS#1 keys with \`openssl pkcs8 -topk8 -nocrypt\`.`);
|
|
70
|
+
return crypto.subtle.importKey("pkcs8", toArrayBuffer(base64Decode(armor.body)), {
|
|
71
|
+
name: "RSASSA-PKCS1-v1_5",
|
|
72
|
+
hash: "SHA-256"
|
|
73
|
+
}, true, ["sign"]);
|
|
74
|
+
}
|
|
75
|
+
function toArrayBuffer(view) {
|
|
76
|
+
const copy = new Uint8Array(view.byteLength);
|
|
77
|
+
copy.set(view);
|
|
78
|
+
return copy.buffer;
|
|
79
|
+
}
|
|
80
|
+
function peekPemArmor(pem) {
|
|
81
|
+
const match = pem.match(/-----BEGIN ([A-Z ]+)-----([\s\S]*?)-----END \1-----/u);
|
|
82
|
+
if (!match) return void 0;
|
|
83
|
+
const type = match[1]?.trim();
|
|
84
|
+
const body = match[2]?.replace(/\s+/gu, "");
|
|
85
|
+
if (!type || !body) return void 0;
|
|
86
|
+
return {
|
|
87
|
+
type,
|
|
88
|
+
body
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Account identifiers accepted by Snowflake take one of these shapes:
|
|
93
|
+
* - `orgname-accountname` (preferred)
|
|
94
|
+
* - `account_locator.region`
|
|
95
|
+
* - `account_locator.region.aws`
|
|
96
|
+
* - `account_locator` (legacy single-region)
|
|
97
|
+
*
|
|
98
|
+
* The JWT `iss`/`sub` require just the account segment (everything before the
|
|
99
|
+
* first dot for locator-form identifiers) — the org-account hyphen form is
|
|
100
|
+
* passed through as-is.
|
|
101
|
+
*/
|
|
102
|
+
function normalizeAccountForJwt(account) {
|
|
103
|
+
const trimmed = account.trim();
|
|
104
|
+
const firstDot = trimmed.indexOf(".");
|
|
105
|
+
return firstDot === -1 ? trimmed : trimmed.slice(0, firstDot);
|
|
106
|
+
}
|
|
107
|
+
function base64UrlEncode(input) {
|
|
108
|
+
return base64Encode(typeof input === "string" ? new TextEncoder().encode(input) : input).replaceAll("+", "-").replaceAll("/", "_").replace(/=+$/u, "");
|
|
109
|
+
}
|
|
110
|
+
function base64Encode(bytes) {
|
|
111
|
+
let binary = "";
|
|
112
|
+
for (let i = 0; i < bytes.byteLength; i += 1) binary += String.fromCharCode(bytes[i] ?? 0);
|
|
113
|
+
if (typeof btoa === "function") return btoa(binary);
|
|
114
|
+
/* c8 ignore next */
|
|
115
|
+
return Buffer.from(bytes).toString("base64");
|
|
116
|
+
}
|
|
117
|
+
function base64Decode(input) {
|
|
118
|
+
if (typeof atob === "function") {
|
|
119
|
+
const binary = atob(input);
|
|
120
|
+
const bytes = new Uint8Array(binary.length);
|
|
121
|
+
for (let i = 0; i < binary.length; i += 1) bytes[i] = binary.charCodeAt(i);
|
|
122
|
+
return bytes;
|
|
123
|
+
}
|
|
124
|
+
/* c8 ignore next */
|
|
125
|
+
return new Uint8Array(Buffer.from(input, "base64"));
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
//#endregion
|
|
129
|
+
//#region src/retry.ts
|
|
130
|
+
const DEFAULT_RETRY_POLICY = {
|
|
131
|
+
maxRetries: 4,
|
|
132
|
+
baseDelayMs: 500,
|
|
133
|
+
maxDelayMs: 2e4,
|
|
134
|
+
jitterMs: 250
|
|
135
|
+
};
|
|
136
|
+
const RETRYABLE_KINDS = new Set([
|
|
137
|
+
"rate_limit",
|
|
138
|
+
"server_error",
|
|
139
|
+
"timeout",
|
|
140
|
+
"network"
|
|
141
|
+
]);
|
|
142
|
+
function shouldRetry(kind, attempt, policy = DEFAULT_RETRY_POLICY) {
|
|
143
|
+
if (attempt >= policy.maxRetries) return false;
|
|
144
|
+
return RETRYABLE_KINDS.has(kind);
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Compute the delay (ms) before the next retry. If the server provided a
|
|
148
|
+
* `Retry-After` header we respect it (clamped to `maxDelayMs`). Otherwise we
|
|
149
|
+
* use `baseDelayMs * 2^attempt` with additive jitter, capped at `maxDelayMs`.
|
|
150
|
+
*/
|
|
151
|
+
function computeBackoffMs({ attempt, retryAfterMs, policy = DEFAULT_RETRY_POLICY, random = Math.random }) {
|
|
152
|
+
if (retryAfterMs !== void 0 && retryAfterMs >= 0) return Math.min(retryAfterMs, policy.maxDelayMs);
|
|
153
|
+
const exponent = Math.max(0, attempt - 1);
|
|
154
|
+
const base = policy.baseDelayMs * 2 ** exponent;
|
|
155
|
+
const jitter = policy.jitterMs > 0 ? random() * policy.jitterMs : 0;
|
|
156
|
+
return Math.min(policy.maxDelayMs, Math.floor(base + jitter));
|
|
157
|
+
}
|
|
158
|
+
async function defaultSleep(ms) {
|
|
159
|
+
if (ms <= 0) return;
|
|
160
|
+
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
//#endregion
|
|
164
|
+
export { createSnowflakeJwt as a, shouldRetry as i, computeBackoffMs as n, defaultSleep as r, DEFAULT_RETRY_POLICY as t };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
//#region src/retry.d.ts
|
|
2
|
+
interface RetryPolicy {
|
|
3
|
+
readonly maxRetries: number;
|
|
4
|
+
readonly baseDelayMs: number;
|
|
5
|
+
readonly maxDelayMs: number;
|
|
6
|
+
/** Adds up to `jitterMs` random ms on top of each backoff. */
|
|
7
|
+
readonly jitterMs: number;
|
|
8
|
+
}
|
|
9
|
+
//#endregion
|
|
10
|
+
export { RetryPolicy as t };
|
package/dist/roles.d.mts
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { SnowflakeClient } from "./client.mjs";
|
|
2
|
+
import { t as OptionMap } from "./sql-options-2k5xQ-oS.mjs";
|
|
3
|
+
import { CatalogResult, ShowOptions, showRoles } from "./catalog.mjs";
|
|
4
|
+
|
|
5
|
+
//#region src/roles.d.ts
|
|
6
|
+
interface RoleContext {
|
|
7
|
+
readonly warehouse?: string;
|
|
8
|
+
readonly sessionRole?: string;
|
|
9
|
+
}
|
|
10
|
+
interface CreateRoleOptions extends RoleContext {
|
|
11
|
+
readonly name: string;
|
|
12
|
+
readonly orReplace?: boolean;
|
|
13
|
+
readonly ifNotExists?: boolean;
|
|
14
|
+
readonly comment?: string;
|
|
15
|
+
readonly options?: OptionMap;
|
|
16
|
+
}
|
|
17
|
+
declare function createRole(client: SnowflakeClient, options: CreateRoleOptions): Promise<void>;
|
|
18
|
+
interface AlterRoleOptions extends RoleContext {
|
|
19
|
+
readonly name: string;
|
|
20
|
+
readonly rename?: string;
|
|
21
|
+
readonly set?: OptionMap;
|
|
22
|
+
readonly unset?: readonly string[];
|
|
23
|
+
readonly comment?: string;
|
|
24
|
+
readonly ifExists?: boolean;
|
|
25
|
+
}
|
|
26
|
+
declare function alterRole(client: SnowflakeClient, options: AlterRoleOptions): Promise<void>;
|
|
27
|
+
interface DropRoleOptions extends RoleContext {
|
|
28
|
+
readonly name: string;
|
|
29
|
+
readonly ifExists?: boolean;
|
|
30
|
+
}
|
|
31
|
+
declare function dropRole(client: SnowflakeClient, options: DropRoleOptions): Promise<void>;
|
|
32
|
+
interface UseRoleOptions extends RoleContext {
|
|
33
|
+
readonly name: string;
|
|
34
|
+
}
|
|
35
|
+
declare function useRole(client: SnowflakeClient, options: UseRoleOptions): Promise<void>;
|
|
36
|
+
interface GrantRoleToUserOptions extends RoleContext {
|
|
37
|
+
/** Role being granted. */
|
|
38
|
+
readonly role: string;
|
|
39
|
+
readonly user: string;
|
|
40
|
+
}
|
|
41
|
+
declare function grantRoleToUser(client: SnowflakeClient, options: GrantRoleToUserOptions): Promise<void>;
|
|
42
|
+
interface RevokeRoleFromUserOptions extends RoleContext {
|
|
43
|
+
readonly role: string;
|
|
44
|
+
readonly user: string;
|
|
45
|
+
}
|
|
46
|
+
declare function revokeRoleFromUser(client: SnowflakeClient, options: RevokeRoleFromUserOptions): Promise<void>;
|
|
47
|
+
interface GrantRoleToRoleOptions extends RoleContext {
|
|
48
|
+
/** The child role being granted. */
|
|
49
|
+
readonly role: string;
|
|
50
|
+
/** The parent role that will inherit `role`. */
|
|
51
|
+
readonly parentRole: string;
|
|
52
|
+
}
|
|
53
|
+
declare function grantRoleToRole(client: SnowflakeClient, options: GrantRoleToRoleOptions): Promise<void>;
|
|
54
|
+
interface RevokeRoleFromRoleOptions extends RoleContext {
|
|
55
|
+
readonly role: string;
|
|
56
|
+
readonly parentRole: string;
|
|
57
|
+
}
|
|
58
|
+
declare function revokeRoleFromRole(client: SnowflakeClient, options: RevokeRoleFromRoleOptions): Promise<void>;
|
|
59
|
+
//#endregion
|
|
60
|
+
export { AlterRoleOptions, type CatalogResult, CreateRoleOptions, DropRoleOptions, GrantRoleToRoleOptions, GrantRoleToUserOptions, RevokeRoleFromRoleOptions, RevokeRoleFromUserOptions, RoleContext, type ShowOptions, UseRoleOptions, alterRole, createRole, dropRole, grantRoleToRole, grantRoleToUser, revokeRoleFromRole, revokeRoleFromUser, showRoles, useRole };
|
package/dist/roles.mjs
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { executeSql } from "./sql.mjs";
|
|
2
|
+
import { a as quoteLiteral, i as quoteIdentifier } from "./sql-safety-CywdR3EP.mjs";
|
|
3
|
+
import { t as formatOptions } from "./sql-options-DI-OmLU6.mjs";
|
|
4
|
+
import { showRoles } from "./catalog.mjs";
|
|
5
|
+
|
|
6
|
+
//#region src/roles.ts
|
|
7
|
+
async function createRole(client, options) {
|
|
8
|
+
const prefix = `CREATE${options.orReplace ? " OR REPLACE" : ""} ROLE${options.ifNotExists ? " IF NOT EXISTS" : ""}`;
|
|
9
|
+
const body = options.options && Object.keys(options.options).length > 0 ? ` ${formatOptions(options.options)}` : "";
|
|
10
|
+
const comment = options.comment ? ` COMMENT = ${quoteLiteral(options.comment)}` : "";
|
|
11
|
+
await executeSql(client, {
|
|
12
|
+
statement: `${prefix} ${quoteIdentifier(options.name)}${body}${comment}`,
|
|
13
|
+
warehouse: options.warehouse,
|
|
14
|
+
role: options.sessionRole
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
async function alterRole(client, options) {
|
|
18
|
+
let tail;
|
|
19
|
+
if (options.rename) tail = `RENAME TO ${quoteIdentifier(options.rename)}`;
|
|
20
|
+
else if (options.set && Object.keys(options.set).length > 0) tail = `SET ${formatOptions(options.set)}`;
|
|
21
|
+
else if (options.unset && options.unset.length > 0) tail = `UNSET ${options.unset.map((k) => k.toUpperCase()).join(", ")}`;
|
|
22
|
+
else if (options.comment !== void 0) tail = `SET COMMENT = ${quoteLiteral(options.comment)}`;
|
|
23
|
+
else throw new Error("alterRole requires one of: rename / set / unset / comment");
|
|
24
|
+
await executeSql(client, {
|
|
25
|
+
statement: `ALTER ROLE${options.ifExists ? " IF EXISTS" : ""} ${quoteIdentifier(options.name)} ${tail}`,
|
|
26
|
+
warehouse: options.warehouse,
|
|
27
|
+
role: options.sessionRole
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
async function dropRole(client, options) {
|
|
31
|
+
await executeSql(client, {
|
|
32
|
+
statement: `DROP ROLE${options.ifExists ? " IF EXISTS" : ""} ${quoteIdentifier(options.name)}`,
|
|
33
|
+
warehouse: options.warehouse,
|
|
34
|
+
role: options.sessionRole
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
async function useRole(client, options) {
|
|
38
|
+
await executeSql(client, {
|
|
39
|
+
statement: `USE ROLE ${quoteIdentifier(options.name)}`,
|
|
40
|
+
warehouse: options.warehouse,
|
|
41
|
+
role: options.sessionRole
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
async function grantRoleToUser(client, options) {
|
|
45
|
+
await executeSql(client, {
|
|
46
|
+
statement: `GRANT ROLE ${quoteIdentifier(options.role)} TO USER ${quoteIdentifier(options.user)}`,
|
|
47
|
+
warehouse: options.warehouse,
|
|
48
|
+
role: options.sessionRole
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
async function revokeRoleFromUser(client, options) {
|
|
52
|
+
await executeSql(client, {
|
|
53
|
+
statement: `REVOKE ROLE ${quoteIdentifier(options.role)} FROM USER ${quoteIdentifier(options.user)}`,
|
|
54
|
+
warehouse: options.warehouse,
|
|
55
|
+
role: options.sessionRole
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
async function grantRoleToRole(client, options) {
|
|
59
|
+
await executeSql(client, {
|
|
60
|
+
statement: `GRANT ROLE ${quoteIdentifier(options.role)} TO ROLE ${quoteIdentifier(options.parentRole)}`,
|
|
61
|
+
warehouse: options.warehouse,
|
|
62
|
+
role: options.sessionRole
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
async function revokeRoleFromRole(client, options) {
|
|
66
|
+
await executeSql(client, {
|
|
67
|
+
statement: `REVOKE ROLE ${quoteIdentifier(options.role)} FROM ROLE ${quoteIdentifier(options.parentRole)}`,
|
|
68
|
+
warehouse: options.warehouse,
|
|
69
|
+
role: options.sessionRole
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
//#endregion
|
|
74
|
+
export { alterRole, createRole, dropRole, grantRoleToRole, grantRoleToUser, revokeRoleFromRole, revokeRoleFromUser, showRoles, useRole };
|
package/dist/rows.d.mts
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { SnowflakeClient } from "./client.mjs";
|
|
2
|
+
import { d as HydratedRow, v as SqlBinding } from "./statements-DJL0qVNA.mjs";
|
|
3
|
+
//#region src/rows.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* A value a caller can pass into an `insert` / `update` / `where` map. The
|
|
6
|
+
* binding helper below maps each JS primitive onto a Snowflake type (`TEXT`,
|
|
7
|
+
* `FIXED`, `REAL`, `BOOLEAN`, `TIMESTAMP_LTZ`) so the SQL API receives a
|
|
8
|
+
* faithful typed value. Callers needing a specific Snowflake type can pass a
|
|
9
|
+
* pre-built `SqlBinding` directly.
|
|
10
|
+
*/
|
|
11
|
+
type RowValue = string | number | bigint | boolean | Date | null | undefined | SqlBinding;
|
|
12
|
+
type RowRecord = Readonly<Record<string, RowValue>>;
|
|
13
|
+
interface RowTableRef {
|
|
14
|
+
readonly database?: string;
|
|
15
|
+
readonly schema?: string;
|
|
16
|
+
readonly table: string;
|
|
17
|
+
}
|
|
18
|
+
interface RowContext {
|
|
19
|
+
readonly warehouse?: string;
|
|
20
|
+
readonly role?: string;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Map a JS value to its Snowflake typed binding. Callers who need full
|
|
24
|
+
* control can pre-build a `{type, value}` object and pass it through.
|
|
25
|
+
*/
|
|
26
|
+
declare function inferBinding(value: RowValue): SqlBinding;
|
|
27
|
+
/**
|
|
28
|
+
* Mutable binding builder. Internally indexes bindings starting at 1 and
|
|
29
|
+
* returns the `?` placeholder for each append so callers can weave them into
|
|
30
|
+
* their SQL composition.
|
|
31
|
+
*/
|
|
32
|
+
declare class BindingBuilder {
|
|
33
|
+
private readonly map;
|
|
34
|
+
private counter;
|
|
35
|
+
add(value: RowValue): string;
|
|
36
|
+
toRecord(): Readonly<Record<string, SqlBinding>>;
|
|
37
|
+
size(): number;
|
|
38
|
+
}
|
|
39
|
+
type OrderDirection = 'ASC' | 'DESC';
|
|
40
|
+
interface FindRowsOptions extends RowTableRef, RowContext {
|
|
41
|
+
readonly where?: RowRecord;
|
|
42
|
+
readonly columns?: readonly string[];
|
|
43
|
+
readonly orderBy?: readonly {
|
|
44
|
+
readonly column: string;
|
|
45
|
+
readonly direction?: OrderDirection;
|
|
46
|
+
}[];
|
|
47
|
+
readonly limit?: number;
|
|
48
|
+
readonly offset?: number;
|
|
49
|
+
}
|
|
50
|
+
interface FindRowOptions extends RowTableRef, RowContext {
|
|
51
|
+
readonly where: RowRecord;
|
|
52
|
+
readonly columns?: readonly string[];
|
|
53
|
+
}
|
|
54
|
+
declare function findRows(client: SnowflakeClient, options: FindRowsOptions): Promise<readonly HydratedRow[]>;
|
|
55
|
+
declare function findRow(client: SnowflakeClient, options: FindRowOptions): Promise<HydratedRow | undefined>;
|
|
56
|
+
interface InsertRowOptions extends RowTableRef, RowContext {
|
|
57
|
+
readonly row: RowRecord;
|
|
58
|
+
}
|
|
59
|
+
declare function insertRow(client: SnowflakeClient, options: InsertRowOptions): Promise<number>;
|
|
60
|
+
interface BatchInsertOptions extends RowTableRef, RowContext {
|
|
61
|
+
readonly rows: readonly RowRecord[];
|
|
62
|
+
/**
|
|
63
|
+
* When provided, use this explicit column list and look up each row's
|
|
64
|
+
* value by column; when missing, use the union of keys from the first
|
|
65
|
+
* row (all subsequent rows must share the same shape).
|
|
66
|
+
*/
|
|
67
|
+
readonly columns?: readonly string[];
|
|
68
|
+
}
|
|
69
|
+
declare function batchInsert(client: SnowflakeClient, options: BatchInsertOptions): Promise<number>;
|
|
70
|
+
interface UpdateRowOptions extends RowTableRef, RowContext {
|
|
71
|
+
readonly where: RowRecord;
|
|
72
|
+
readonly set: RowRecord;
|
|
73
|
+
}
|
|
74
|
+
declare function updateRow(client: SnowflakeClient, options: UpdateRowOptions): Promise<number>;
|
|
75
|
+
interface DeleteRowOptions extends RowTableRef, RowContext {
|
|
76
|
+
readonly where: RowRecord;
|
|
77
|
+
}
|
|
78
|
+
declare function deleteRow(client: SnowflakeClient, options: DeleteRowOptions): Promise<number>;
|
|
79
|
+
interface UpsertRowOptions extends RowTableRef, RowContext {
|
|
80
|
+
readonly row: RowRecord;
|
|
81
|
+
/** Columns whose equality defines the MATCH condition. */
|
|
82
|
+
readonly matchColumns: readonly string[];
|
|
83
|
+
/**
|
|
84
|
+
* Columns to write on UPDATE (defaults to every column in `row` except the
|
|
85
|
+
* match columns, which are left untouched on match).
|
|
86
|
+
*/
|
|
87
|
+
readonly updateColumns?: readonly string[];
|
|
88
|
+
}
|
|
89
|
+
interface UpsertRowResult {
|
|
90
|
+
readonly inserted: number;
|
|
91
|
+
readonly updated: number;
|
|
92
|
+
}
|
|
93
|
+
declare function upsertRow(client: SnowflakeClient, options: UpsertRowOptions): Promise<UpsertRowResult>;
|
|
94
|
+
interface MergeRowsOptions extends RowTableRef, RowContext {
|
|
95
|
+
/**
|
|
96
|
+
* Raw MERGE statement source. Useful when callers want full control over
|
|
97
|
+
* a merge and just want the client/context/binding infra. `statement`
|
|
98
|
+
* should already reference the resolved FQN from
|
|
99
|
+
* {@link buildFqn}/{@link quoteIdentifier}.
|
|
100
|
+
*/
|
|
101
|
+
readonly statement: string;
|
|
102
|
+
readonly bindings?: Readonly<Record<string, SqlBinding>>;
|
|
103
|
+
}
|
|
104
|
+
declare function mergeRows(client: SnowflakeClient, options: MergeRowsOptions): Promise<UpsertRowResult>;
|
|
105
|
+
//#endregion
|
|
106
|
+
export { BatchInsertOptions, BindingBuilder, DeleteRowOptions, FindRowOptions, FindRowsOptions, InsertRowOptions, MergeRowsOptions, OrderDirection, RowContext, RowRecord, RowTableRef, RowValue, UpdateRowOptions, UpsertRowOptions, UpsertRowResult, batchInsert, deleteRow, findRow, findRows, inferBinding, insertRow, mergeRows, updateRow, upsertRow };
|