@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
@@ -0,0 +1,1363 @@
1
+ import { c as snowflakeGcsPubSubWebhookCredentialSet, l as snowflakeS3SnsWebhookCredentialSet, n as snowflake, s as snowflakeAzureEventGridWebhookCredentialSet } from "./integration-Dptv8L0L.mjs";
2
+ import { createSnowflakeClient } from "./client.mjs";
3
+ import { azureEventGridEventSchema, gcsPubSubPushSchema, grantChangedEventSchema, pipeLoadEventSchema, scheduledDeltaEventSchema, scheduledSqlEventSchema, shareChangedEventSchema, snowflakeDiffSnapshotEntrySchema, snowflakeObjectLifecycleEventSchema, snsNotificationSchema, statementCompletedEventSchema, statusPageComponentChangeEventSchema, statusPageIncidentEventSchema, streamAppendOnlyEventSchema, streamHasDataEventSchema, taskRunEventSchema, warehouseCreditsEventSchema, warehouseQueueEventSchema } from "./events.mjs";
4
+ import { executeSql } from "./sql.mjs";
5
+ import { a as quoteLiteral, i as quoteIdentifier, t as buildFqn } from "./sql-safety-CywdR3EP.mjs";
6
+ import { BindingBuilder } from "./rows.mjs";
7
+ import { getStatusSummary, getUnresolvedIncidents } from "./status-page.mjs";
8
+ import { EVENT_GRID_VALIDATION_EVENT_TYPE, SnowflakeWebhookVerificationError, extractGcsObjectPayload, extractS3Records, parseAzureBlobLocation, parsePubSubPush, verifyEventGridRequest, verifyPubSubPushAuthorization, verifySnsMessage } from "./verification.mjs";
9
+ import { z } from "zod";
10
+ import { createPollingTriggerBindingFactory, createWebhookTriggerBindingFactory } from "@keystrokehq/integration-authoring";
11
+
12
+ //#region src/triggers.ts
13
+ function defineSnowflakePollingTrigger(definition) {
14
+ return definition;
15
+ }
16
+ function splitBindingOptions(input) {
17
+ return {
18
+ schedule: input?.schedule,
19
+ bindOptions: {
20
+ ...input?.name ? { name: input.name } : {},
21
+ ...input?.description ? { description: input.description } : {},
22
+ ...input?.filter ? { filter: input.filter } : {},
23
+ ...input?.transform ? { transform: input.transform } : {}
24
+ }
25
+ };
26
+ }
27
+ function resolveSchedule(override, fallback) {
28
+ if (override === void 0) return fallback;
29
+ return override;
30
+ }
31
+ function createSnowflakePollingBindingFactory(definition, params, schedule) {
32
+ return createPollingTriggerBindingFactory({
33
+ defaultName: definition.defaultName,
34
+ defaultDescription: definition.defaultDescription,
35
+ schedule: resolveSchedule(schedule, definition.defaultSchedule),
36
+ credentialSets: [snowflake],
37
+ response: definition.responseSchema,
38
+ poll: (ctx) => {
39
+ const snowflakeCtx = ctx;
40
+ return definition.poll(params, snowflakeCtx.credentials.snowflake, snowflakeCtx);
41
+ },
42
+ idempotencyKey: (response) => definition.idempotencyKey(response),
43
+ definitionFilter: (response) => definition.hasPayload(response)
44
+ });
45
+ }
46
+ function bindSnowflakePollingTrigger(definition, params, input) {
47
+ const { schedule, bindOptions } = splitBindingOptions(input);
48
+ return createSnowflakePollingBindingFactory(definition, params, schedule)(bindOptions);
49
+ }
50
+ function isoNow() {
51
+ return (/* @__PURE__ */ new Date()).toISOString();
52
+ }
53
+ function readStringField(row, ...keys) {
54
+ for (const key of keys) {
55
+ const value = row[key];
56
+ if (typeof value === "string" && value.length > 0) return value;
57
+ }
58
+ }
59
+ function readNumberField(row, ...keys) {
60
+ for (const key of keys) {
61
+ const value = row[key];
62
+ if (typeof value === "number" && Number.isFinite(value)) return value;
63
+ if (typeof value === "string" && value.length > 0) {
64
+ const parsed = Number(value);
65
+ if (Number.isFinite(parsed)) return parsed;
66
+ }
67
+ }
68
+ }
69
+ function hashStatement(statement) {
70
+ let hash = 0;
71
+ for (let i = 0; i < statement.length; i += 1) hash = (hash << 5) - hash + statement.charCodeAt(i) | 0;
72
+ return `sha-${(hash >>> 0).toString(16)}-${statement.length}`;
73
+ }
74
+ const streamHasNewRowsDefinition = defineSnowflakePollingTrigger({
75
+ responseSchema: streamHasDataEventSchema,
76
+ defaultName: "Snowflake Stream Has New Rows",
77
+ defaultDescription: "Fires when SYSTEM$STREAM_HAS_DATA returns TRUE for a Snowflake stream (fires at most once per schedule tick).",
78
+ defaultSchedule: "1m",
79
+ poll: async (params, credentials) => {
80
+ const client = createSnowflakeClient(credentials);
81
+ const fqn = buildFqn(params.database, params.schema, params.streamName);
82
+ const row = (await executeSql(client, {
83
+ statement: `SELECT SYSTEM$STREAM_HAS_DATA(${quoteLiteral(fqn)}) AS HAS_DATA`,
84
+ warehouse: params.warehouse,
85
+ role: params.role
86
+ })).rows[0] ?? {};
87
+ const raw = row.HAS_DATA ?? row.has_data;
88
+ let hasData;
89
+ if (typeof raw === "boolean") hasData = raw;
90
+ else if (typeof raw === "string") hasData = raw.toLowerCase() === "true";
91
+ else hasData = Boolean(raw);
92
+ return {
93
+ streamName: fqn,
94
+ hasData,
95
+ checkedAt: isoNow()
96
+ };
97
+ },
98
+ idempotencyKey: (response) => `${response.streamName}:${response.checkedAt}`,
99
+ hasPayload: (response) => response.hasData
100
+ });
101
+ function streamHasNewRows(input) {
102
+ return bindSnowflakePollingTrigger(streamHasNewRowsDefinition, input, input);
103
+ }
104
+ const streamAppendOnlyDefinition = defineSnowflakePollingTrigger({
105
+ responseSchema: streamAppendOnlyEventSchema,
106
+ defaultName: "Snowflake Stream Append-Only",
107
+ defaultDescription: "Emits new inserted rows from a Snowflake stream on each poll. Consumers must progress the stream via downstream DML in a transaction.",
108
+ defaultSchedule: "1m",
109
+ poll: async (params, credentials) => {
110
+ const client = createSnowflakeClient(credentials);
111
+ const fqn = buildFqn(params.database, params.schema, params.streamName);
112
+ const limit = params.limit ?? 1e3;
113
+ const result = await executeSql(client, {
114
+ statement: `SELECT * FROM ${fqn} WHERE ${`METADATA$ACTION = 'INSERT'${params.where ? ` AND (${params.where})` : ""}`} LIMIT ${Number(limit)}`,
115
+ warehouse: params.warehouse,
116
+ role: params.role
117
+ });
118
+ const rows = result.rows;
119
+ return {
120
+ streamName: fqn,
121
+ rows: [...rows],
122
+ columns: [...result.columns],
123
+ rowCount: rows.length,
124
+ fetchedAt: isoNow()
125
+ };
126
+ },
127
+ idempotencyKey: (response) => `${response.streamName}:${response.fetchedAt}:${response.rowCount}`,
128
+ hasPayload: (response) => response.rowCount > 0
129
+ });
130
+ function streamAppendOnly(input) {
131
+ return bindSnowflakePollingTrigger(streamAppendOnlyDefinition, input, input);
132
+ }
133
+ const scheduledSqlDefinition = defineSnowflakePollingTrigger({
134
+ responseSchema: scheduledSqlEventSchema,
135
+ defaultName: "Snowflake Scheduled SQL",
136
+ defaultDescription: "Runs a caller-supplied SQL statement on each schedule tick and emits the full result set.",
137
+ defaultSchedule: "5m",
138
+ poll: async (params, credentials) => {
139
+ const client = createSnowflakeClient(credentials);
140
+ let bindings;
141
+ if (params.bindings && params.bindings.length > 0) {
142
+ const builder = new BindingBuilder();
143
+ for (const value of params.bindings) builder.add(value);
144
+ bindings = { ...builder.toRecord() };
145
+ }
146
+ const result = await executeSql(client, {
147
+ statement: params.statement,
148
+ warehouse: params.warehouse,
149
+ role: params.role,
150
+ database: params.database,
151
+ schema: params.schema,
152
+ ...bindings ? { bindings } : {}
153
+ });
154
+ const rows = result.rows;
155
+ return {
156
+ statementDigest: hashStatement(params.statement),
157
+ rows: [...rows],
158
+ columns: [...result.columns],
159
+ rowCount: rows.length,
160
+ fetchedAt: isoNow()
161
+ };
162
+ },
163
+ idempotencyKey: (response) => `${response.statementDigest}:${response.fetchedAt}`,
164
+ hasPayload: (response) => response.rowCount > 0
165
+ });
166
+ function scheduledSql(input) {
167
+ return bindSnowflakePollingTrigger(input.emitOnEmpty ? {
168
+ ...scheduledSqlDefinition,
169
+ hasPayload: () => true
170
+ } : scheduledSqlDefinition, input, input);
171
+ }
172
+ function stringifyCursor(value) {
173
+ if (value === null || value === void 0) return null;
174
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") return String(value);
175
+ if (value instanceof Date) return value.toISOString();
176
+ return String(value);
177
+ }
178
+ const scheduledDeltaDefinition = defineSnowflakePollingTrigger({
179
+ responseSchema: scheduledDeltaEventSchema,
180
+ defaultName: "Snowflake Scheduled Delta",
181
+ defaultDescription: "Emits new rows from a Snowflake table where a cursor column exceeds the last observed high-water mark.",
182
+ defaultSchedule: "1m",
183
+ poll: async (params, credentials, ctx) => {
184
+ const client = createSnowflakeClient(credentials);
185
+ const fqn = buildFqn(params.database, params.schema, params.table);
186
+ const cursor = quoteIdentifier(params.cursorColumn);
187
+ const limit = params.limit ?? 1e3;
188
+ const selectList = params.columns && params.columns.length > 0 ? params.columns.map((c) => quoteIdentifier(c)).join(", ") : "*";
189
+ const priorHighWater = ctx.lastResponse?.highWaterValue ?? (params.initialHighWater !== void 0 ? String(params.initialHighWater) : null);
190
+ const clauses = [];
191
+ let bindings;
192
+ if (priorHighWater !== null) {
193
+ clauses.push(`${cursor} > ?`);
194
+ bindings = { "1": {
195
+ type: "TEXT",
196
+ value: priorHighWater
197
+ } };
198
+ }
199
+ if (params.where) clauses.push(`(${params.where})`);
200
+ const result = await executeSql(client, {
201
+ statement: `SELECT ${selectList} FROM ${fqn}${clauses.length > 0 ? ` WHERE ${clauses.join(" AND ")}` : ""} ORDER BY ${cursor} ASC LIMIT ${Number(limit)}`,
202
+ warehouse: params.warehouse,
203
+ role: params.role,
204
+ ...bindings ? { bindings } : {}
205
+ });
206
+ const rows = result.rows;
207
+ const latest = rows[rows.length - 1]?.[params.cursorColumn] ?? rows[rows.length - 1]?.[params.cursorColumn.toUpperCase()];
208
+ const nextHighWater = rows.length > 0 ? stringifyCursor(latest) : priorHighWater;
209
+ return {
210
+ table: fqn,
211
+ cursorColumn: params.cursorColumn,
212
+ highWaterValue: nextHighWater,
213
+ rows: [...rows],
214
+ columns: [...result.columns],
215
+ rowCount: rows.length,
216
+ fetchedAt: isoNow()
217
+ };
218
+ },
219
+ idempotencyKey: (response) => `${response.table}:${response.cursorColumn}:${response.highWaterValue ?? "initial"}`,
220
+ hasPayload: (response) => response.rowCount > 0
221
+ });
222
+ function scheduledDelta(input) {
223
+ return bindSnowflakePollingTrigger(scheduledDeltaDefinition, input, input);
224
+ }
225
+ const DEFAULT_TASK_STATES = [
226
+ "SUCCEEDED",
227
+ "FAILED",
228
+ "FAILED_AND_AUTO_SUSPENDED",
229
+ "CANCELLED"
230
+ ];
231
+ const taskRunCompletedDefinition = defineSnowflakePollingTrigger({
232
+ responseSchema: z.object({
233
+ events: z.array(taskRunEventSchema),
234
+ highWaterValue: z.string().nullable(),
235
+ fetchedAt: z.string()
236
+ }).catchall(z.unknown()),
237
+ defaultName: "Snowflake Task Run Completed",
238
+ defaultDescription: "Emits terminal task runs polled from INFORMATION_SCHEMA.TASK_HISTORY (SUCCEEDED / FAILED / CANCELLED).",
239
+ defaultSchedule: "1m",
240
+ poll: async (params, credentials, ctx) => {
241
+ const client = createSnowflakeClient(credentials);
242
+ const limit = params.limit ?? 200;
243
+ const statesPredicate = (params.states ?? DEFAULT_TASK_STATES).map((s) => quoteLiteral(s)).join(", ");
244
+ const priorHighWater = ctx.lastResponse?.highWaterValue ?? null;
245
+ const lookbackMinutes = params.lookbackMinutes ?? 60;
246
+ const since = priorHighWater ?? (/* @__PURE__ */ new Date(Date.now() - lookbackMinutes * 6e4)).toISOString();
247
+ const taskFilter = params.taskName ? `, TASK_NAME => ${quoteLiteral(params.taskName)}` : "";
248
+ const rows = (await executeSql(client, {
249
+ statement: `SELECT * FROM TABLE(INFORMATION_SCHEMA.TASK_HISTORY(${`SCHEDULED_TIME_RANGE_START => TO_TIMESTAMP_LTZ(${quoteLiteral(since)})${taskFilter}, RESULT_LIMIT => ${Number(limit)}`})) WHERE STATE IN (${statesPredicate}) ORDER BY SCHEDULED_TIME ASC`,
250
+ warehouse: params.warehouse,
251
+ role: params.role,
252
+ database: params.database,
253
+ schema: params.schema
254
+ })).rows;
255
+ const events = rows.map((row) => ({
256
+ name: readStringField(row, "NAME", "name") ?? "",
257
+ databaseName: readStringField(row, "DATABASE_NAME", "database_name"),
258
+ schemaName: readStringField(row, "SCHEMA_NAME", "schema_name"),
259
+ state: readStringField(row, "STATE", "state") ?? "UNKNOWN",
260
+ queryId: readStringField(row, "QUERY_ID", "query_id"),
261
+ scheduledTime: readStringField(row, "SCHEDULED_TIME", "scheduled_time"),
262
+ completedTime: readStringField(row, "COMPLETED_TIME", "completed_time"),
263
+ errorCode: readStringField(row, "ERROR_CODE", "error_code") ?? null,
264
+ errorMessage: readStringField(row, "ERROR_MESSAGE", "error_message") ?? null,
265
+ attemptNumber: readNumberField(row, "ATTEMPT_NUMBER", "attempt_number"),
266
+ runId: readStringField(row, "RUN_ID", "run_id"),
267
+ row
268
+ }));
269
+ const last = rows[rows.length - 1];
270
+ return {
271
+ events,
272
+ highWaterValue: readStringField(last ?? {}, "COMPLETED_TIME", "completed_time") ?? readStringField(last ?? {}, "SCHEDULED_TIME", "scheduled_time") ?? priorHighWater,
273
+ fetchedAt: isoNow()
274
+ };
275
+ },
276
+ idempotencyKey: (response) => `task-run:${response.highWaterValue ?? "initial"}:${response.events.length}`,
277
+ hasPayload: (response) => response.events.length > 0
278
+ });
279
+ function taskRunCompleted(input) {
280
+ return bindSnowflakePollingTrigger(taskRunCompletedDefinition, {
281
+ taskName: input?.taskName,
282
+ database: input?.database,
283
+ schema: input?.schema,
284
+ warehouse: input?.warehouse,
285
+ role: input?.role,
286
+ lookbackMinutes: input?.lookbackMinutes,
287
+ limit: input?.limit,
288
+ states: input?.states
289
+ }, input);
290
+ }
291
+ const DEFAULT_STATEMENT_STATES = [
292
+ "SUCCESS",
293
+ "FAIL",
294
+ "FAILED_WITH_ERROR",
295
+ "CANCELLED",
296
+ "INCIDENT"
297
+ ];
298
+ const statementCompletedDefinition = defineSnowflakePollingTrigger({
299
+ responseSchema: z.object({
300
+ events: z.array(statementCompletedEventSchema),
301
+ highWaterValue: z.string().nullable(),
302
+ fetchedAt: z.string()
303
+ }).catchall(z.unknown()),
304
+ defaultName: "Snowflake Statement Completed",
305
+ defaultDescription: "Emits terminal statements from INFORMATION_SCHEMA.QUERY_HISTORY, optionally filtered by user / text / warehouse.",
306
+ defaultSchedule: "1m",
307
+ poll: async (params, credentials, ctx) => {
308
+ const client = createSnowflakeClient(credentials);
309
+ const limit = params.limit ?? 200;
310
+ const states = params.executionStatuses ?? DEFAULT_STATEMENT_STATES;
311
+ const priorHighWater = ctx.lastResponse?.highWaterValue ?? null;
312
+ const lookbackMinutes = params.lookbackMinutes ?? 30;
313
+ const args = [`END_TIME_RANGE_START => TO_TIMESTAMP_LTZ(${quoteLiteral(priorHighWater ?? (/* @__PURE__ */ new Date(Date.now() - lookbackMinutes * 6e4)).toISOString())})`, `RESULT_LIMIT => ${Number(limit)}`];
314
+ if (params.userName) args.push(`USER_NAME => ${quoteLiteral(params.userName)}`);
315
+ if (params.warehouse) args.push(`WAREHOUSE_NAME => ${quoteLiteral(params.warehouse)}`);
316
+ const clauses = [];
317
+ if (!params.includeInFlight) {
318
+ const statesPredicate = states.map((s) => quoteLiteral(s)).join(", ");
319
+ clauses.push(`EXECUTION_STATUS IN (${statesPredicate})`);
320
+ }
321
+ if (params.queryTextLike) clauses.push(`QUERY_TEXT ILIKE ${quoteLiteral(params.queryTextLike)}`);
322
+ const where = clauses.length > 0 ? ` WHERE ${clauses.join(" AND ")}` : "";
323
+ const rows = (await executeSql(client, {
324
+ statement: `SELECT * FROM TABLE(INFORMATION_SCHEMA.QUERY_HISTORY(${args.join(", ")}))${where} ORDER BY END_TIME ASC`,
325
+ warehouse: params.warehouse,
326
+ role: params.role,
327
+ database: params.database,
328
+ schema: params.schema
329
+ })).rows;
330
+ const events = rows.map((row) => ({
331
+ queryId: readStringField(row, "QUERY_ID", "query_id") ?? "",
332
+ executionStatus: readStringField(row, "EXECUTION_STATUS", "execution_status") ?? "UNKNOWN",
333
+ queryText: readStringField(row, "QUERY_TEXT", "query_text"),
334
+ errorCode: readStringField(row, "ERROR_CODE", "error_code") ?? null,
335
+ errorMessage: readStringField(row, "ERROR_MESSAGE", "error_message") ?? null,
336
+ startTime: readStringField(row, "START_TIME", "start_time"),
337
+ endTime: readStringField(row, "END_TIME", "end_time"),
338
+ warehouseName: readStringField(row, "WAREHOUSE_NAME", "warehouse_name"),
339
+ databaseName: readStringField(row, "DATABASE_NAME", "database_name"),
340
+ schemaName: readStringField(row, "SCHEMA_NAME", "schema_name"),
341
+ userName: readStringField(row, "USER_NAME", "user_name"),
342
+ roleName: readStringField(row, "ROLE_NAME", "role_name"),
343
+ totalElapsedTimeMs: readNumberField(row, "TOTAL_ELAPSED_TIME", "total_elapsed_time") ?? null,
344
+ row
345
+ }));
346
+ const last = rows[rows.length - 1];
347
+ return {
348
+ events,
349
+ highWaterValue: readStringField(last ?? {}, "END_TIME", "end_time") ?? readStringField(last ?? {}, "START_TIME", "start_time") ?? priorHighWater,
350
+ fetchedAt: isoNow()
351
+ };
352
+ },
353
+ idempotencyKey: (response) => `stmt:${response.highWaterValue ?? "initial"}:${response.events.length}`,
354
+ hasPayload: (response) => response.events.length > 0
355
+ });
356
+ function statementCompleted(input) {
357
+ return bindSnowflakePollingTrigger(statementCompletedDefinition, {
358
+ warehouse: input?.warehouse,
359
+ role: input?.role,
360
+ database: input?.database,
361
+ schema: input?.schema,
362
+ userName: input?.userName,
363
+ queryTextLike: input?.queryTextLike,
364
+ lookbackMinutes: input?.lookbackMinutes,
365
+ limit: input?.limit,
366
+ executionStatuses: input?.executionStatuses,
367
+ includeInFlight: input?.includeInFlight
368
+ }, input);
369
+ }
370
+ const pipeLoadBatchSchema = z.object({
371
+ events: z.array(pipeLoadEventSchema),
372
+ highWaterValue: z.string().nullable(),
373
+ fetchedAt: z.string()
374
+ }).catchall(z.unknown());
375
+ function buildPipeLoadDefinition(defaultName, defaultDescription, statusPredicate) {
376
+ return defineSnowflakePollingTrigger({
377
+ responseSchema: pipeLoadBatchSchema,
378
+ defaultName,
379
+ defaultDescription,
380
+ defaultSchedule: "1m",
381
+ poll: async (params, credentials, ctx) => {
382
+ const client = createSnowflakeClient(credentials);
383
+ const limit = params.limit ?? 200;
384
+ const priorHighWater = ctx.lastResponse?.highWaterValue ?? null;
385
+ const lookbackMinutes = params.lookbackMinutes ?? 60;
386
+ const args = [`START_TIME => TO_TIMESTAMP_LTZ(${quoteLiteral(priorHighWater ?? (/* @__PURE__ */ new Date(Date.now() - lookbackMinutes * 6e4)).toISOString())})`];
387
+ if (params.tableName) {
388
+ const tableFqn = buildFqn(params.database, params.schema, params.tableName);
389
+ args.push(`TABLE_NAME => ${quoteLiteral(tableFqn)}`);
390
+ }
391
+ const pipeFilter = params.pipeName ? ` AND PIPE_NAME = ${quoteLiteral(buildFqn(params.database, params.schema, params.pipeName))}` : "";
392
+ const rows = (await executeSql(client, {
393
+ statement: `SELECT * FROM TABLE(INFORMATION_SCHEMA.COPY_HISTORY(${args.join(", ")})) WHERE ${statusPredicate}${pipeFilter} ORDER BY LAST_LOAD_TIME ASC LIMIT ${Number(limit)}`,
394
+ warehouse: params.warehouse,
395
+ role: params.role,
396
+ database: params.database,
397
+ schema: params.schema
398
+ })).rows;
399
+ const events = rows.map((row) => ({
400
+ pipeName: readStringField(row, "PIPE_NAME", "pipe_name") ?? "",
401
+ filePath: readStringField(row, "FILE_NAME", "file_name") ?? "",
402
+ status: readStringField(row, "STATUS", "status") ?? "UNKNOWN",
403
+ loadTime: readStringField(row, "LAST_LOAD_TIME", "last_load_time"),
404
+ rowCount: readNumberField(row, "ROW_COUNT", "row_count") ?? null,
405
+ rowParsed: readNumberField(row, "ROW_PARSED", "row_parsed") ?? null,
406
+ errorLimit: readNumberField(row, "ERROR_LIMIT", "error_limit") ?? null,
407
+ errorsSeen: readNumberField(row, "ERRORS_SEEN", "errors_seen") ?? null,
408
+ firstErrorMessage: readStringField(row, "FIRST_ERROR_MESSAGE", "first_error_message") ?? null,
409
+ firstErrorLineNumber: readNumberField(row, "FIRST_ERROR_LINE_NUMBER", "first_error_line_number") ?? null,
410
+ firstErrorColumnName: readStringField(row, "FIRST_ERROR_COLUMN_NAME", "first_error_column_name") ?? null,
411
+ fileSize: readNumberField(row, "FILE_SIZE", "file_size") ?? null,
412
+ stageLocation: readStringField(row, "STAGE_LOCATION", "stage_location") ?? null,
413
+ row
414
+ }));
415
+ const last = rows[rows.length - 1];
416
+ return {
417
+ events,
418
+ highWaterValue: readStringField(last ?? {}, "LAST_LOAD_TIME", "last_load_time") ?? priorHighWater,
419
+ fetchedAt: isoNow()
420
+ };
421
+ },
422
+ idempotencyKey: (response) => `pipe-load:${response.highWaterValue ?? "initial"}:${response.events.length}`,
423
+ hasPayload: (response) => response.events.length > 0
424
+ });
425
+ }
426
+ const pipeLoadCompletedDefinition = buildPipeLoadDefinition("Snowflake Pipe Load Completed", "Emits successful Snowpipe loads polled from INFORMATION_SCHEMA.COPY_HISTORY.", `STATUS = 'LOADED'`);
427
+ const pipeLoadFailedDefinition = buildPipeLoadDefinition("Snowflake Pipe Load Failed", "Emits failed Snowpipe loads polled from INFORMATION_SCHEMA.COPY_HISTORY.", `STATUS IN ('LOAD_FAILED', 'PARTIALLY_LOADED')`);
428
+ function pipeLoadCompleted(input) {
429
+ return bindSnowflakePollingTrigger(pipeLoadCompletedDefinition, {
430
+ pipeName: input?.pipeName,
431
+ database: input?.database,
432
+ schema: input?.schema,
433
+ tableName: input?.tableName,
434
+ warehouse: input?.warehouse,
435
+ role: input?.role,
436
+ lookbackMinutes: input?.lookbackMinutes,
437
+ limit: input?.limit
438
+ }, input);
439
+ }
440
+ function pipeLoadFailed(input) {
441
+ return bindSnowflakePollingTrigger(pipeLoadFailedDefinition, {
442
+ pipeName: input?.pipeName,
443
+ database: input?.database,
444
+ schema: input?.schema,
445
+ tableName: input?.tableName,
446
+ warehouse: input?.warehouse,
447
+ role: input?.role,
448
+ lookbackMinutes: input?.lookbackMinutes,
449
+ limit: input?.limit
450
+ }, input);
451
+ }
452
+ /**
453
+ * Diff-trigger response envelope carried across polls. The emitted shape
454
+ * is `{ events: TEvent[], snapshot: Entry[], fetchedAt: string }`. The
455
+ * `snapshot` field is the identity+signature list that the NEXT poll
456
+ * will diff against — it is stored in `ctx.lastResponse` and replayed
457
+ * into the trigger automatically.
458
+ */
459
+ const diffSnapshotSchema = z.array(snowflakeDiffSnapshotEntrySchema);
460
+ function buildDiffBatchSchema(event) {
461
+ return z.object({
462
+ events: z.array(event),
463
+ snapshot: diffSnapshotSchema,
464
+ fetchedAt: z.string()
465
+ }).catchall(z.unknown());
466
+ }
467
+ /**
468
+ * Compute the diff between two snapshots. For `created`: entries in
469
+ * `current` whose identity is not in `previous`. For `dropped`: entries
470
+ * in `previous` whose identity is not in `current`. For `changed`:
471
+ * entries in `current` whose signature differs from `previous`'s
472
+ * (including entries that are newly present).
473
+ */
474
+ function diffEntries(current, previous, mode) {
475
+ if (previous === void 0) return [];
476
+ const prevMap = new Map(previous.map((p) => [p.identity, p]));
477
+ const currMap = new Map(current.map((c) => [c.identity, c]));
478
+ if (mode === "created") return current.filter((c) => !prevMap.has(c.identity));
479
+ if (mode === "dropped") return previous.filter((p) => !currMap.has(p.identity)).map((p) => ({
480
+ identity: p.identity,
481
+ ...p.signature !== void 0 ? { signature: p.signature } : {},
482
+ row: p.row ?? {}
483
+ }));
484
+ return current.filter((c) => {
485
+ const prev = prevMap.get(c.identity);
486
+ if (prev === void 0) return true;
487
+ return prev.signature !== c.signature;
488
+ });
489
+ }
490
+ function toSnapshotEntries(entries) {
491
+ return entries.map((e) => ({
492
+ identity: e.identity,
493
+ ...e.signature !== void 0 ? { signature: e.signature } : {}
494
+ }));
495
+ }
496
+ function sortedIdempotencyKey(prefix, events, getIdentity = (e) => e.identity ?? "") {
497
+ return `${prefix}:${events.map(getIdentity).slice().sort().join(",") || "empty"}`;
498
+ }
499
+ const lifecycleBatchSchema = buildDiffBatchSchema(snowflakeObjectLifecycleEventSchema);
500
+ function rowToLifecycleEntry(kind, row) {
501
+ const name = readStringField(row, "name", "NAME") ?? "";
502
+ const database = readStringField(row, "database_name", "DATABASE_NAME");
503
+ const schema = readStringField(row, "schema_name", "SCHEMA_NAME");
504
+ return {
505
+ identity: [
506
+ kind,
507
+ database ?? "",
508
+ schema ?? "",
509
+ name
510
+ ].join("::"),
511
+ row
512
+ };
513
+ }
514
+ function lifecycleEventFromEntry(kind, change, entry, detectedAt) {
515
+ const row = entry.row;
516
+ return {
517
+ kind,
518
+ name: readStringField(row, "name", "NAME") ?? "",
519
+ change,
520
+ identity: entry.identity,
521
+ owner: readStringField(row, "owner", "OWNER"),
522
+ createdOn: readStringField(row, "created_on", "CREATED_ON"),
523
+ detectedAt,
524
+ row
525
+ };
526
+ }
527
+ function buildLifecycleDefinition(config) {
528
+ return defineSnowflakePollingTrigger({
529
+ responseSchema: lifecycleBatchSchema,
530
+ defaultName: config.defaultName,
531
+ defaultDescription: config.defaultDescription,
532
+ defaultSchedule: "5m",
533
+ poll: async (params, credentials, ctx) => {
534
+ const current = (await executeSql(createSnowflakeClient(credentials), {
535
+ statement: config.buildStatement(params),
536
+ warehouse: params.warehouse,
537
+ role: params.role,
538
+ database: params.database,
539
+ schema: params.schema
540
+ })).rows.map((row) => rowToLifecycleEntry(config.kind, row));
541
+ const previousSnapshot = ctx.lastResponse?.snapshot;
542
+ const detectedAt = isoNow();
543
+ let diffed;
544
+ if (previousSnapshot === void 0) diffed = config.change === "created" && params.emitInitialSnapshot ? current : [];
545
+ else diffed = diffEntries(current, previousSnapshot, config.change);
546
+ return {
547
+ events: diffed.map((entry) => lifecycleEventFromEntry(config.kind, config.change, entry, detectedAt)),
548
+ snapshot: toSnapshotEntries(current),
549
+ fetchedAt: detectedAt
550
+ };
551
+ },
552
+ idempotencyKey: (response) => sortedIdempotencyKey(`${config.kind}:${config.change}`, response.events),
553
+ hasPayload: (response) => response.events.length > 0
554
+ });
555
+ }
556
+ function likeClause(like) {
557
+ return like ? ` LIKE ${quoteLiteral(like)}` : "";
558
+ }
559
+ function inScope(params) {
560
+ const db = params.in?.database ?? params.database;
561
+ const schema = params.in?.schema ?? params.schema;
562
+ if (db && schema) return ` IN SCHEMA ${quoteIdentifier(db)}.${quoteIdentifier(schema)}`;
563
+ if (db) return ` IN DATABASE ${quoteIdentifier(db)}`;
564
+ return "";
565
+ }
566
+ const userCreatedDefinition = buildLifecycleDefinition({
567
+ kind: "USER",
568
+ change: "created",
569
+ defaultName: "Snowflake User Created",
570
+ defaultDescription: "Emits every new user observed via SHOW USERS since the prior poll.",
571
+ buildStatement: (params) => `SHOW USERS${likeClause(params.like)}`
572
+ });
573
+ const userDroppedDefinition = buildLifecycleDefinition({
574
+ kind: "USER",
575
+ change: "dropped",
576
+ defaultName: "Snowflake User Dropped",
577
+ defaultDescription: "Emits every user that has disappeared from SHOW USERS since the prior poll.",
578
+ buildStatement: (params) => `SHOW USERS${likeClause(params.like)}`
579
+ });
580
+ function userCreated(input) {
581
+ return bindSnowflakePollingTrigger(userCreatedDefinition, {
582
+ database: input?.database,
583
+ schema: input?.schema,
584
+ warehouse: input?.warehouse,
585
+ role: input?.role,
586
+ like: input?.like,
587
+ emitInitialSnapshot: input?.emitInitialSnapshot
588
+ }, input);
589
+ }
590
+ function userDropped(input) {
591
+ return bindSnowflakePollingTrigger(userDroppedDefinition, {
592
+ database: input?.database,
593
+ schema: input?.schema,
594
+ warehouse: input?.warehouse,
595
+ role: input?.role,
596
+ like: input?.like
597
+ }, input);
598
+ }
599
+ const roleCreatedDefinition = buildLifecycleDefinition({
600
+ kind: "ROLE",
601
+ change: "created",
602
+ defaultName: "Snowflake Role Created",
603
+ defaultDescription: "Emits every new role observed via SHOW ROLES since the prior poll.",
604
+ buildStatement: (params) => `SHOW ROLES${likeClause(params.like)}`
605
+ });
606
+ const roleDroppedDefinition = buildLifecycleDefinition({
607
+ kind: "ROLE",
608
+ change: "dropped",
609
+ defaultName: "Snowflake Role Dropped",
610
+ defaultDescription: "Emits every role that has disappeared from SHOW ROLES since the prior poll.",
611
+ buildStatement: (params) => `SHOW ROLES${likeClause(params.like)}`
612
+ });
613
+ function roleCreated(input) {
614
+ return bindSnowflakePollingTrigger(roleCreatedDefinition, {
615
+ warehouse: input?.warehouse,
616
+ role: input?.role,
617
+ like: input?.like,
618
+ emitInitialSnapshot: input?.emitInitialSnapshot
619
+ }, input);
620
+ }
621
+ function roleDropped(input) {
622
+ return bindSnowflakePollingTrigger(roleDroppedDefinition, {
623
+ warehouse: input?.warehouse,
624
+ role: input?.role,
625
+ like: input?.like
626
+ }, input);
627
+ }
628
+ const databaseCreatedDefinition = buildLifecycleDefinition({
629
+ kind: "DATABASE",
630
+ change: "created",
631
+ defaultName: "Snowflake Database Created",
632
+ defaultDescription: "Emits every new database observed via SHOW DATABASES since the prior poll.",
633
+ buildStatement: (params) => `SHOW DATABASES${likeClause(params.like)}`
634
+ });
635
+ const databaseDroppedDefinition = buildLifecycleDefinition({
636
+ kind: "DATABASE",
637
+ change: "dropped",
638
+ defaultName: "Snowflake Database Dropped",
639
+ defaultDescription: "Emits every database that disappeared from SHOW DATABASES since the prior poll.",
640
+ buildStatement: (params) => `SHOW DATABASES${likeClause(params.like)}`
641
+ });
642
+ function databaseCreated(input) {
643
+ return bindSnowflakePollingTrigger(databaseCreatedDefinition, {
644
+ warehouse: input?.warehouse,
645
+ role: input?.role,
646
+ like: input?.like,
647
+ emitInitialSnapshot: input?.emitInitialSnapshot
648
+ }, input);
649
+ }
650
+ function databaseDropped(input) {
651
+ return bindSnowflakePollingTrigger(databaseDroppedDefinition, {
652
+ warehouse: input?.warehouse,
653
+ role: input?.role,
654
+ like: input?.like
655
+ }, input);
656
+ }
657
+ const tableCreatedDefinition = buildLifecycleDefinition({
658
+ kind: "TABLE",
659
+ change: "created",
660
+ defaultName: "Snowflake Table Created",
661
+ defaultDescription: "Emits every new table observed via SHOW TABLES since the prior poll.",
662
+ buildStatement: (params) => `SHOW TABLES${likeClause(params.like)}${inScope(params)}`
663
+ });
664
+ const tableDroppedDefinition = buildLifecycleDefinition({
665
+ kind: "TABLE",
666
+ change: "dropped",
667
+ defaultName: "Snowflake Table Dropped",
668
+ defaultDescription: "Emits every table that disappeared from SHOW TABLES since the prior poll.",
669
+ buildStatement: (params) => `SHOW TABLES${likeClause(params.like)}${inScope(params)}`
670
+ });
671
+ function tableCreated(input) {
672
+ return bindSnowflakePollingTrigger(tableCreatedDefinition, {
673
+ in: input?.in,
674
+ database: input?.database,
675
+ schema: input?.schema,
676
+ warehouse: input?.warehouse,
677
+ role: input?.role,
678
+ like: input?.like,
679
+ emitInitialSnapshot: input?.emitInitialSnapshot
680
+ }, input);
681
+ }
682
+ function tableDropped(input) {
683
+ return bindSnowflakePollingTrigger(tableDroppedDefinition, {
684
+ in: input?.in,
685
+ database: input?.database,
686
+ schema: input?.schema,
687
+ warehouse: input?.warehouse,
688
+ role: input?.role,
689
+ like: input?.like
690
+ }, input);
691
+ }
692
+ const shareChangedBatchSchema = buildDiffBatchSchema(shareChangedEventSchema);
693
+ const SHARE_SIGNATURE_FIELDS = [
694
+ "kind",
695
+ "owner",
696
+ "comment",
697
+ "to",
698
+ "database_name",
699
+ "listing_global_name"
700
+ ];
701
+ function signatureOf(row, fields) {
702
+ return fields.map((field) => {
703
+ const upper = field.toUpperCase();
704
+ const v = row[field] ?? row[upper];
705
+ return v === void 0 || v === null ? "" : String(v);
706
+ }).join("|");
707
+ }
708
+ const shareChangedDefinition = defineSnowflakePollingTrigger({
709
+ responseSchema: shareChangedBatchSchema,
710
+ defaultName: "Snowflake Share Changed",
711
+ defaultDescription: "Emits share create / drop / modify events observed via SHOW SHARES. Modifications diff against a signature of kind / owner / comment / accounts / listing.",
712
+ defaultSchedule: "5m",
713
+ poll: async (params, credentials, ctx) => {
714
+ const current = (await executeSql(createSnowflakeClient(credentials), {
715
+ statement: `SHOW SHARES${likeClause(params.like)}`,
716
+ warehouse: params.warehouse,
717
+ role: params.role
718
+ })).rows.map((row) => ({
719
+ identity: `SHARE::${readStringField(row, "name", "NAME") ?? ""}`,
720
+ signature: signatureOf(row, SHARE_SIGNATURE_FIELDS),
721
+ row
722
+ }));
723
+ const previousSnapshot = ctx.lastResponse?.snapshot;
724
+ const detectedAt = isoNow();
725
+ const prevMap = new Map((previousSnapshot ?? []).map((p) => [p.identity, p]));
726
+ const currMap = new Map(current.map((c) => [c.identity, c]));
727
+ const events = [];
728
+ if (previousSnapshot !== void 0) {
729
+ for (const entry of current) {
730
+ const prev = prevMap.get(entry.identity);
731
+ if (prev === void 0) events.push({
732
+ name: readStringField(entry.row, "name", "NAME") ?? "",
733
+ kind: readStringField(entry.row, "kind", "KIND"),
734
+ change: "created",
735
+ identity: entry.identity,
736
+ detectedAt,
737
+ currentRow: entry.row
738
+ });
739
+ else if (prev.signature !== entry.signature) events.push({
740
+ name: readStringField(entry.row, "name", "NAME") ?? "",
741
+ kind: readStringField(entry.row, "kind", "KIND"),
742
+ change: "modified",
743
+ identity: entry.identity,
744
+ detectedAt,
745
+ currentRow: entry.row
746
+ });
747
+ }
748
+ for (const prev of previousSnapshot) if (!currMap.has(prev.identity)) events.push({
749
+ name: prev.identity.replace(/^SHARE::/u, ""),
750
+ change: "dropped",
751
+ identity: prev.identity,
752
+ detectedAt
753
+ });
754
+ } else if (params.emitInitialSnapshot) for (const entry of current) events.push({
755
+ name: readStringField(entry.row, "name", "NAME") ?? "",
756
+ kind: readStringField(entry.row, "kind", "KIND"),
757
+ change: "created",
758
+ identity: entry.identity,
759
+ detectedAt,
760
+ currentRow: entry.row
761
+ });
762
+ return {
763
+ events,
764
+ snapshot: toSnapshotEntries(current),
765
+ fetchedAt: detectedAt
766
+ };
767
+ },
768
+ idempotencyKey: (response) => sortedIdempotencyKey("share-changed", response.events),
769
+ hasPayload: (response) => response.events.length > 0
770
+ });
771
+ function shareChanged(input) {
772
+ return bindSnowflakePollingTrigger(shareChangedDefinition, {
773
+ warehouse: input?.warehouse,
774
+ role: input?.role,
775
+ like: input?.like,
776
+ emitInitialSnapshot: input?.emitInitialSnapshot
777
+ }, input);
778
+ }
779
+ const grantsChangedBatchSchema = buildDiffBatchSchema(grantChangedEventSchema);
780
+ function grantIdentity(row) {
781
+ return `${readStringField(row, "privilege", "PRIVILEGE") ?? ""}::${readStringField(row, "granted_on", "GRANTED_ON") ?? ""}::${readStringField(row, "name", "NAME") ?? ""}::${readStringField(row, "granted_to", "GRANTED_TO") ?? ""}::${readStringField(row, "grantee_name", "GRANTEE_NAME") ?? ""}`;
782
+ }
783
+ function grantStatement(params) {
784
+ if ("role" in params.grantee) return `SHOW GRANTS TO ROLE ${quoteIdentifier(params.grantee.role)}`;
785
+ if ("user" in params.grantee) return `SHOW GRANTS TO USER ${quoteIdentifier(params.grantee.user)}`;
786
+ return `SHOW GRANTS TO SHARE ${quoteIdentifier(params.grantee.share)}`;
787
+ }
788
+ const grantsChangedDefinition = defineSnowflakePollingTrigger({
789
+ responseSchema: grantsChangedBatchSchema,
790
+ defaultName: "Snowflake Grants Changed",
791
+ defaultDescription: "Emits grant / revoke events observed via SHOW GRANTS TO <ROLE|USER|SHARE>. Diffs against the prior poll by (privilege, granted_on, name, grantee).",
792
+ defaultSchedule: "5m",
793
+ poll: async (params, credentials, ctx) => {
794
+ const current = (await executeSql(createSnowflakeClient(credentials), {
795
+ statement: grantStatement(params),
796
+ warehouse: params.warehouse,
797
+ role: params.sessionRole
798
+ })).rows.map((row) => ({
799
+ identity: grantIdentity(row),
800
+ row
801
+ }));
802
+ const previousSnapshot = ctx.lastResponse?.snapshot;
803
+ const detectedAt = isoNow();
804
+ const events = [];
805
+ if (previousSnapshot !== void 0) {
806
+ const prevSet = new Set(previousSnapshot.map((p) => p.identity));
807
+ const currSet = new Set(current.map((c) => c.identity));
808
+ for (const entry of current) if (!prevSet.has(entry.identity)) events.push(buildGrantEvent(entry.row, "granted", entry.identity, detectedAt));
809
+ for (const prev of previousSnapshot) if (!currSet.has(prev.identity)) {
810
+ const [privilege, grantedOn, name, granteeType, granteeName] = prev.identity.split("::");
811
+ events.push({
812
+ privilege: privilege ?? "",
813
+ grantedOn: grantedOn ?? "",
814
+ objectName: name ?? "",
815
+ granteeType,
816
+ granteeName: granteeName ?? "",
817
+ change: "revoked",
818
+ identity: prev.identity,
819
+ detectedAt,
820
+ row: {}
821
+ });
822
+ }
823
+ }
824
+ return {
825
+ events,
826
+ snapshot: toSnapshotEntries(current),
827
+ fetchedAt: detectedAt
828
+ };
829
+ },
830
+ idempotencyKey: (response) => sortedIdempotencyKey("grants-changed", response.events),
831
+ hasPayload: (response) => response.events.length > 0
832
+ });
833
+ function buildGrantEvent(row, change, identity, detectedAt) {
834
+ return {
835
+ privilege: readStringField(row, "privilege", "PRIVILEGE") ?? "",
836
+ grantedOn: readStringField(row, "granted_on", "GRANTED_ON") ?? "",
837
+ objectName: readStringField(row, "name", "NAME") ?? "",
838
+ granteeName: readStringField(row, "grantee_name", "GRANTEE_NAME") ?? "",
839
+ granteeType: readStringField(row, "granted_to", "GRANTED_TO"),
840
+ change,
841
+ identity,
842
+ detectedAt,
843
+ row
844
+ };
845
+ }
846
+ function grantsChanged(input) {
847
+ return bindSnowflakePollingTrigger(grantsChangedDefinition, input, input);
848
+ }
849
+ const warehouseCreditsBatchSchema = z.object({
850
+ events: z.array(warehouseCreditsEventSchema),
851
+ highWaterValue: z.string().nullable(),
852
+ fetchedAt: z.string()
853
+ }).catchall(z.unknown());
854
+ const warehouseQueueBatchSchema = z.object({
855
+ events: z.array(warehouseQueueEventSchema),
856
+ highWaterValue: z.string().nullable(),
857
+ fetchedAt: z.string()
858
+ }).catchall(z.unknown());
859
+ const warehouseCreditsUsageDefinition = defineSnowflakePollingTrigger({
860
+ responseSchema: warehouseCreditsBatchSchema,
861
+ defaultName: "Snowflake Warehouse Credits Usage",
862
+ defaultDescription: "Tails INFORMATION_SCHEMA.WAREHOUSE_METERING_HISTORY and emits per-warehouse credit buckets above an optional minimum.",
863
+ defaultSchedule: "5m",
864
+ poll: async (params, credentials, ctx) => {
865
+ const client = createSnowflakeClient(credentials);
866
+ const limit = params.limit ?? 200;
867
+ const priorHighWater = ctx.lastResponse?.highWaterValue ?? null;
868
+ const lookbackMinutes = params.lookbackMinutes ?? 60;
869
+ const args = [`DATE_RANGE_START => TO_TIMESTAMP_LTZ(${quoteLiteral(priorHighWater ?? (/* @__PURE__ */ new Date(Date.now() - lookbackMinutes * 6e4)).toISOString())})`];
870
+ if (params.warehouseName) args.push(`WAREHOUSE_NAME => ${quoteLiteral(params.warehouseName)}`);
871
+ const clauses = [];
872
+ if (params.minimum !== void 0) clauses.push(`CREDITS_USED >= ${Number(params.minimum)}`);
873
+ const where = clauses.length > 0 ? ` WHERE ${clauses.join(" AND ")}` : "";
874
+ const rows = (await executeSql(client, {
875
+ statement: `SELECT * FROM TABLE(INFORMATION_SCHEMA.WAREHOUSE_METERING_HISTORY(${args.join(", ")}))${where} ORDER BY END_TIME ASC LIMIT ${Number(limit)}`,
876
+ warehouse: params.warehouse,
877
+ role: params.role,
878
+ database: params.database,
879
+ schema: params.schema
880
+ })).rows;
881
+ const events = rows.map((row) => ({
882
+ warehouseName: readStringField(row, "WAREHOUSE_NAME", "warehouse_name") ?? "",
883
+ startTime: readStringField(row, "START_TIME", "start_time"),
884
+ endTime: readStringField(row, "END_TIME", "end_time"),
885
+ creditsUsed: readNumberField(row, "CREDITS_USED", "credits_used") ?? null,
886
+ creditsUsedCompute: readNumberField(row, "CREDITS_USED_COMPUTE", "credits_used_compute") ?? null,
887
+ creditsUsedCloudServices: readNumberField(row, "CREDITS_USED_CLOUD_SERVICES", "credits_used_cloud_services") ?? null,
888
+ row
889
+ }));
890
+ const last = rows[rows.length - 1];
891
+ return {
892
+ events,
893
+ highWaterValue: readStringField(last ?? {}, "END_TIME", "end_time") ?? priorHighWater,
894
+ fetchedAt: isoNow()
895
+ };
896
+ },
897
+ idempotencyKey: (response) => `wh-credits:${response.highWaterValue ?? "initial"}:${response.events.length}`,
898
+ hasPayload: (response) => response.events.length > 0
899
+ });
900
+ const warehouseQueueDepthDefinition = defineSnowflakePollingTrigger({
901
+ responseSchema: warehouseQueueBatchSchema,
902
+ defaultName: "Snowflake Warehouse Queue Depth",
903
+ defaultDescription: "Tails INFORMATION_SCHEMA.WAREHOUSE_LOAD_HISTORY and emits per-interval queue depth above an optional minimum.",
904
+ defaultSchedule: "5m",
905
+ poll: async (params, credentials, ctx) => {
906
+ const client = createSnowflakeClient(credentials);
907
+ const limit = params.limit ?? 200;
908
+ const priorHighWater = ctx.lastResponse?.highWaterValue ?? null;
909
+ const lookbackMinutes = params.lookbackMinutes ?? 60;
910
+ const args = [`DATE_RANGE_START => TO_TIMESTAMP_LTZ(${quoteLiteral(priorHighWater ?? (/* @__PURE__ */ new Date(Date.now() - lookbackMinutes * 6e4)).toISOString())})`];
911
+ if (params.warehouseName) args.push(`WAREHOUSE_NAME => ${quoteLiteral(params.warehouseName)}`);
912
+ const clauses = [];
913
+ if (params.minimum !== void 0) clauses.push(`AVG_QUEUED_LOAD >= ${Number(params.minimum)}`);
914
+ const where = clauses.length > 0 ? ` WHERE ${clauses.join(" AND ")}` : "";
915
+ const rows = (await executeSql(client, {
916
+ statement: `SELECT * FROM TABLE(INFORMATION_SCHEMA.WAREHOUSE_LOAD_HISTORY(${args.join(", ")}))${where} ORDER BY END_TIME ASC LIMIT ${Number(limit)}`,
917
+ warehouse: params.warehouse,
918
+ role: params.role,
919
+ database: params.database,
920
+ schema: params.schema
921
+ })).rows;
922
+ const events = rows.map((row) => ({
923
+ warehouseName: readStringField(row, "WAREHOUSE_NAME", "warehouse_name") ?? "",
924
+ startTime: readStringField(row, "START_TIME", "start_time"),
925
+ endTime: readStringField(row, "END_TIME", "end_time"),
926
+ avgRunning: readNumberField(row, "AVG_RUNNING", "avg_running") ?? null,
927
+ avgQueuedLoad: readNumberField(row, "AVG_QUEUED_LOAD", "avg_queued_load") ?? null,
928
+ avgQueuedProvisioning: readNumberField(row, "AVG_QUEUED_PROVISIONING", "avg_queued_provisioning") ?? null,
929
+ avgBlocked: readNumberField(row, "AVG_BLOCKED", "avg_blocked") ?? null,
930
+ row
931
+ }));
932
+ const last = rows[rows.length - 1];
933
+ return {
934
+ events,
935
+ highWaterValue: readStringField(last ?? {}, "END_TIME", "end_time") ?? priorHighWater,
936
+ fetchedAt: isoNow()
937
+ };
938
+ },
939
+ idempotencyKey: (response) => `wh-queue:${response.highWaterValue ?? "initial"}:${response.events.length}`,
940
+ hasPayload: (response) => response.events.length > 0
941
+ });
942
+ function warehouseCreditsUsage(input) {
943
+ return bindSnowflakePollingTrigger(warehouseCreditsUsageDefinition, input ?? {}, input);
944
+ }
945
+ function warehouseQueueDepth(input) {
946
+ return bindSnowflakePollingTrigger(warehouseQueueDepthDefinition, input ?? {}, input);
947
+ }
948
+ const statusPageIncidentBatchSchema = buildDiffBatchSchema(statusPageIncidentEventSchema);
949
+ const statusPageComponentChangeBatchSchema = buildDiffBatchSchema(statusPageComponentChangeEventSchema);
950
+ function incidentToEntry(incident) {
951
+ const row = incident;
952
+ return {
953
+ identity: `INCIDENT::${incident.id}`,
954
+ signature: `${incident.status}|${incident.updated_at}`,
955
+ row
956
+ };
957
+ }
958
+ function componentToEntry(component) {
959
+ const row = component;
960
+ return {
961
+ identity: `COMPONENT::${component.id}`,
962
+ signature: component.status,
963
+ row
964
+ };
965
+ }
966
+ function extractStatusPageOptions(params) {
967
+ const opts = {};
968
+ if (params.baseUrl !== void 0) opts.baseUrl = params.baseUrl;
969
+ if (params.fetchImpl !== void 0) opts.fetchImpl = params.fetchImpl;
970
+ if (params.timeoutMs !== void 0) opts.timeoutMs = params.timeoutMs;
971
+ if (params.headers !== void 0) opts.headers = params.headers;
972
+ if (params.signal !== void 0) opts.signal = params.signal;
973
+ return opts;
974
+ }
975
+ const statusPageIncidentDefinition = {
976
+ responseSchema: statusPageIncidentBatchSchema,
977
+ defaultName: "Snowflake Status Page Incident",
978
+ defaultDescription: "Polls https://status.snowflake.com/api/v2/incidents/unresolved.json and emits newly-appearing or status-changed incidents.",
979
+ defaultSchedule: "5m",
980
+ poll: async (params, _credentials, ctx) => {
981
+ const current = (await getUnresolvedIncidents(extractStatusPageOptions(params))).incidents.map(incidentToEntry);
982
+ const previousSnapshot = ctx.lastResponse?.snapshot;
983
+ const detectedAt = isoNow();
984
+ const events = [];
985
+ const prevMap = new Map((previousSnapshot ?? []).map((p) => [p.identity, p]));
986
+ const emitAll = previousSnapshot === void 0 && params.emitInitialSnapshot;
987
+ if (previousSnapshot === void 0 && !emitAll) {} else for (const entry of current) {
988
+ const prev = prevMap.get(entry.identity);
989
+ if (prev === void 0 || prev.signature !== entry.signature) {
990
+ const incident = entry.row;
991
+ events.push({
992
+ id: incident.id,
993
+ name: incident.name,
994
+ status: incident.status,
995
+ impact: incident.impact,
996
+ createdAt: incident.created_at,
997
+ updatedAt: incident.updated_at,
998
+ resolvedAt: incident.resolved_at ?? null,
999
+ shortlink: incident.shortlink,
1000
+ detectedAt,
1001
+ raw: entry.row
1002
+ });
1003
+ }
1004
+ }
1005
+ return {
1006
+ events,
1007
+ snapshot: toSnapshotEntries(current),
1008
+ fetchedAt: detectedAt
1009
+ };
1010
+ },
1011
+ idempotencyKey: (response) => sortedIdempotencyKey("status-incident", response.events, (e) => `${e.id}:${e.updatedAt ?? ""}`),
1012
+ hasPayload: (response) => response.events.length > 0
1013
+ };
1014
+ const statusPageComponentChangeDefinition = {
1015
+ responseSchema: statusPageComponentChangeBatchSchema,
1016
+ defaultName: "Snowflake Status Page Component Changed",
1017
+ defaultDescription: "Polls https://status.snowflake.com/api/v2/summary.json and emits a change event when any component transitions status.",
1018
+ defaultSchedule: "2m",
1019
+ poll: async (params, _credentials, ctx) => {
1020
+ const current = ((await getStatusSummary(extractStatusPageOptions(params))).components ?? []).map(componentToEntry);
1021
+ const previousSnapshot = ctx.lastResponse?.snapshot;
1022
+ const detectedAt = isoNow();
1023
+ const events = [];
1024
+ if (previousSnapshot !== void 0) {
1025
+ const prevMap = new Map(previousSnapshot.map((p) => [p.identity, p]));
1026
+ for (const entry of current) {
1027
+ const prevStatus = prevMap.get(entry.identity)?.signature ?? null;
1028
+ if (prevStatus !== entry.signature) {
1029
+ const component = entry.row;
1030
+ events.push({
1031
+ id: component.id,
1032
+ name: component.name,
1033
+ previousStatus: prevStatus,
1034
+ currentStatus: component.status,
1035
+ description: component.description ?? void 0,
1036
+ changedAt: component.updated_at ?? detectedAt,
1037
+ detectedAt,
1038
+ raw: entry.row
1039
+ });
1040
+ }
1041
+ }
1042
+ }
1043
+ return {
1044
+ events,
1045
+ snapshot: toSnapshotEntries(current),
1046
+ fetchedAt: detectedAt
1047
+ };
1048
+ },
1049
+ idempotencyKey: (response) => sortedIdempotencyKey("status-component", response.events, (e) => `${e.id}:${e.currentStatus}`),
1050
+ hasPayload: (response) => response.events.length > 0
1051
+ };
1052
+ /**
1053
+ * Status-page pollers don't need Snowflake credentials, but they still
1054
+ * flow through the Snowflake binding factory so they show up in the
1055
+ * polling manifest alongside the rest. Credentials are declared but
1056
+ * unused — the poll callback ignores `ctx.credentials.snowflake`.
1057
+ */
1058
+ function statusPageIncident(input) {
1059
+ return bindSnowflakePollingTrigger(statusPageIncidentDefinition, input ?? {}, input);
1060
+ }
1061
+ function statusPageComponentChanged(input) {
1062
+ return bindSnowflakePollingTrigger(statusPageComponentChangeDefinition, input ?? {}, input);
1063
+ }
1064
+ function resolveWebhookPath(override, fallback) {
1065
+ return override ?? fallback;
1066
+ }
1067
+ function isoNowUtc() {
1068
+ return (/* @__PURE__ */ new Date()).toISOString();
1069
+ }
1070
+ /**
1071
+ * The public SNS payload schema accepted by this trigger. SNS delivers
1072
+ * an application/json body; we validate it before signature-checking.
1073
+ */
1074
+ const snsWebhookPayloadSchema = snsNotificationSchema;
1075
+ function s3RecordsToBatch(snsMessage, rawBody) {
1076
+ const receivedAt = isoNowUtc();
1077
+ return {
1078
+ events: extractS3Records(snsMessage).map((record) => {
1079
+ const bucket = record.s3?.bucket?.name ?? "";
1080
+ const key = record.s3?.object?.key ?? "";
1081
+ return {
1082
+ provider: "aws-s3",
1083
+ eventType: record.eventName ?? "",
1084
+ eventTime: record.eventTime ?? receivedAt,
1085
+ bucket,
1086
+ key,
1087
+ size: typeof record.s3?.object?.size === "number" ? record.s3.object.size : null,
1088
+ etag: record.s3?.object?.eTag ?? null,
1089
+ region: record.awsRegion ?? null,
1090
+ sourceIp: record.requestParameters?.sourceIPAddress ?? null,
1091
+ messageId: snsMessage.MessageId,
1092
+ receivedAt,
1093
+ raw: record
1094
+ };
1095
+ }),
1096
+ provider: "aws-s3",
1097
+ receivedAt,
1098
+ messageId: snsMessage.MessageId,
1099
+ topic: snsMessage.TopicArn,
1100
+ raw: JSON.parse(rawBody)
1101
+ };
1102
+ }
1103
+ /**
1104
+ * `triggers.autoIngestS3Sns` — receives AWS SNS HTTPS deliveries,
1105
+ * verifies the SNS signature against the signing certificate, and
1106
+ * emits normalized S3 object events. Subscription-confirmation
1107
+ * messages are surfaced via a sentinel event of type
1108
+ * `SubscriptionConfirmation` so operators can auto-confirm in
1109
+ * downstream logic.
1110
+ */
1111
+ function autoIngestS3Sns(input) {
1112
+ const { verifierOptions, path, ...rest } = input ?? {};
1113
+ return createWebhookTriggerBindingFactory({
1114
+ defaultName: "Snowflake Auto-Ingest — AWS SNS",
1115
+ defaultDescription: "Receives Snowpipe auto-ingest notifications via AWS SNS HTTPS delivery. Verifies SNS signature against the SigningCertURL, pins TopicArn, and normalizes S3 object events.",
1116
+ path: resolveWebhookPath(path, "/snowflake/auto-ingest/s3-sns"),
1117
+ method: "POST",
1118
+ payload: snsWebhookPayloadSchema,
1119
+ credentialSets: [snowflake, snowflakeS3SnsWebhookCredentialSet],
1120
+ response: { successStatus: 200 },
1121
+ verify: async (request, ctx) => {
1122
+ const creds = ctx.credentials["snowflake-s3-sns-webhook"];
1123
+ try {
1124
+ await verifySnsMessage(request.rawBody, {
1125
+ ...creds.expectedTopicArn ? { expectedTopicArn: creds.expectedTopicArn } : {},
1126
+ ...creds.allowedSigningCertHostPattern ? { allowedSigningCertHostPattern: creds.allowedSigningCertHostPattern } : {},
1127
+ ...verifierOptions ?? {}
1128
+ });
1129
+ } catch (err) {
1130
+ if (err instanceof SnowflakeWebhookVerificationError) throw err;
1131
+ throw new SnowflakeWebhookVerificationError({
1132
+ message: "Unexpected error verifying SNS signature.",
1133
+ reason: "unknown",
1134
+ provider: "aws-s3",
1135
+ cause: err
1136
+ });
1137
+ }
1138
+ },
1139
+ mapPayload: (payload) => {
1140
+ if (payload.Type !== "Notification") return null;
1141
+ const batch = s3RecordsToBatch(payload, JSON.stringify(payload));
1142
+ return batch.events.length > 0 ? batch : null;
1143
+ },
1144
+ idempotencyKey: (_payload, mapped) => mapped ? `sns:${mapped.messageId ?? "no-id"}` : void 0
1145
+ })(rest);
1146
+ }
1147
+ const gcsWebhookPayloadSchema = gcsPubSubPushSchema;
1148
+ function gcsPushToBatch(push) {
1149
+ const receivedAt = isoNowUtc();
1150
+ const payload = extractGcsObjectPayload(push);
1151
+ const attributes = push.message.attributes ?? {};
1152
+ if (!payload || !payload.bucket || !payload.name) return {
1153
+ events: [],
1154
+ provider: "gcp-gcs",
1155
+ receivedAt,
1156
+ messageId: push.message.messageId ?? null,
1157
+ topic: push.subscription ?? null,
1158
+ raw: push
1159
+ };
1160
+ const eventType = attributes.eventType ?? "OBJECT_FINALIZE";
1161
+ const sizeNumber = typeof payload.size === "number" ? payload.size : typeof payload.size === "string" ? Number(payload.size) : null;
1162
+ return {
1163
+ events: [{
1164
+ provider: "gcp-gcs",
1165
+ eventType,
1166
+ eventTime: payload.updated ?? payload.timeCreated ?? receivedAt,
1167
+ bucket: payload.bucket,
1168
+ key: payload.name,
1169
+ size: sizeNumber !== null && Number.isFinite(sizeNumber) ? sizeNumber : null,
1170
+ etag: payload.etag ?? null,
1171
+ region: null,
1172
+ sourceIp: null,
1173
+ messageId: push.message.messageId ?? null,
1174
+ receivedAt,
1175
+ raw: payload
1176
+ }],
1177
+ provider: "gcp-gcs",
1178
+ receivedAt,
1179
+ messageId: push.message.messageId ?? null,
1180
+ topic: push.subscription ?? null,
1181
+ raw: push
1182
+ };
1183
+ }
1184
+ /**
1185
+ * `triggers.autoIngestGcsPubSub` — receives GCP Pub/Sub push deliveries,
1186
+ * verifies the Google-signed OIDC JWT against Google's JWKS, and
1187
+ * emits normalized GCS object events extracted from the Pub/Sub
1188
+ * message body.
1189
+ */
1190
+ function autoIngestGcsPubSub(input) {
1191
+ const { verifierOptions, path, ...rest } = input ?? {};
1192
+ return createWebhookTriggerBindingFactory({
1193
+ defaultName: "Snowflake Auto-Ingest — GCS Pub/Sub",
1194
+ defaultDescription: "Receives Snowpipe auto-ingest notifications via GCP Pub/Sub push. Verifies the Google-signed OIDC JWT in the Authorization header and normalizes GCS object events.",
1195
+ path: resolveWebhookPath(path, "/snowflake/auto-ingest/gcs-pubsub"),
1196
+ method: "POST",
1197
+ payload: gcsWebhookPayloadSchema,
1198
+ credentialSets: [snowflake, snowflakeGcsPubSubWebhookCredentialSet],
1199
+ response: { successStatus: 204 },
1200
+ verify: async (request, ctx) => {
1201
+ const creds = ctx.credentials["snowflake-gcs-pubsub-webhook"];
1202
+ try {
1203
+ parsePubSubPush(request.rawBody);
1204
+ await verifyPubSubPushAuthorization(request.headers.authorization, {
1205
+ expectedAudience: creds.expectedAudience,
1206
+ expectedServiceAccount: creds.expectedServiceAccount,
1207
+ ...creds.issuer ? { issuer: creds.issuer } : {},
1208
+ ...creds.jwksUri ? { jwksUri: creds.jwksUri } : {},
1209
+ ...verifierOptions ?? {}
1210
+ });
1211
+ } catch (err) {
1212
+ if (err instanceof SnowflakeWebhookVerificationError) throw err;
1213
+ throw new SnowflakeWebhookVerificationError({
1214
+ message: "Unexpected error verifying Pub/Sub push.",
1215
+ reason: "unknown",
1216
+ provider: "gcp-gcs",
1217
+ cause: err
1218
+ });
1219
+ }
1220
+ },
1221
+ mapPayload: (payload) => {
1222
+ const batch = gcsPushToBatch(payload);
1223
+ return batch.events.length > 0 ? batch : null;
1224
+ },
1225
+ idempotencyKey: (_payload, mapped) => mapped ? `pubsub:${mapped.messageId ?? "no-id"}` : void 0
1226
+ })(rest);
1227
+ }
1228
+ /**
1229
+ * Event Grid wire format is `AzureEventGridEvent[]`. Keystroke's
1230
+ * webhook primitive validates a single payload schema — so we accept
1231
+ * either a single event or a wrapper object. The runtime reparses the
1232
+ * raw body to preserve the array shape.
1233
+ */
1234
+ const azureWebhookPayloadSchema = z.union([z.array(azureEventGridEventSchema), azureEventGridEventSchema]);
1235
+ function azureBatchToBatch(events, rawBody) {
1236
+ const receivedAt = isoNowUtc();
1237
+ const mapped = [];
1238
+ let topic = null;
1239
+ for (const event of events) {
1240
+ if (event.eventType === EVENT_GRID_VALIDATION_EVENT_TYPE) continue;
1241
+ topic = event.topic ?? topic;
1242
+ const loc = parseAzureBlobLocation(event);
1243
+ const data = event.data;
1244
+ mapped.push({
1245
+ provider: "azure-blob",
1246
+ eventType: event.eventType,
1247
+ eventTime: event.eventTime,
1248
+ bucket: loc.bucket,
1249
+ key: loc.key,
1250
+ size: typeof data.contentLength === "number" ? data.contentLength : null,
1251
+ etag: typeof data.eTag === "string" ? data.eTag : null,
1252
+ region: null,
1253
+ sourceIp: null,
1254
+ messageId: event.id,
1255
+ receivedAt,
1256
+ raw: event
1257
+ });
1258
+ }
1259
+ return {
1260
+ events: mapped,
1261
+ provider: "azure-blob",
1262
+ receivedAt,
1263
+ messageId: events[0]?.id ?? null,
1264
+ topic,
1265
+ raw: JSON.parse(rawBody)
1266
+ };
1267
+ }
1268
+ /**
1269
+ * `triggers.autoIngestAzureEventGrid` — receives Azure Event Grid
1270
+ * deliveries. Validates shared-secret header (if configured), parses
1271
+ * the batch, and emits normalized Azure Blob object events. The
1272
+ * synchronous subscription-validation handshake is NOT supported
1273
+ * through the Keystroke static-response primitive; operators must
1274
+ * use manual validation URLs (see IMPLEMENTATION_NOTES.md).
1275
+ */
1276
+ function autoIngestAzureEventGrid(input) {
1277
+ const { verifierOptions, path, ...rest } = input ?? {};
1278
+ return createWebhookTriggerBindingFactory({
1279
+ defaultName: "Snowflake Auto-Ingest — Azure Event Grid",
1280
+ defaultDescription: "Receives Snowpipe auto-ingest notifications via Azure Event Grid. Validates shared-secret header, drops subscription-validation events (manual handshake required), and normalizes Azure Blob object events.",
1281
+ path: resolveWebhookPath(path, "/snowflake/auto-ingest/azure-eventgrid"),
1282
+ method: "POST",
1283
+ payload: azureWebhookPayloadSchema,
1284
+ credentialSets: [snowflake, snowflakeAzureEventGridWebhookCredentialSet],
1285
+ response: { successStatus: 200 },
1286
+ verify: async (request, ctx) => {
1287
+ const creds = ctx.credentials["snowflake-azure-eventgrid-webhook"];
1288
+ try {
1289
+ verifyEventGridRequest({
1290
+ rawBody: request.rawBody,
1291
+ headers: request.headers
1292
+ }, {
1293
+ ...creds.sharedSecret ? { sharedSecret: creds.sharedSecret } : {},
1294
+ ...creds.sharedSecretHeader ? { sharedSecretHeader: creds.sharedSecretHeader } : {},
1295
+ ...creds.expectedTopicArn ? { expectedTopic: creds.expectedTopicArn } : {},
1296
+ ...verifierOptions ?? {}
1297
+ });
1298
+ } catch (err) {
1299
+ if (err instanceof SnowflakeWebhookVerificationError) throw err;
1300
+ throw new SnowflakeWebhookVerificationError({
1301
+ message: "Unexpected error verifying Event Grid request.",
1302
+ reason: "unknown",
1303
+ provider: "azure-blob",
1304
+ cause: err
1305
+ });
1306
+ }
1307
+ },
1308
+ mapPayload: (payload) => {
1309
+ const events = Array.isArray(payload) ? payload : [payload];
1310
+ const batch = azureBatchToBatch(events, JSON.stringify(events));
1311
+ return batch.events.length > 0 ? batch : null;
1312
+ },
1313
+ idempotencyKey: (_payload, mapped) => mapped ? `eventgrid:${mapped.messageId ?? "no-id"}` : void 0
1314
+ })(rest);
1315
+ }
1316
+ /**
1317
+ * Convenience namespace grouping every polling trigger in this package.
1318
+ * Equivalent to calling the individual factory functions directly.
1319
+ */
1320
+ const polling = Object.freeze({
1321
+ streamHasNewRows,
1322
+ streamAppendOnly,
1323
+ scheduledSql,
1324
+ scheduledDelta,
1325
+ taskRunCompleted,
1326
+ statementCompleted,
1327
+ pipeLoadCompleted,
1328
+ pipeLoadFailed,
1329
+ userCreated,
1330
+ userDropped,
1331
+ roleCreated,
1332
+ roleDropped,
1333
+ databaseCreated,
1334
+ databaseDropped,
1335
+ tableCreated,
1336
+ tableDropped,
1337
+ shareChanged,
1338
+ grantsChanged,
1339
+ warehouseCreditsUsage,
1340
+ warehouseQueueDepth,
1341
+ statusPageIncident,
1342
+ statusPageComponentChanged
1343
+ });
1344
+ /**
1345
+ * Convenience namespace grouping every auto-ingest webhook trigger.
1346
+ */
1347
+ const webhooks = Object.freeze({
1348
+ autoIngestS3Sns,
1349
+ autoIngestGcsPubSub,
1350
+ autoIngestAzureEventGrid
1351
+ });
1352
+ /**
1353
+ * Nested `autoIngest` namespace per the spec (§7 PLAN.md) so callers
1354
+ * can reach the three webhook triggers via `triggers.autoIngest.<cloud>`.
1355
+ */
1356
+ const autoIngest = Object.freeze({
1357
+ s3Sns: autoIngestS3Sns,
1358
+ gcsPubSub: autoIngestGcsPubSub,
1359
+ azureEventGrid: autoIngestAzureEventGrid
1360
+ });
1361
+
1362
+ //#endregion
1363
+ export { autoIngest, autoIngestAzureEventGrid, autoIngestGcsPubSub, autoIngestS3Sns, databaseCreated, databaseDropped, grantsChanged, pipeLoadCompleted, pipeLoadFailed, polling, roleCreated, roleDropped, scheduledDelta, scheduledSql, shareChanged, statementCompleted, statusPageComponentChanged, statusPageIncident, streamAppendOnly, streamHasNewRows, tableCreated, tableDropped, taskRunCompleted, userCreated, userDropped, warehouseCreditsUsage, warehouseQueueDepth, webhooks };