@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.
Files changed (81) hide show
  1. package/README.md +247 -0
  2. package/dist/_official/index.d.mts +2 -0
  3. package/dist/_official/index.mjs +3 -0
  4. package/dist/_runtime/index.d.mts +1 -0
  5. package/dist/_runtime/index.mjs +1 -0
  6. package/dist/accounts.d.mts +40 -0
  7. package/dist/accounts.mjs +51 -0
  8. package/dist/bulk.d.mts +71 -0
  9. package/dist/bulk.mjs +65 -0
  10. package/dist/catalog.d.mts +144 -0
  11. package/dist/catalog.mjs +158 -0
  12. package/dist/client.d.mts +44 -0
  13. package/dist/client.mjs +273 -0
  14. package/dist/common-BnKTPQXc.mjs +92 -0
  15. package/dist/connection.d.mts +2 -0
  16. package/dist/connection.mjs +3 -0
  17. package/dist/databases.d.mts +2 -0
  18. package/dist/databases.mjs +3 -0
  19. package/dist/errors-DKnc9o_a.mjs +95 -0
  20. package/dist/events.d.mts +382 -0
  21. package/dist/events.mjs +291 -0
  22. package/dist/file-formats.d.mts +39 -0
  23. package/dist/file-formats.mjs +48 -0
  24. package/dist/functions.d.mts +53 -0
  25. package/dist/functions.mjs +56 -0
  26. package/dist/grants.d.mts +93 -0
  27. package/dist/grants.mjs +102 -0
  28. package/dist/index.d.mts +1 -0
  29. package/dist/index.mjs +1 -0
  30. package/dist/integration-CCWMSTlO.d.mts +170 -0
  31. package/dist/integration-Dptv8L0L.mjs +207 -0
  32. package/dist/org-admin.d.mts +80 -0
  33. package/dist/org-admin.mjs +79 -0
  34. package/dist/pipes.d.mts +119 -0
  35. package/dist/pipes.mjs +106 -0
  36. package/dist/procedures.d.mts +61 -0
  37. package/dist/procedures.mjs +79 -0
  38. package/dist/results.d.mts +16 -0
  39. package/dist/results.mjs +64 -0
  40. package/dist/retry-0hMfb8cC.mjs +164 -0
  41. package/dist/retry-w7cTp1QL.d.mts +10 -0
  42. package/dist/roles.d.mts +60 -0
  43. package/dist/roles.mjs +74 -0
  44. package/dist/rows.d.mts +106 -0
  45. package/dist/rows.mjs +223 -0
  46. package/dist/schemas/index.d.mts +2 -0
  47. package/dist/schemas/index.mjs +4 -0
  48. package/dist/schemas-catalog.d.mts +2 -0
  49. package/dist/schemas-catalog.mjs +3 -0
  50. package/dist/shares.d.mts +56 -0
  51. package/dist/shares.mjs +77 -0
  52. package/dist/sql-options-2k5xQ-oS.d.mts +32 -0
  53. package/dist/sql-options-DI-OmLU6.mjs +79 -0
  54. package/dist/sql-safety-CywdR3EP.mjs +56 -0
  55. package/dist/sql.d.mts +84 -0
  56. package/dist/sql.mjs +209 -0
  57. package/dist/stages.d.mts +64 -0
  58. package/dist/stages.mjs +81 -0
  59. package/dist/statements-B2ThF_4b.mjs +81 -0
  60. package/dist/statements-DJL0qVNA.d.mts +238 -0
  61. package/dist/status-page.d.mts +510 -0
  62. package/dist/status-page.mjs +261 -0
  63. package/dist/streaming.d.mts +70 -0
  64. package/dist/streaming.mjs +56 -0
  65. package/dist/streams.d.mts +71 -0
  66. package/dist/streams.mjs +78 -0
  67. package/dist/tables.d.mts +2 -0
  68. package/dist/tables.mjs +3 -0
  69. package/dist/tasks.d.mts +79 -0
  70. package/dist/tasks.mjs +104 -0
  71. package/dist/triggers.d.mts +578 -0
  72. package/dist/triggers.mjs +1363 -0
  73. package/dist/users.d.mts +61 -0
  74. package/dist/users.mjs +67 -0
  75. package/dist/verification.d.mts +201 -0
  76. package/dist/verification.mjs +512 -0
  77. package/dist/views.d.mts +2 -0
  78. package/dist/views.mjs +3 -0
  79. package/dist/warehouses.d.mts +68 -0
  80. package/dist/warehouses.mjs +101 -0
  81. package/package.json +190 -0
package/dist/rows.mjs ADDED
@@ -0,0 +1,223 @@
1
+ import { executeSql } from "./sql.mjs";
2
+ import { i as quoteIdentifier, t as buildFqn } from "./sql-safety-CywdR3EP.mjs";
3
+
4
+ //#region src/rows.ts
5
+ function isSqlBinding(value) {
6
+ return typeof value === "object" && value !== null && "type" in value && "value" in value && typeof value.type === "string";
7
+ }
8
+ /**
9
+ * Map a JS value to its Snowflake typed binding. Callers who need full
10
+ * control can pre-build a `{type, value}` object and pass it through.
11
+ */
12
+ function inferBinding(value) {
13
+ if (isSqlBinding(value)) return value;
14
+ if (value === null || value === void 0) return {
15
+ type: "TEXT",
16
+ value: null
17
+ };
18
+ if (typeof value === "string") return {
19
+ type: "TEXT",
20
+ value
21
+ };
22
+ if (typeof value === "boolean") return {
23
+ type: "BOOLEAN",
24
+ value: value ? "true" : "false"
25
+ };
26
+ if (typeof value === "bigint") return {
27
+ type: "FIXED",
28
+ value: value.toString()
29
+ };
30
+ if (typeof value === "number") {
31
+ if (Number.isNaN(value) || !Number.isFinite(value)) throw new TypeError("Cannot bind NaN or Infinity as a Snowflake value");
32
+ return Number.isInteger(value) ? {
33
+ type: "FIXED",
34
+ value: value.toString()
35
+ } : {
36
+ type: "REAL",
37
+ value: value.toString()
38
+ };
39
+ }
40
+ if (value instanceof Date) return {
41
+ type: "TIMESTAMP_LTZ",
42
+ value: value.toISOString()
43
+ };
44
+ const unreachable = value;
45
+ throw new TypeError(`Unsupported binding type: ${typeof unreachable}`);
46
+ }
47
+ /**
48
+ * Mutable binding builder. Internally indexes bindings starting at 1 and
49
+ * returns the `?` placeholder for each append so callers can weave them into
50
+ * their SQL composition.
51
+ */
52
+ var BindingBuilder = class {
53
+ map = {};
54
+ counter = 0;
55
+ add(value) {
56
+ this.counter += 1;
57
+ this.map[String(this.counter)] = inferBinding(value);
58
+ return "?";
59
+ }
60
+ toRecord() {
61
+ return this.map;
62
+ }
63
+ size() {
64
+ return this.counter;
65
+ }
66
+ };
67
+ function composeWhere(where, bindings) {
68
+ const clauses = [];
69
+ for (const [column, value] of Object.entries(where)) {
70
+ if (value === null || value === void 0) {
71
+ clauses.push(`${quoteIdentifier(column)} IS NULL`);
72
+ continue;
73
+ }
74
+ clauses.push(`${quoteIdentifier(column)} = ${bindings.add(value)}`);
75
+ }
76
+ return clauses.length > 0 ? ` WHERE ${clauses.join(" AND ")}` : "";
77
+ }
78
+ function composeSet(set, bindings) {
79
+ const fragments = Object.entries(set).map(([column, value]) => `${quoteIdentifier(column)} = ${bindings.add(value)}`);
80
+ if (fragments.length === 0) throw new Error("updateRow requires at least one column in `set`");
81
+ return fragments.join(", ");
82
+ }
83
+ function executeRowStatement(client, sql, bindings, ctx) {
84
+ return executeSql(client, {
85
+ statement: sql,
86
+ warehouse: ctx.warehouse,
87
+ role: ctx.role,
88
+ database: ctx.database,
89
+ schema: ctx.schema,
90
+ bindings: bindings.size() > 0 ? bindings.toRecord() : void 0
91
+ });
92
+ }
93
+ /**
94
+ * Read the first numeric value from the first row of a DML result.
95
+ * Snowflake returns INSERT/UPDATE/DELETE stats as a single row with a
96
+ * column like `"number of rows inserted"`.
97
+ */
98
+ function readAffectedRows(result) {
99
+ if (result.rows.length === 0) return 0;
100
+ const firstRow = result.rows[0];
101
+ if (!firstRow) return 0;
102
+ const values = Array.isArray(firstRow) ? firstRow : Object.values(firstRow);
103
+ for (const value of values) {
104
+ if (typeof value === "number") return value;
105
+ if (typeof value === "bigint") return Number(value);
106
+ if (typeof value === "string") {
107
+ const parsed = Number(value);
108
+ if (!Number.isNaN(parsed)) return parsed;
109
+ }
110
+ }
111
+ return 0;
112
+ }
113
+ function composeColumnList(columns) {
114
+ if (!columns || columns.length === 0) return "*";
115
+ return columns.map((c) => quoteIdentifier(c)).join(", ");
116
+ }
117
+ function composeOrderBy(orderBy) {
118
+ if (!orderBy || orderBy.length === 0) return "";
119
+ return ` ORDER BY ${orderBy.map(({ column, direction }) => {
120
+ const dir = direction === "DESC" ? " DESC" : " ASC";
121
+ return `${quoteIdentifier(column)}${dir}`;
122
+ }).join(", ")}`;
123
+ }
124
+ function composeLimitOffset(limit, offset) {
125
+ let out = "";
126
+ if (typeof limit === "number" && limit >= 0) out += ` LIMIT ${Math.floor(limit)}`;
127
+ if (typeof offset === "number" && offset > 0) out += ` OFFSET ${Math.floor(offset)}`;
128
+ return out;
129
+ }
130
+ async function findRows(client, options) {
131
+ const bindings = new BindingBuilder();
132
+ return (await executeRowStatement(client, `SELECT ${composeColumnList(options.columns)} FROM ${buildFqn(options.database, options.schema, options.table)}` + composeWhere(options.where ?? {}, bindings) + composeOrderBy(options.orderBy) + composeLimitOffset(options.limit, options.offset), bindings, options)).rows;
133
+ }
134
+ async function findRow(client, options) {
135
+ return (await findRows(client, {
136
+ ...options,
137
+ limit: 1
138
+ }))[0];
139
+ }
140
+ async function insertRow(client, options) {
141
+ const columns = Object.keys(options.row);
142
+ if (columns.length === 0) throw new Error("insertRow requires at least one column in `row`");
143
+ const bindings = new BindingBuilder();
144
+ const placeholders = columns.map((column) => bindings.add(options.row[column]));
145
+ return readAffectedRows(await executeRowStatement(client, `INSERT INTO ${buildFqn(options.database, options.schema, options.table)} (${columns.map((c) => quoteIdentifier(c)).join(", ")}) VALUES (${placeholders.join(", ")})`, bindings, options));
146
+ }
147
+ async function batchInsert(client, options) {
148
+ if (options.rows.length === 0) return 0;
149
+ const first = options.rows[0];
150
+ if (!first) return 0;
151
+ const columns = options.columns ?? Object.keys(first);
152
+ if (columns.length === 0) throw new Error("batchInsert requires at least one column");
153
+ const bindings = new BindingBuilder();
154
+ const valueTuples = options.rows.map((row) => {
155
+ return `(${columns.map((column) => bindings.add(row[column])).join(", ")})`;
156
+ });
157
+ return readAffectedRows(await executeRowStatement(client, `INSERT INTO ${buildFqn(options.database, options.schema, options.table)} (${columns.map((c) => quoteIdentifier(c)).join(", ")}) VALUES ${valueTuples.join(", ")}`, bindings, options));
158
+ }
159
+ async function updateRow(client, options) {
160
+ const bindings = new BindingBuilder();
161
+ const setClause = composeSet(options.set, bindings);
162
+ const whereClause = composeWhere(options.where, bindings);
163
+ return readAffectedRows(await executeRowStatement(client, `UPDATE ${buildFqn(options.database, options.schema, options.table)} SET ${setClause}${whereClause}`, bindings, options));
164
+ }
165
+ async function deleteRow(client, options) {
166
+ if (Object.keys(options.where).length === 0) throw new Error("deleteRow refuses an empty where filter; call executeSql to truncate intentionally");
167
+ const bindings = new BindingBuilder();
168
+ const whereClause = composeWhere(options.where, bindings);
169
+ return readAffectedRows(await executeRowStatement(client, `DELETE FROM ${buildFqn(options.database, options.schema, options.table)}${whereClause}`, bindings, options));
170
+ }
171
+ function readMergeStats(result) {
172
+ if (result.rows.length === 0) return {
173
+ inserted: 0,
174
+ updated: 0
175
+ };
176
+ const firstRow = result.rows[0];
177
+ if (!firstRow) return {
178
+ inserted: 0,
179
+ updated: 0
180
+ };
181
+ const entries = Array.isArray(firstRow) ? result.columns.map((col, idx) => [col.name, firstRow[idx]]) : Object.entries(firstRow);
182
+ let inserted = 0;
183
+ let updated = 0;
184
+ for (const [name, value] of entries) {
185
+ const numeric = typeof value === "number" ? value : Number(value);
186
+ if (!Number.isFinite(numeric)) continue;
187
+ const key = name.toLowerCase();
188
+ if (key.includes("insert")) inserted = numeric;
189
+ else if (key.includes("update")) updated = numeric;
190
+ }
191
+ return {
192
+ inserted,
193
+ updated
194
+ };
195
+ }
196
+ async function upsertRow(client, options) {
197
+ const rowColumns = Object.keys(options.row);
198
+ if (rowColumns.length === 0) throw new Error("upsertRow requires at least one column in `row`");
199
+ if (options.matchColumns.length === 0) throw new Error("upsertRow requires at least one match column");
200
+ for (const column of options.matchColumns) if (!rowColumns.includes(column)) throw new Error(`upsertRow match column "${column}" is missing from row`);
201
+ const bindings = new BindingBuilder();
202
+ const srcTuples = rowColumns.map((column) => bindings.add(options.row[column]));
203
+ const fqn = buildFqn(options.database, options.schema, options.table);
204
+ rowColumns.map((c) => quoteIdentifier(c)).join(", ");
205
+ const matchPredicate = options.matchColumns.map((column) => `t.${quoteIdentifier(column)} = s.${quoteIdentifier(column)}`).join(" AND ");
206
+ const updateSet = (options.updateColumns ?? rowColumns.filter((c) => !options.matchColumns.includes(c))).map((column) => `${quoteIdentifier(column)} = s.${quoteIdentifier(column)}`).join(", ");
207
+ const insertCols = rowColumns.map((c) => quoteIdentifier(c)).join(", ");
208
+ const insertVals = rowColumns.map((c) => `s.${quoteIdentifier(c)}`).join(", ");
209
+ return readMergeStats(await executeRowStatement(client, `MERGE INTO ${fqn} t USING (SELECT ${srcTuples.map((placeholder, idx) => `${placeholder} AS ${quoteIdentifier(rowColumns[idx] ?? "")}`).join(", ")}) s ON ${matchPredicate}` + (updateSet ? ` WHEN MATCHED THEN UPDATE SET ${updateSet}` : "") + ` WHEN NOT MATCHED THEN INSERT (${insertCols}) VALUES (${insertVals})`, bindings, options));
210
+ }
211
+ async function mergeRows(client, options) {
212
+ return readMergeStats(await executeSql(client, {
213
+ statement: options.statement,
214
+ warehouse: options.warehouse,
215
+ role: options.role,
216
+ database: options.database,
217
+ schema: options.schema,
218
+ bindings: options.bindings
219
+ }));
220
+ }
221
+
222
+ //#endregion
223
+ export { BindingBuilder, batchInsert, deleteRow, findRow, findRows, inferBinding, insertRow, mergeRows, updateRow, upsertRow };
@@ -0,0 +1,2 @@
1
+ import { C as resultSetMetaDataSchema, D as sqlBindingValueSchema, E as snowflakeObjectRowSchema, O as sqlBindingsSchema, S as partitionInfoSchema, T as snowflakeIdentifier, _ as SnowflakeObjectRow, a as SubmitStatementInput, b as StatementContext, c as statementStatus, d as HydratedRow, f as PartitionResponse, g as ResultSetMetaData, h as PartitionInfo, i as StatementTerminalStatus, k as statementContextSchema, l as statementStatusCodeSchema, m as ColumnMeta, n as StatementResponse, o as cancelStatementResponseSchema, p as partitionResponseSchema, r as StatementStatus, s as statementResponseSchema, t as CancelStatementResponse, u as submitStatementInputSchema, v as SqlBinding, w as snowflakeFqn, x as columnMetaSchema, y as SqlBindings } from "../statements-DJL0qVNA.mjs";
2
+ export { CancelStatementResponse, ColumnMeta, HydratedRow, PartitionInfo, PartitionResponse, ResultSetMetaData, SnowflakeObjectRow, SqlBinding, SqlBindings, StatementContext, StatementResponse, StatementStatus, StatementTerminalStatus, SubmitStatementInput, cancelStatementResponseSchema, columnMetaSchema, partitionInfoSchema, partitionResponseSchema, resultSetMetaDataSchema, snowflakeFqn, snowflakeIdentifier, snowflakeObjectRowSchema, sqlBindingValueSchema, sqlBindingsSchema, statementContextSchema, statementResponseSchema, statementStatus, statementStatusCodeSchema, submitStatementInputSchema };
@@ -0,0 +1,4 @@
1
+ import { a as snowflakeIdentifier, c as sqlBindingsSchema, i as snowflakeFqn, l as statementContextSchema, n as partitionInfoSchema, o as snowflakeObjectRowSchema, r as resultSetMetaDataSchema, s as sqlBindingValueSchema, t as columnMetaSchema } from "../common-BnKTPQXc.mjs";
2
+ import { a as submitStatementInputSchema, i as statementStatusCodeSchema, n as statementResponseSchema, o as partitionResponseSchema, r as statementStatus, t as cancelStatementResponseSchema } from "../statements-B2ThF_4b.mjs";
3
+
4
+ export { cancelStatementResponseSchema, columnMetaSchema, partitionInfoSchema, partitionResponseSchema, resultSetMetaDataSchema, snowflakeFqn, snowflakeIdentifier, snowflakeObjectRowSchema, sqlBindingValueSchema, sqlBindingsSchema, statementContextSchema, statementResponseSchema, statementStatus, statementStatusCodeSchema, submitStatementInputSchema };
@@ -0,0 +1,2 @@
1
+ import { CatalogResult, FindSchemasOptions, ShowSchemasOptions, findSchemas, showSchemas } from "./catalog.mjs";
2
+ export { type CatalogResult, type FindSchemasOptions, type ShowSchemasOptions, findSchemas, showSchemas };
@@ -0,0 +1,3 @@
1
+ import { findSchemas, showSchemas } from "./catalog.mjs";
2
+
3
+ export { findSchemas, showSchemas };
@@ -0,0 +1,56 @@
1
+ import { SnowflakeClient } from "./client.mjs";
2
+ import { d as HydratedRow } from "./statements-DJL0qVNA.mjs";
3
+ import { CatalogResult, ShowOptions, showShares } from "./catalog.mjs";
4
+
5
+ //#region src/shares.d.ts
6
+ interface ShareContext {
7
+ readonly warehouse?: string;
8
+ readonly role?: string;
9
+ }
10
+ interface CreateShareOptions extends ShareContext {
11
+ readonly name: string;
12
+ readonly orReplace?: boolean;
13
+ readonly ifNotExists?: boolean;
14
+ readonly comment?: string;
15
+ }
16
+ declare function createShare(client: SnowflakeClient, options: CreateShareOptions): Promise<void>;
17
+ interface AlterShareOptions extends ShareContext {
18
+ readonly name: string;
19
+ readonly ifExists?: boolean;
20
+ /** Replace the full account list via `SET ACCOUNTS = (...)`. */
21
+ readonly setAccounts?: readonly string[];
22
+ /** `ADD ACCOUNTS = (...)`. */
23
+ readonly addAccounts?: readonly string[];
24
+ /** `REMOVE ACCOUNTS = (...)`. */
25
+ readonly removeAccounts?: readonly string[];
26
+ /** `SET COMMENT = '...'`. */
27
+ readonly comment?: string;
28
+ /** `UNSET COMMENT`. */
29
+ readonly unsetComment?: boolean;
30
+ }
31
+ declare function alterShare(client: SnowflakeClient, options: AlterShareOptions): Promise<void>;
32
+ interface DropShareOptions extends ShareContext {
33
+ readonly name: string;
34
+ readonly ifExists?: boolean;
35
+ }
36
+ declare function dropShare(client: SnowflakeClient, options: DropShareOptions): Promise<void>;
37
+ interface DescribeShareOptions extends ShareContext {
38
+ readonly name: string;
39
+ }
40
+ declare function describeShare(client: SnowflakeClient, options: DescribeShareOptions): Promise<readonly HydratedRow[]>;
41
+ interface AddAccountsToShareOptions extends ShareContext {
42
+ readonly name: string;
43
+ readonly accounts: readonly string[];
44
+ readonly ifExists?: boolean;
45
+ /** Optional `SHARE_RESTRICTIONS = TRUE|FALSE` appended to ADD ACCOUNTS. */
46
+ readonly shareRestrictions?: boolean;
47
+ }
48
+ declare function addAccountsToShare(client: SnowflakeClient, options: AddAccountsToShareOptions): Promise<void>;
49
+ interface RemoveAccountsFromShareOptions extends ShareContext {
50
+ readonly name: string;
51
+ readonly accounts: readonly string[];
52
+ readonly ifExists?: boolean;
53
+ }
54
+ declare function removeAccountsFromShare(client: SnowflakeClient, options: RemoveAccountsFromShareOptions): Promise<void>;
55
+ //#endregion
56
+ export { AddAccountsToShareOptions, AlterShareOptions, type CatalogResult, CreateShareOptions, DescribeShareOptions, DropShareOptions, RemoveAccountsFromShareOptions, ShareContext, type ShowOptions, addAccountsToShare, alterShare, createShare, describeShare, dropShare, removeAccountsFromShare, showShares };
@@ -0,0 +1,77 @@
1
+ import { executeSql } from "./sql.mjs";
2
+ import { a as quoteLiteral, i as quoteIdentifier } from "./sql-safety-CywdR3EP.mjs";
3
+ import { showShares } from "./catalog.mjs";
4
+
5
+ //#region src/shares.ts
6
+ async function createShare(client, options) {
7
+ const prefix = `CREATE${options.orReplace ? " OR REPLACE" : ""} SHARE${options.ifNotExists ? " IF NOT EXISTS" : ""}`;
8
+ const comment = options.comment ? ` COMMENT = ${quoteLiteral(options.comment)}` : "";
9
+ await executeSql(client, {
10
+ statement: `${prefix} ${quoteIdentifier(options.name)}${comment}`,
11
+ warehouse: options.warehouse,
12
+ role: options.role
13
+ });
14
+ }
15
+ /**
16
+ * Snowflake account identifiers for shares take the form
17
+ * `<orgName>.<accountName>` (unquoted). We validate the shape lightly
18
+ * and pass it through without quoting because Snowflake treats quoted
19
+ * identifiers here as case-sensitive names which would not match the
20
+ * real account.
21
+ */
22
+ const ACCOUNT_ID_RE = /^[A-Za-z0-9_]+\.[A-Za-z0-9_]+$/u;
23
+ function renderAccountList(accounts) {
24
+ if (accounts.length === 0) throw new Error("accounts must be a non-empty list");
25
+ for (const a of accounts) if (!ACCOUNT_ID_RE.test(a)) throw new Error(`Invalid account identifier "${a}" — expected "<orgName>.<accountName>" (letters, digits, underscore)`);
26
+ return accounts.join(", ");
27
+ }
28
+ async function alterShare(client, options) {
29
+ let tail;
30
+ if (options.addAccounts) tail = `ADD ACCOUNTS = (${renderAccountList(options.addAccounts)})`;
31
+ else if (options.removeAccounts) tail = `REMOVE ACCOUNTS = (${renderAccountList(options.removeAccounts)})`;
32
+ else if (options.setAccounts) tail = `SET ACCOUNTS = (${renderAccountList(options.setAccounts)})`;
33
+ else if (options.comment !== void 0) tail = `SET COMMENT = ${quoteLiteral(options.comment)}`;
34
+ else if (options.unsetComment) tail = "UNSET COMMENT";
35
+ else throw new Error("alterShare requires one of: setAccounts / addAccounts / removeAccounts / comment / unsetComment");
36
+ await executeSql(client, {
37
+ statement: `ALTER SHARE${options.ifExists ? " IF EXISTS" : ""} ${quoteIdentifier(options.name)} ${tail}`,
38
+ warehouse: options.warehouse,
39
+ role: options.role
40
+ });
41
+ }
42
+ async function dropShare(client, options) {
43
+ await executeSql(client, {
44
+ statement: `DROP SHARE${options.ifExists ? " IF EXISTS" : ""} ${quoteIdentifier(options.name)}`,
45
+ warehouse: options.warehouse,
46
+ role: options.role
47
+ });
48
+ }
49
+ async function describeShare(client, options) {
50
+ return (await executeSql(client, {
51
+ statement: `DESCRIBE SHARE ${quoteIdentifier(options.name)}`,
52
+ warehouse: options.warehouse,
53
+ role: options.role
54
+ })).rows;
55
+ }
56
+ async function addAccountsToShare(client, options) {
57
+ const ifExists = options.ifExists ? " IF EXISTS" : "";
58
+ const accounts = renderAccountList(options.accounts);
59
+ const restrictions = options.shareRestrictions !== void 0 ? ` SHARE_RESTRICTIONS = ${options.shareRestrictions ? "TRUE" : "FALSE"}` : "";
60
+ await executeSql(client, {
61
+ statement: `ALTER SHARE${ifExists} ${quoteIdentifier(options.name)} ADD ACCOUNTS = (${accounts})${restrictions}`,
62
+ warehouse: options.warehouse,
63
+ role: options.role
64
+ });
65
+ }
66
+ async function removeAccountsFromShare(client, options) {
67
+ const ifExists = options.ifExists ? " IF EXISTS" : "";
68
+ const accounts = renderAccountList(options.accounts);
69
+ await executeSql(client, {
70
+ statement: `ALTER SHARE${ifExists} ${quoteIdentifier(options.name)} REMOVE ACCOUNTS = (${accounts})`,
71
+ warehouse: options.warehouse,
72
+ role: options.role
73
+ });
74
+ }
75
+
76
+ //#endregion
77
+ export { addAccountsToShare, alterShare, createShare, describeShare, dropShare, removeAccountsFromShare, showShares };
@@ -0,0 +1,32 @@
1
+ //#region src/sql-options.d.ts
2
+ /**
3
+ * Option-list composition helpers for Snowflake DDL/DML.
4
+ *
5
+ * Most CREATE/ALTER statements (stages, file formats, pipes, tasks, COPY
6
+ * options, warehouses) accept option lists like:
7
+ *
8
+ * FILE_FORMAT = (TYPE = 'CSV' FIELD_DELIMITER = '|' SKIP_HEADER = 1)
9
+ * CREDENTIALS = (AWS_KEY_ID = 'abc' AWS_SECRET_KEY = 'xyz')
10
+ * COPY_OPTIONS = (ON_ERROR = 'CONTINUE' PURGE = TRUE)
11
+ *
12
+ * These helpers compose those lists safely:
13
+ * - String values are quoted with {@link quoteLiteral}.
14
+ * - Numbers, booleans, and raw-marked values pass through verbatim.
15
+ * - Nested option maps render as parenthesised sub-lists.
16
+ * - Ordering is preserved in insertion order.
17
+ */
18
+ /**
19
+ * A scalar that can appear on the RHS of an option. Pre-built raw SQL can
20
+ * be passed via {@link rawOption} when callers need to inject a keyword
21
+ * (e.g. `AUTO_REFRESH = TRUE`, `ENCRYPTION = (TYPE = 'SNOWFLAKE_SSE')`).
22
+ */
23
+ type OptionScalar = string | number | bigint | boolean | null | undefined | RawSql | OptionMap | readonly OptionScalar[];
24
+ interface OptionMap {
25
+ readonly [key: string]: OptionScalar;
26
+ }
27
+ /** Marker type for a pre-formatted SQL fragment. */
28
+ interface RawSql {
29
+ readonly __rawSql: string;
30
+ }
31
+ //#endregion
32
+ export { OptionMap as t };
@@ -0,0 +1,79 @@
1
+ import { a as quoteLiteral } from "./sql-safety-CywdR3EP.mjs";
2
+
3
+ //#region src/sql-options.ts
4
+ /**
5
+ * Option-list composition helpers for Snowflake DDL/DML.
6
+ *
7
+ * Most CREATE/ALTER statements (stages, file formats, pipes, tasks, COPY
8
+ * options, warehouses) accept option lists like:
9
+ *
10
+ * FILE_FORMAT = (TYPE = 'CSV' FIELD_DELIMITER = '|' SKIP_HEADER = 1)
11
+ * CREDENTIALS = (AWS_KEY_ID = 'abc' AWS_SECRET_KEY = 'xyz')
12
+ * COPY_OPTIONS = (ON_ERROR = 'CONTINUE' PURGE = TRUE)
13
+ *
14
+ * These helpers compose those lists safely:
15
+ * - String values are quoted with {@link quoteLiteral}.
16
+ * - Numbers, booleans, and raw-marked values pass through verbatim.
17
+ * - Nested option maps render as parenthesised sub-lists.
18
+ * - Ordering is preserved in insertion order.
19
+ */
20
+ function isRawSql(value) {
21
+ return typeof value === "object" && value !== null && typeof value.__rawSql === "string";
22
+ }
23
+ function isOptionMap(value) {
24
+ if (value === null || typeof value !== "object") return false;
25
+ if (Array.isArray(value)) return false;
26
+ if (isRawSql(value)) return false;
27
+ if (value instanceof Date) return false;
28
+ return true;
29
+ }
30
+ function formatScalar(value) {
31
+ if (value === void 0) return void 0;
32
+ if (value === null) return "NULL";
33
+ if (isRawSql(value)) return value.__rawSql;
34
+ if (typeof value === "string") return quoteLiteral(value);
35
+ if (typeof value === "number") {
36
+ if (!Number.isFinite(value)) throw new TypeError("Cannot serialise NaN/Infinity as a Snowflake option value");
37
+ return value.toString();
38
+ }
39
+ if (typeof value === "bigint") return value.toString();
40
+ if (typeof value === "boolean") return value ? "TRUE" : "FALSE";
41
+ if (Array.isArray(value)) return `(${value.map((item) => formatScalar(item)).filter((item) => item !== void 0).join(", ")})`;
42
+ if (isOptionMap(value)) return formatOptionGroup(value);
43
+ throw new TypeError(`Unsupported Snowflake option value: ${typeof value}`);
44
+ }
45
+ /** Render a map like `(KEY = value KEY2 = 'literal')`. Empty maps render as `()`. */
46
+ function formatOptionGroup(map) {
47
+ const parts = [];
48
+ for (const [key, value] of Object.entries(map)) {
49
+ const rendered = formatScalar(value);
50
+ if (rendered === void 0) continue;
51
+ parts.push(`${key.toUpperCase()} = ${rendered}`);
52
+ }
53
+ return `(${parts.join(" ")})`;
54
+ }
55
+ /**
56
+ * Render a map as a space-separated option list (no wrapping parens). This
57
+ * is the form used directly in `CREATE STAGE ... URL='...' CREDENTIALS=(..)`
58
+ * where top-level options sit alongside the object name.
59
+ */
60
+ function formatOptions(map) {
61
+ const parts = [];
62
+ for (const [key, value] of Object.entries(map)) {
63
+ const rendered = formatScalar(value);
64
+ if (rendered === void 0) continue;
65
+ parts.push(`${key.toUpperCase()} = ${rendered}`);
66
+ }
67
+ return parts.join(" ");
68
+ }
69
+ /**
70
+ * Render a nested option group as ` KEY = (...)` — returns empty string when
71
+ * the map is undefined so callers can compose optional clauses inline.
72
+ */
73
+ function renderOptionGroup(key, map) {
74
+ if (!map) return "";
75
+ return ` ${key.toUpperCase()} = ${formatOptionGroup(map)}`;
76
+ }
77
+
78
+ //#endregion
79
+ export { renderOptionGroup as n, formatOptions as t };
@@ -0,0 +1,56 @@
1
+ //#region src/sql-safety.ts
2
+ /**
3
+ * SQL identifier + literal escaping utilities.
4
+ *
5
+ * Snowflake follows ANSI rules:
6
+ * - Identifiers: wrap in `"..."`; internal `"` is escaped as `""`.
7
+ * - String literals: wrap in `'...'`; internal `'` is escaped as `''`.
8
+ * - Numeric, boolean, null literals are written raw.
9
+ *
10
+ * Every domain module that composes SQL goes through these helpers — they
11
+ * are the package's sole defense against SQL injection from operation
12
+ * inputs that name identifiers or match patterns.
13
+ */
14
+ const UNQUOTED_IDENT = /^[A-Z_][A-Z0-9_$]*$/u;
15
+ /**
16
+ * Return a double-quoted form of the identifier, escaping embedded
17
+ * double-quotes. When `allowUnquoted` is true and the input is already a
18
+ * canonical unquoted identifier, return it verbatim so the resulting SQL
19
+ * is more readable.
20
+ */
21
+ function quoteIdentifier(name, options = {}) {
22
+ if (options.allowUnquoted && UNQUOTED_IDENT.test(name)) return name;
23
+ return `"${name.replaceAll("\"", "\"\"")}"`;
24
+ }
25
+ /**
26
+ * Build a fully-qualified name from up to three segments. Each segment is
27
+ * independently quoted. Missing leading segments are omitted.
28
+ */
29
+ function buildFqn(...segments) {
30
+ return segments.filter((segment) => segment !== void 0 && segment.length > 0).map((segment) => quoteIdentifier(segment)).join(".");
31
+ }
32
+ /**
33
+ * Quote a Snowflake string literal, doubling embedded single quotes.
34
+ */
35
+ function quoteLiteral(value) {
36
+ return `'${value.replaceAll("'", "''")}'`;
37
+ }
38
+ /**
39
+ * Compose `LIKE '<pattern>'` when `pattern` is provided, empty string
40
+ * otherwise. Pattern is escaped as a SQL literal.
41
+ */
42
+ function likeClause(pattern) {
43
+ return pattern ? ` LIKE ${quoteLiteral(pattern)}` : "";
44
+ }
45
+ /**
46
+ * Compose `IN <scopeKeyword> <quoted identifier>` clause (e.g. "IN DATABASE
47
+ * MYDB"). Caller supplies a pre-joined identifier (fqn already built).
48
+ */
49
+ function inClause(scopeKeyword, target) {
50
+ if (!target) return "";
51
+ if (scopeKeyword === "ACCOUNT") return " IN ACCOUNT";
52
+ return ` IN ${scopeKeyword} ${target}`;
53
+ }
54
+
55
+ //#endregion
56
+ export { quoteLiteral as a, quoteIdentifier as i, inClause as n, likeClause as r, buildFqn as t };
package/dist/sql.d.mts ADDED
@@ -0,0 +1,84 @@
1
+ import { SnowflakeClient } from "./client.mjs";
2
+ import { a as SubmitStatementInput, d as HydratedRow, f as PartitionResponse, m as ColumnMeta, n as StatementResponse } from "./statements-DJL0qVNA.mjs";
3
+ //#region src/sql.d.ts
4
+ interface SubmitOptions {
5
+ readonly async?: boolean;
6
+ readonly requestId?: string;
7
+ }
8
+ interface SubmitResult {
9
+ readonly response: StatementResponse;
10
+ readonly status: 'done' | 'failed' | 'pending' | 'cancelled';
11
+ readonly httpStatus: number;
12
+ }
13
+ /**
14
+ * Submit a statement. When `async: true` (or the server chooses async) we
15
+ * return the inflated response envelope along with the `pending` status so
16
+ * callers can poll via {@link getStatement} / {@link waitForStatement}.
17
+ *
18
+ * The SQL API itself keys idempotency on the `requestId` query param which
19
+ * {@link SnowflakeClient} auto-populates — callers can override it per
20
+ * request when they need deterministic retries.
21
+ */
22
+ declare function submitStatement(client: SnowflakeClient, input: SubmitStatementInput, options?: SubmitOptions): Promise<SubmitResult>;
23
+ /**
24
+ * Fetch the current status of a previously submitted async statement. Returns
25
+ * both the envelope and the derived `status` bucket.
26
+ */
27
+ declare function getStatement(client: SnowflakeClient, handle: string, options?: {
28
+ readonly partition?: number;
29
+ }): Promise<SubmitResult>;
30
+ /**
31
+ * POST `/cancel`. Snowflake responds with `code: 333335` on success — we
32
+ * normalise anything else into a {@link SnowflakeApiError}.
33
+ */
34
+ declare function cancelStatement(client: SnowflakeClient, handle: string): Promise<void>;
35
+ interface ExecuteSqlOptions extends SubmitOptions {
36
+ readonly pollIntervalMs?: number;
37
+ readonly maxWaitMs?: number;
38
+ readonly hydrate?: boolean;
39
+ readonly now?: () => number;
40
+ readonly sleep?: (ms: number) => Promise<void>;
41
+ }
42
+ interface ExecuteSqlResult<TRow = HydratedRow> {
43
+ readonly handle: string;
44
+ readonly columns: readonly ColumnMeta[];
45
+ readonly rows: readonly TRow[];
46
+ readonly envelope: StatementResponse;
47
+ readonly partitions: number;
48
+ }
49
+ /**
50
+ * Submit + wait + fetch all partitions in one go. The returned rows are
51
+ * hydrated into objects keyed by column name by default; pass
52
+ * `hydrate: false` to get the raw tuple form.
53
+ */
54
+ declare function executeSql(client: SnowflakeClient, input: SubmitStatementInput, options?: ExecuteSqlOptions): Promise<ExecuteSqlResult<HydratedRow | readonly unknown[]>>;
55
+ /**
56
+ * Composio `SNOWFLAKE_BASIC_RUN_QUERY` parity — requires an explicit
57
+ * database + schema to match the published BASIC shape.
58
+ */
59
+ declare function runQuery(client: SnowflakeClient, input: {
60
+ readonly statement: string;
61
+ readonly database: string;
62
+ readonly schema_name: string;
63
+ readonly warehouse?: string;
64
+ readonly role?: string;
65
+ }, options?: ExecuteSqlOptions): Promise<ExecuteSqlResult<HydratedRow | readonly unknown[]>>;
66
+ /**
67
+ * Fetch a single partition for a known handle.
68
+ */
69
+ declare function getStatementResultPartition(client: SnowflakeClient, handle: string, partition: number): Promise<PartitionResponse>;
70
+ /**
71
+ * Async iterator over every partition for a handle. Useful for streaming
72
+ * large result sets into downstream pipelines.
73
+ */
74
+ declare function iterateStatementResult(client: SnowflakeClient, handle: string, options?: {
75
+ readonly maxPartitions?: number;
76
+ }): AsyncGenerator<PartitionResponse, void, void>;
77
+ /**
78
+ * Blocking helper: polls `getStatement` until the statement reaches a
79
+ * terminal status or the `maxWaitMs` budget elapses. Returns the terminal
80
+ * envelope (throws for `failed` / `cancelled`).
81
+ */
82
+ declare function waitForStatement(client: SnowflakeClient, handle: string, options?: ExecuteSqlOptions): Promise<StatementResponse>;
83
+ //#endregion
84
+ export { ExecuteSqlOptions, ExecuteSqlResult, SubmitOptions, SubmitResult, cancelStatement, executeSql, getStatement, getStatementResultPartition, iterateStatementResult, runQuery, submitStatement, waitForStatement };