@keystrokehq/snowflake 0.0.1 → 0.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/dist/_official/index.d.mts +1 -1
- package/dist/_official/index.mjs +1 -1
- package/dist/client.d.mts +1 -1
- package/dist/client.mjs +2 -2
- package/dist/connection.d.mts +1 -1
- package/dist/connection.mjs +1 -1
- package/dist/{integration-Dptv8L0L.mjs → integration-BVS1DWxb.mjs} +5 -5
- package/dist/{integration-CCWMSTlO.d.mts → integration-CQNVr6NY.d.mts} +1 -1
- package/dist/triggers.d.mts +6 -205
- package/dist/triggers.mjs +49 -360
- package/package.json +21 -19
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Buster
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { c as snowflakeOfficialIntegration, i as snowflake, s as snowflakeBundle } from "../integration-
|
|
1
|
+
import { c as snowflakeOfficialIntegration, i as snowflake, s as snowflakeBundle } from "../integration-CQNVr6NY.mjs";
|
|
2
2
|
export { snowflake, snowflakeBundle, snowflakeOfficialIntegration };
|
package/dist/_official/index.mjs
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { a as snowflakeBundle, n as snowflake, o as snowflakeOfficialIntegration } from "../integration-
|
|
1
|
+
import { a as snowflakeBundle, n as snowflake, o as snowflakeOfficialIntegration } from "../integration-BVS1DWxb.mjs";
|
|
2
2
|
|
|
3
3
|
export { snowflake, snowflakeBundle, snowflakeOfficialIntegration };
|
package/dist/client.d.mts
CHANGED
package/dist/client.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as parseSnowflakeCredentials } from "./integration-
|
|
1
|
+
import { t as parseSnowflakeCredentials } from "./integration-BVS1DWxb.mjs";
|
|
2
2
|
import { a as extractSnowflakeMessage, c as parseRetryAfterHeader, i as extractSnowflakeCode, n as SnowflakeSqlError, o as extractSnowflakeSqlState, r as classifySnowflakeHttpStatus, s as extractSnowflakeStatementHandle, t as SnowflakeApiError } from "./errors-DKnc9o_a.mjs";
|
|
3
3
|
import { a as createSnowflakeJwt, i as shouldRetry, n as computeBackoffMs, r as defaultSleep, t as DEFAULT_RETRY_POLICY } from "./retry-0hMfb8cC.mjs";
|
|
4
4
|
|
|
@@ -20,7 +20,7 @@ import { a as createSnowflakeJwt, i as shouldRetry, n as computeBackoffMs, r as
|
|
|
20
20
|
*/
|
|
21
21
|
const DEFAULT_TIMEOUT_MS = 3e4;
|
|
22
22
|
const JWT_REFRESH_SKEW_SECONDS = 60;
|
|
23
|
-
const USER_AGENT = "keystroke-
|
|
23
|
+
const USER_AGENT = "keystroke-snowflake/0.0.0";
|
|
24
24
|
function createSnowflakeClient(rawCredentials, options = {}) {
|
|
25
25
|
const credentials = isSnowflakeCredentials(rawCredentials) ? rawCredentials : parseSnowflakeCredentials(rawCredentials);
|
|
26
26
|
const fetchImpl = options.fetchImpl ?? fetch;
|
package/dist/connection.d.mts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as snowflakeAuthModeSchema, c as snowflakeOfficialIntegration, i as snowflake, n as SnowflakeCredentials, o as snowflakeAuthSchema, r as parseSnowflakeCredentials, s as snowflakeBundle, t as SnowflakeAuthMode } from "./integration-
|
|
1
|
+
import { a as snowflakeAuthModeSchema, c as snowflakeOfficialIntegration, i as snowflake, n as SnowflakeCredentials, o as snowflakeAuthSchema, r as parseSnowflakeCredentials, s as snowflakeBundle, t as SnowflakeAuthMode } from "./integration-CQNVr6NY.mjs";
|
|
2
2
|
export { type SnowflakeAuthMode, type SnowflakeCredentials, parseSnowflakeCredentials, snowflake, snowflakeAuthModeSchema, snowflakeAuthSchema, snowflakeBundle, snowflakeOfficialIntegration };
|
package/dist/connection.mjs
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { a as snowflakeBundle, i as snowflakeAuthSchema, n as snowflake, o as snowflakeOfficialIntegration, r as snowflakeAuthModeSchema, t as parseSnowflakeCredentials } from "./integration-
|
|
1
|
+
import { a as snowflakeBundle, i as snowflakeAuthSchema, n as snowflake, o as snowflakeOfficialIntegration, r as snowflakeAuthModeSchema, t as parseSnowflakeCredentials } from "./integration-BVS1DWxb.mjs";
|
|
2
2
|
|
|
3
3
|
export { parseSnowflakeCredentials, snowflake, snowflakeAuthModeSchema, snowflakeAuthSchema, snowflakeBundle, snowflakeOfficialIntegration };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { defineOfficialIntegration } from "@keystrokehq/integration-authoring/official";
|
|
1
|
+
import { defineOfficialIntegration, officialCredentialSetId } from "@keystrokehq/integration-authoring/official";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { CredentialSet } from "@keystrokehq/core";
|
|
4
4
|
|
|
@@ -28,7 +28,7 @@ import { CredentialSet } from "@keystrokehq/core";
|
|
|
28
28
|
* certificate compromise) from posting events from an unrelated topic.
|
|
29
29
|
*/
|
|
30
30
|
const snowflakeS3SnsWebhookCredentialSet = new CredentialSet({
|
|
31
|
-
id: "snowflake-s3-sns-webhook",
|
|
31
|
+
id: officialCredentialSetId("snowflake-s3-sns-webhook"),
|
|
32
32
|
exposure: "platform-only",
|
|
33
33
|
name: "Snowflake Auto-Ingest — AWS SNS",
|
|
34
34
|
auth: z.object({
|
|
@@ -51,7 +51,7 @@ const snowflakeS3SnsWebhookCredentialSet = new CredentialSet({
|
|
|
51
51
|
* with any valid Google OIDC token from impersonating the subscription.
|
|
52
52
|
*/
|
|
53
53
|
const snowflakeGcsPubSubWebhookCredentialSet = new CredentialSet({
|
|
54
|
-
id: "snowflake-gcs-pubsub-webhook",
|
|
54
|
+
id: officialCredentialSetId("snowflake-gcs-pubsub-webhook"),
|
|
55
55
|
exposure: "platform-only",
|
|
56
56
|
name: "Snowflake Auto-Ingest — GCS Pub/Sub",
|
|
57
57
|
auth: z.object({
|
|
@@ -74,7 +74,7 @@ const snowflakeGcsPubSubWebhookCredentialSet = new CredentialSet({
|
|
|
74
74
|
* Cloud-Events schema. See `IMPLEMENTATION_NOTES.md` § trigger webhooks.
|
|
75
75
|
*/
|
|
76
76
|
const snowflakeAzureEventGridWebhookCredentialSet = new CredentialSet({
|
|
77
|
-
id: "snowflake-azure-eventgrid-webhook",
|
|
77
|
+
id: officialCredentialSetId("snowflake-azure-eventgrid-webhook"),
|
|
78
78
|
exposure: "platform-only",
|
|
79
79
|
name: "Snowflake Auto-Ingest — Azure Event Grid",
|
|
80
80
|
auth: z.object({
|
|
@@ -204,4 +204,4 @@ function parseSnowflakeCredentials(input) {
|
|
|
204
204
|
}
|
|
205
205
|
|
|
206
206
|
//#endregion
|
|
207
|
-
export { snowflakeBundle as a,
|
|
207
|
+
export { snowflakeBundle as a, snowflakeAuthSchema as i, snowflake as n, snowflakeOfficialIntegration as o, snowflakeAuthModeSchema as r, parseSnowflakeCredentials as t };
|
|
@@ -106,7 +106,7 @@ declare const snowflakeBundle: _keystrokehq_integration_authoring_official0.Offi
|
|
|
106
106
|
SNOWFLAKE_REGION: z.ZodOptional<z.ZodString>;
|
|
107
107
|
SNOWFLAKE_PUBLIC_KEY_FP: z.ZodOptional<z.ZodString>;
|
|
108
108
|
}, z.core.$strip>>;
|
|
109
|
-
declare const snowflake: _keystrokehq_core0.CredentialSet<"snowflake", z.ZodObject<{
|
|
109
|
+
declare const snowflake: _keystrokehq_core0.CredentialSet<"keystroke:snowflake", z.ZodObject<{
|
|
110
110
|
mode: z.ZodEnum<{
|
|
111
111
|
keypair: "keypair";
|
|
112
112
|
oauth: "oauth";
|
package/dist/triggers.d.mts
CHANGED
|
@@ -1,92 +1,16 @@
|
|
|
1
|
-
import { i as snowflake } from "./integration-
|
|
2
|
-
import { scheduledDeltaEventSchema, scheduledSqlEventSchema,
|
|
1
|
+
import { i as snowflake } from "./integration-CQNVr6NY.mjs";
|
|
2
|
+
import { scheduledDeltaEventSchema, scheduledSqlEventSchema, streamAppendOnlyEventSchema, streamHasDataEventSchema } from "./events.mjs";
|
|
3
3
|
import { RowValue } from "./rows.mjs";
|
|
4
4
|
import { StatusPageOptions } from "./status-page.mjs";
|
|
5
|
-
import { VerifyEventGridRequestOptions, VerifyPubSubPushOptions, VerifySnsMessageOptions } from "./verification.mjs";
|
|
6
5
|
import { z } from "zod";
|
|
7
|
-
import
|
|
8
|
-
import { BoundTrigger, CredentialSet } from "@keystrokehq/core";
|
|
6
|
+
import { BoundTrigger } from "@keystrokehq/core";
|
|
9
7
|
import { IntegrationTriggerBindingOptions, createPollingTriggerBindingFactory } from "@keystrokehq/integration-authoring";
|
|
10
|
-
import
|
|
11
|
-
import { AnyCredentialSet, InferCredentialSetAuth } from "@keystrokehq/core/credential-set";
|
|
12
|
-
import { PollingTriggerManifest, WebhookRequest, WebhookTriggerManifest } from "@keystrokehq/core/trigger";
|
|
8
|
+
import { PollingTriggerManifest } from "@keystrokehq/core/trigger";
|
|
13
9
|
|
|
14
|
-
//#region src/webhook-credentials.d.ts
|
|
15
|
-
/**
|
|
16
|
-
* AWS SNS webhook credentials.
|
|
17
|
-
*
|
|
18
|
-
* SNS verifies message authenticity via an X.509 signature whose public
|
|
19
|
-
* cert is published at an SNS-hosted URL. The only real secret is the
|
|
20
|
-
* optional {@link expectedTopicArn}: tight-scoping to a single topic
|
|
21
|
-
* prevents an attacker that fooled SNS signing (not possible absent a
|
|
22
|
-
* certificate compromise) from posting events from an unrelated topic.
|
|
23
|
-
*/
|
|
24
|
-
declare const snowflakeS3SnsWebhookCredentialSet: CredentialSet<"snowflake-s3-sns-webhook", z.ZodObject<{
|
|
25
|
-
expectedTopicArn: z.ZodOptional<z.ZodString>;
|
|
26
|
-
allowedSigningCertHostPattern: z.ZodOptional<z.ZodDefault<z.ZodString>>;
|
|
27
|
-
autoConfirmSubscriptions: z.ZodOptional<z.ZodDefault<z.ZodEnum<{
|
|
28
|
-
true: "true";
|
|
29
|
-
false: "false";
|
|
30
|
-
}>>>;
|
|
31
|
-
}, z.core.$strip>, readonly _keystrokehq_core_credential_set0.CredentialConnection<z.ZodObject<{
|
|
32
|
-
expectedTopicArn: z.ZodOptional<z.ZodString>;
|
|
33
|
-
allowedSigningCertHostPattern: z.ZodOptional<z.ZodDefault<z.ZodString>>;
|
|
34
|
-
autoConfirmSubscriptions: z.ZodOptional<z.ZodDefault<z.ZodEnum<{
|
|
35
|
-
true: "true";
|
|
36
|
-
false: "false";
|
|
37
|
-
}>>>;
|
|
38
|
-
}, z.core.$strip>>[] | undefined>;
|
|
39
|
-
/**
|
|
40
|
-
* GCP Pub/Sub push subscriptions sign each request with a Google-minted
|
|
41
|
-
* OIDC JWT (RS256) in the `Authorization` header. Verification requires
|
|
42
|
-
* knowing:
|
|
43
|
-
*
|
|
44
|
-
* - `expectedAudience`: the OIDC audience configured on the push
|
|
45
|
-
* subscription (commonly the webhook URL itself).
|
|
46
|
-
* - `expectedServiceAccount`: the push subscription's service-account
|
|
47
|
-
* email, which appears as the JWT `email` claim.
|
|
48
|
-
*
|
|
49
|
-
* Both are public identifiers, but pinning them prevents an attacker
|
|
50
|
-
* with any valid Google OIDC token from impersonating the subscription.
|
|
51
|
-
*/
|
|
52
|
-
declare const snowflakeGcsPubSubWebhookCredentialSet: CredentialSet<"snowflake-gcs-pubsub-webhook", z.ZodObject<{
|
|
53
|
-
expectedAudience: z.ZodString;
|
|
54
|
-
expectedServiceAccount: z.ZodString;
|
|
55
|
-
jwksUri: z.ZodOptional<z.ZodDefault<z.ZodString>>;
|
|
56
|
-
issuer: z.ZodOptional<z.ZodDefault<z.ZodString>>;
|
|
57
|
-
}, z.core.$strip>, readonly _keystrokehq_core_credential_set0.CredentialConnection<z.ZodObject<{
|
|
58
|
-
expectedAudience: z.ZodString;
|
|
59
|
-
expectedServiceAccount: z.ZodString;
|
|
60
|
-
jwksUri: z.ZodOptional<z.ZodDefault<z.ZodString>>;
|
|
61
|
-
issuer: z.ZodOptional<z.ZodDefault<z.ZodString>>;
|
|
62
|
-
}, z.core.$strip>>[] | undefined>;
|
|
63
|
-
/**
|
|
64
|
-
* Azure Event Grid authenticates webhook endpoints via one-time
|
|
65
|
-
* handshake validation (validation code or URL) rather than per-request
|
|
66
|
-
* signatures. If the operator wires a **shared secret** into the
|
|
67
|
-
* Event Grid subscription as a custom header, that secret can be
|
|
68
|
-
* checked here as a defence-in-depth measure.
|
|
69
|
-
*
|
|
70
|
-
* Event Grid's synchronous handshake (`SubscriptionValidationEvent`) is
|
|
71
|
-
* not fulfillable through the Keystroke static-response webhook
|
|
72
|
-
* primitive; Snowflake operators should use **manual handshake** or
|
|
73
|
-
* Cloud-Events schema. See `IMPLEMENTATION_NOTES.md` § trigger webhooks.
|
|
74
|
-
*/
|
|
75
|
-
declare const snowflakeAzureEventGridWebhookCredentialSet: CredentialSet<"snowflake-azure-eventgrid-webhook", z.ZodObject<{
|
|
76
|
-
sharedSecret: z.ZodOptional<z.ZodString>;
|
|
77
|
-
sharedSecretHeader: z.ZodOptional<z.ZodDefault<z.ZodString>>;
|
|
78
|
-
expectedTopicArn: z.ZodOptional<z.ZodString>;
|
|
79
|
-
}, z.core.$strip>, readonly _keystrokehq_core_credential_set0.CredentialConnection<z.ZodObject<{
|
|
80
|
-
sharedSecret: z.ZodOptional<z.ZodString>;
|
|
81
|
-
sharedSecretHeader: z.ZodOptional<z.ZodDefault<z.ZodString>>;
|
|
82
|
-
expectedTopicArn: z.ZodOptional<z.ZodString>;
|
|
83
|
-
}, z.core.$strip>>[] | undefined>;
|
|
84
|
-
//#endregion
|
|
85
10
|
//#region src/triggers.d.ts
|
|
86
11
|
type Schedule = Parameters<typeof createPollingTriggerBindingFactory>[0]['schedule'];
|
|
87
12
|
type SnowflakeCredentialSets = readonly [typeof snowflake];
|
|
88
13
|
type SnowflakePollingBoundTrigger<TResponse extends z.ZodTypeAny, TOutput = z.output<TResponse>> = BoundTrigger<z.output<TResponse>, SnowflakeCredentialSets, PollingTriggerManifest, TOutput>;
|
|
89
|
-
type SnowflakeWebhookBoundTrigger<TPayloadSchema extends z.ZodTypeAny, TCredentialSets extends readonly [typeof snowflake, ...AnyCredentialSet[]], TOutput = SnowpipeObjectBatchOutput> = BoundTrigger<z.output<TPayloadSchema>, TCredentialSets, WebhookTriggerManifest, TOutput, WebhookRequest>;
|
|
90
14
|
interface SnowflakePollingBindingOptions<TResponse extends z.ZodTypeAny, TOutput = z.output<TResponse>> extends IntegrationTriggerBindingOptions<z.output<TResponse>, TOutput> {
|
|
91
15
|
readonly schedule?: Schedule;
|
|
92
16
|
}
|
|
@@ -423,116 +347,10 @@ declare const statusPageComponentChangeBatchSchema: z.ZodObject<{
|
|
|
423
347
|
* Status-page pollers don't need Snowflake credentials, but they still
|
|
424
348
|
* flow through the Snowflake binding factory so they show up in the
|
|
425
349
|
* polling manifest alongside the rest. Credentials are declared but
|
|
426
|
-
* unused — the poll callback ignores `ctx.credentials
|
|
350
|
+
* unused — the poll callback ignores `ctx.credentials['keystroke:snowflake']`.
|
|
427
351
|
*/
|
|
428
352
|
declare function statusPageIncident<TOutput = z.output<typeof statusPageIncidentBatchSchema>>(input?: StatusPagePollingParams & SnowflakePollingBindingOptions<typeof statusPageIncidentBatchSchema, TOutput>): SnowflakePollingBoundTrigger<typeof statusPageIncidentBatchSchema, TOutput>;
|
|
429
353
|
declare function statusPageComponentChanged<TOutput = z.output<typeof statusPageComponentChangeBatchSchema>>(input?: StatusPagePollingParams & SnowflakePollingBindingOptions<typeof statusPageComponentChangeBatchSchema, TOutput>): SnowflakePollingBoundTrigger<typeof statusPageComponentChangeBatchSchema, TOutput>;
|
|
430
|
-
/**
|
|
431
|
-
* Shape of the event emitted by an auto-ingest webhook trigger. Same
|
|
432
|
-
* envelope across the 3 clouds so downstream pipelines can branch on
|
|
433
|
-
* `provider` without re-modelling.
|
|
434
|
-
*/
|
|
435
|
-
type SnowpipeObjectBatchOutput = z.output<typeof snowpipeObjectBatchSchema>;
|
|
436
|
-
type SnowpipeWebhookRequest = _keystrokehq_core0.WebhookRequest;
|
|
437
|
-
interface WebhookBindingOptions<TOutput = SnowpipeObjectBatchOutput> extends IntegrationTriggerBindingOptions<SnowpipeObjectBatchOutput, TOutput, SnowpipeWebhookRequest> {
|
|
438
|
-
/**
|
|
439
|
-
* Override the default `/snowflake/...` path suffix. The router serves
|
|
440
|
-
* webhooks at `/api/v1/webhooks/{orgId}{path}` so authors only supply the
|
|
441
|
-
* suffix that follows the organization id.
|
|
442
|
-
*/
|
|
443
|
-
readonly path?: string;
|
|
444
|
-
}
|
|
445
|
-
/**
|
|
446
|
-
* The public SNS payload schema accepted by this trigger. SNS delivers
|
|
447
|
-
* an application/json body; we validate it before signature-checking.
|
|
448
|
-
*/
|
|
449
|
-
declare const snsWebhookPayloadSchema: z.ZodObject<{
|
|
450
|
-
Type: z.ZodEnum<{
|
|
451
|
-
Notification: "Notification";
|
|
452
|
-
SubscriptionConfirmation: "SubscriptionConfirmation";
|
|
453
|
-
UnsubscribeConfirmation: "UnsubscribeConfirmation";
|
|
454
|
-
}>;
|
|
455
|
-
MessageId: z.ZodString;
|
|
456
|
-
TopicArn: z.ZodString;
|
|
457
|
-
Message: z.ZodString;
|
|
458
|
-
Timestamp: z.ZodString;
|
|
459
|
-
SignatureVersion: z.ZodString;
|
|
460
|
-
Signature: z.ZodString;
|
|
461
|
-
SigningCertURL: z.ZodString;
|
|
462
|
-
Subject: z.ZodOptional<z.ZodString>;
|
|
463
|
-
Token: z.ZodOptional<z.ZodString>;
|
|
464
|
-
SubscribeURL: z.ZodOptional<z.ZodString>;
|
|
465
|
-
UnsubscribeURL: z.ZodOptional<z.ZodString>;
|
|
466
|
-
}, z.core.$catchall<z.ZodUnknown>>;
|
|
467
|
-
/**
|
|
468
|
-
* `triggers.autoIngestS3Sns` — receives AWS SNS HTTPS deliveries,
|
|
469
|
-
* verifies the SNS signature against the signing certificate, and
|
|
470
|
-
* emits normalized S3 object events. Subscription-confirmation
|
|
471
|
-
* messages are surfaced via a sentinel event of type
|
|
472
|
-
* `SubscriptionConfirmation` so operators can auto-confirm in
|
|
473
|
-
* downstream logic.
|
|
474
|
-
*/
|
|
475
|
-
declare function autoIngestS3Sns<TOutput = SnowpipeObjectBatchOutput>(input?: WebhookBindingOptions<TOutput> & {
|
|
476
|
-
/** Test seam — injected verifier overrides. Not persisted. */readonly verifierOptions?: VerifySnsMessageOptions;
|
|
477
|
-
}): SnowflakeWebhookBoundTrigger<typeof snsWebhookPayloadSchema, readonly [typeof snowflake, typeof snowflakeS3SnsWebhookCredentialSet], TOutput>;
|
|
478
|
-
declare const gcsWebhookPayloadSchema: z.ZodObject<{
|
|
479
|
-
message: z.ZodObject<{
|
|
480
|
-
messageId: z.ZodOptional<z.ZodString>;
|
|
481
|
-
publishTime: z.ZodOptional<z.ZodString>;
|
|
482
|
-
data: z.ZodOptional<z.ZodString>;
|
|
483
|
-
attributes: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
484
|
-
}, z.core.$catchall<z.ZodUnknown>>;
|
|
485
|
-
subscription: z.ZodOptional<z.ZodString>;
|
|
486
|
-
}, z.core.$catchall<z.ZodUnknown>>;
|
|
487
|
-
/**
|
|
488
|
-
* `triggers.autoIngestGcsPubSub` — receives GCP Pub/Sub push deliveries,
|
|
489
|
-
* verifies the Google-signed OIDC JWT against Google's JWKS, and
|
|
490
|
-
* emits normalized GCS object events extracted from the Pub/Sub
|
|
491
|
-
* message body.
|
|
492
|
-
*/
|
|
493
|
-
declare function autoIngestGcsPubSub<TOutput = SnowpipeObjectBatchOutput>(input?: WebhookBindingOptions<TOutput> & {
|
|
494
|
-
readonly verifierOptions?: Partial<VerifyPubSubPushOptions>;
|
|
495
|
-
}): SnowflakeWebhookBoundTrigger<typeof gcsWebhookPayloadSchema, readonly [typeof snowflake, typeof snowflakeGcsPubSubWebhookCredentialSet], TOutput>;
|
|
496
|
-
/**
|
|
497
|
-
* Event Grid wire format is `AzureEventGridEvent[]`. Keystroke's
|
|
498
|
-
* webhook primitive validates a single payload schema — so we accept
|
|
499
|
-
* either a single event or a wrapper object. The runtime reparses the
|
|
500
|
-
* raw body to preserve the array shape.
|
|
501
|
-
*/
|
|
502
|
-
declare const azureWebhookPayloadSchema: z.ZodUnion<readonly [z.ZodArray<z.ZodObject<{
|
|
503
|
-
id: z.ZodString;
|
|
504
|
-
eventType: z.ZodString;
|
|
505
|
-
subject: z.ZodString;
|
|
506
|
-
eventTime: z.ZodString;
|
|
507
|
-
dataVersion: z.ZodOptional<z.ZodString>;
|
|
508
|
-
metadataVersion: z.ZodOptional<z.ZodString>;
|
|
509
|
-
topic: z.ZodOptional<z.ZodString>;
|
|
510
|
-
data: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
511
|
-
}, z.core.$catchall<z.ZodUnknown>>>, z.ZodObject<{
|
|
512
|
-
id: z.ZodString;
|
|
513
|
-
eventType: z.ZodString;
|
|
514
|
-
subject: z.ZodString;
|
|
515
|
-
eventTime: z.ZodString;
|
|
516
|
-
dataVersion: z.ZodOptional<z.ZodString>;
|
|
517
|
-
metadataVersion: z.ZodOptional<z.ZodString>;
|
|
518
|
-
topic: z.ZodOptional<z.ZodString>;
|
|
519
|
-
data: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
520
|
-
}, z.core.$catchall<z.ZodUnknown>>]>;
|
|
521
|
-
/**
|
|
522
|
-
* `triggers.autoIngestAzureEventGrid` — receives Azure Event Grid
|
|
523
|
-
* deliveries. Validates shared-secret header (if configured), parses
|
|
524
|
-
* the batch, and emits normalized Azure Blob object events. The
|
|
525
|
-
* synchronous subscription-validation handshake is NOT supported
|
|
526
|
-
* through the Keystroke static-response primitive; operators must
|
|
527
|
-
* use manual validation URLs (see IMPLEMENTATION_NOTES.md).
|
|
528
|
-
*/
|
|
529
|
-
declare function autoIngestAzureEventGrid<TOutput = SnowpipeObjectBatchOutput>(input?: WebhookBindingOptions<TOutput> & {
|
|
530
|
-
readonly verifierOptions?: VerifyEventGridRequestOptions;
|
|
531
|
-
}): SnowflakeWebhookBoundTrigger<typeof azureWebhookPayloadSchema, readonly [typeof snowflake, typeof snowflakeAzureEventGridWebhookCredentialSet], TOutput>;
|
|
532
|
-
/**
|
|
533
|
-
* Convenience namespace grouping every polling trigger in this package.
|
|
534
|
-
* Equivalent to calling the individual factory functions directly.
|
|
535
|
-
*/
|
|
536
354
|
declare const polling: Readonly<{
|
|
537
355
|
streamHasNewRows: typeof streamHasNewRows;
|
|
538
356
|
streamAppendOnly: typeof streamAppendOnly;
|
|
@@ -557,22 +375,5 @@ declare const polling: Readonly<{
|
|
|
557
375
|
statusPageIncident: typeof statusPageIncident;
|
|
558
376
|
statusPageComponentChanged: typeof statusPageComponentChanged;
|
|
559
377
|
}>;
|
|
560
|
-
/**
|
|
561
|
-
* Convenience namespace grouping every auto-ingest webhook trigger.
|
|
562
|
-
*/
|
|
563
|
-
declare const webhooks: Readonly<{
|
|
564
|
-
autoIngestS3Sns: typeof autoIngestS3Sns;
|
|
565
|
-
autoIngestGcsPubSub: typeof autoIngestGcsPubSub;
|
|
566
|
-
autoIngestAzureEventGrid: typeof autoIngestAzureEventGrid;
|
|
567
|
-
}>;
|
|
568
|
-
/**
|
|
569
|
-
* Nested `autoIngest` namespace per the spec (§7 PLAN.md) so callers
|
|
570
|
-
* can reach the three webhook triggers via `triggers.autoIngest.<cloud>`.
|
|
571
|
-
*/
|
|
572
|
-
declare const autoIngest: Readonly<{
|
|
573
|
-
s3Sns: typeof autoIngestS3Sns;
|
|
574
|
-
gcsPubSub: typeof autoIngestGcsPubSub;
|
|
575
|
-
azureEventGrid: typeof autoIngestAzureEventGrid;
|
|
576
|
-
}>;
|
|
577
378
|
//#endregion
|
|
578
|
-
export {
|
|
379
|
+
export { databaseCreated, databaseDropped, grantsChanged, pipeLoadCompleted, pipeLoadFailed, polling, roleCreated, roleDropped, scheduledDelta, scheduledSql, shareChanged, statementCompleted, statusPageComponentChanged, statusPageIncident, streamAppendOnly, streamHasNewRows, tableCreated, tableDropped, taskRunCompleted, userCreated, userDropped, warehouseCreditsUsage, warehouseQueueDepth };
|
package/dist/triggers.mjs
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { n as snowflake } from "./integration-BVS1DWxb.mjs";
|
|
2
2
|
import { createSnowflakeClient } from "./client.mjs";
|
|
3
|
-
import {
|
|
3
|
+
import { grantChangedEventSchema, pipeLoadEventSchema, scheduledDeltaEventSchema, scheduledSqlEventSchema, shareChangedEventSchema, snowflakeDiffSnapshotEntrySchema, snowflakeObjectLifecycleEventSchema, statementCompletedEventSchema, statusPageComponentChangeEventSchema, statusPageIncidentEventSchema, streamAppendOnlyEventSchema, streamHasDataEventSchema, taskRunEventSchema, warehouseCreditsEventSchema, warehouseQueueEventSchema } from "./events.mjs";
|
|
4
4
|
import { executeSql } from "./sql.mjs";
|
|
5
5
|
import { a as quoteLiteral, i as quoteIdentifier, t as buildFqn } from "./sql-safety-CywdR3EP.mjs";
|
|
6
6
|
import { BindingBuilder } from "./rows.mjs";
|
|
7
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
8
|
import { z } from "zod";
|
|
10
|
-
import { createPollingTriggerBindingFactory
|
|
9
|
+
import { createPollingTriggerBindingFactory } from "@keystrokehq/integration-authoring";
|
|
11
10
|
|
|
12
11
|
//#region src/triggers.ts
|
|
13
12
|
function defineSnowflakePollingTrigger(definition) {
|
|
@@ -17,9 +16,8 @@ function splitBindingOptions(input) {
|
|
|
17
16
|
return {
|
|
18
17
|
schedule: input?.schedule,
|
|
19
18
|
bindOptions: {
|
|
20
|
-
...input?.
|
|
19
|
+
...input?.id ? { id: input.id } : {},
|
|
21
20
|
...input?.description ? { description: input.description } : {},
|
|
22
|
-
...input?.filter ? { filter: input.filter } : {},
|
|
23
21
|
...input?.transform ? { transform: input.transform } : {}
|
|
24
22
|
}
|
|
25
23
|
};
|
|
@@ -30,17 +28,15 @@ function resolveSchedule(override, fallback) {
|
|
|
30
28
|
}
|
|
31
29
|
function createSnowflakePollingBindingFactory(definition, params, schedule) {
|
|
32
30
|
return createPollingTriggerBindingFactory({
|
|
33
|
-
|
|
31
|
+
defaultId: definition.defaultId,
|
|
34
32
|
defaultDescription: definition.defaultDescription,
|
|
35
33
|
schedule: resolveSchedule(schedule, definition.defaultSchedule),
|
|
36
34
|
credentialSets: [snowflake],
|
|
37
35
|
response: definition.responseSchema,
|
|
38
36
|
poll: (ctx) => {
|
|
39
37
|
const snowflakeCtx = ctx;
|
|
40
|
-
return definition.poll(params, snowflakeCtx.credentials
|
|
41
|
-
}
|
|
42
|
-
idempotencyKey: (response) => definition.idempotencyKey(response),
|
|
43
|
-
definitionFilter: (response) => definition.hasPayload(response)
|
|
38
|
+
return definition.poll(params, snowflakeCtx.credentials["keystroke:snowflake"], snowflakeCtx);
|
|
39
|
+
}
|
|
44
40
|
});
|
|
45
41
|
}
|
|
46
42
|
function bindSnowflakePollingTrigger(definition, params, input) {
|
|
@@ -73,7 +69,7 @@ function hashStatement(statement) {
|
|
|
73
69
|
}
|
|
74
70
|
const streamHasNewRowsDefinition = defineSnowflakePollingTrigger({
|
|
75
71
|
responseSchema: streamHasDataEventSchema,
|
|
76
|
-
|
|
72
|
+
defaultId: "snowflake.stream_has_new_rows",
|
|
77
73
|
defaultDescription: "Fires when SYSTEM$STREAM_HAS_DATA returns TRUE for a Snowflake stream (fires at most once per schedule tick).",
|
|
78
74
|
defaultSchedule: "1m",
|
|
79
75
|
poll: async (params, credentials) => {
|
|
@@ -94,16 +90,14 @@ const streamHasNewRowsDefinition = defineSnowflakePollingTrigger({
|
|
|
94
90
|
hasData,
|
|
95
91
|
checkedAt: isoNow()
|
|
96
92
|
};
|
|
97
|
-
}
|
|
98
|
-
idempotencyKey: (response) => `${response.streamName}:${response.checkedAt}`,
|
|
99
|
-
hasPayload: (response) => response.hasData
|
|
93
|
+
}
|
|
100
94
|
});
|
|
101
95
|
function streamHasNewRows(input) {
|
|
102
96
|
return bindSnowflakePollingTrigger(streamHasNewRowsDefinition, input, input);
|
|
103
97
|
}
|
|
104
98
|
const streamAppendOnlyDefinition = defineSnowflakePollingTrigger({
|
|
105
99
|
responseSchema: streamAppendOnlyEventSchema,
|
|
106
|
-
|
|
100
|
+
defaultId: "snowflake.stream_append_only",
|
|
107
101
|
defaultDescription: "Emits new inserted rows from a Snowflake stream on each poll. Consumers must progress the stream via downstream DML in a transaction.",
|
|
108
102
|
defaultSchedule: "1m",
|
|
109
103
|
poll: async (params, credentials) => {
|
|
@@ -123,16 +117,14 @@ const streamAppendOnlyDefinition = defineSnowflakePollingTrigger({
|
|
|
123
117
|
rowCount: rows.length,
|
|
124
118
|
fetchedAt: isoNow()
|
|
125
119
|
};
|
|
126
|
-
}
|
|
127
|
-
idempotencyKey: (response) => `${response.streamName}:${response.fetchedAt}:${response.rowCount}`,
|
|
128
|
-
hasPayload: (response) => response.rowCount > 0
|
|
120
|
+
}
|
|
129
121
|
});
|
|
130
122
|
function streamAppendOnly(input) {
|
|
131
123
|
return bindSnowflakePollingTrigger(streamAppendOnlyDefinition, input, input);
|
|
132
124
|
}
|
|
133
125
|
const scheduledSqlDefinition = defineSnowflakePollingTrigger({
|
|
134
126
|
responseSchema: scheduledSqlEventSchema,
|
|
135
|
-
|
|
127
|
+
defaultId: "snowflake.scheduled_sql",
|
|
136
128
|
defaultDescription: "Runs a caller-supplied SQL statement on each schedule tick and emits the full result set.",
|
|
137
129
|
defaultSchedule: "5m",
|
|
138
130
|
poll: async (params, credentials) => {
|
|
@@ -159,15 +151,10 @@ const scheduledSqlDefinition = defineSnowflakePollingTrigger({
|
|
|
159
151
|
rowCount: rows.length,
|
|
160
152
|
fetchedAt: isoNow()
|
|
161
153
|
};
|
|
162
|
-
}
|
|
163
|
-
idempotencyKey: (response) => `${response.statementDigest}:${response.fetchedAt}`,
|
|
164
|
-
hasPayload: (response) => response.rowCount > 0
|
|
154
|
+
}
|
|
165
155
|
});
|
|
166
156
|
function scheduledSql(input) {
|
|
167
|
-
return bindSnowflakePollingTrigger(input
|
|
168
|
-
...scheduledSqlDefinition,
|
|
169
|
-
hasPayload: () => true
|
|
170
|
-
} : scheduledSqlDefinition, input, input);
|
|
157
|
+
return bindSnowflakePollingTrigger(scheduledSqlDefinition, input, input);
|
|
171
158
|
}
|
|
172
159
|
function stringifyCursor(value) {
|
|
173
160
|
if (value === null || value === void 0) return null;
|
|
@@ -177,7 +164,7 @@ function stringifyCursor(value) {
|
|
|
177
164
|
}
|
|
178
165
|
const scheduledDeltaDefinition = defineSnowflakePollingTrigger({
|
|
179
166
|
responseSchema: scheduledDeltaEventSchema,
|
|
180
|
-
|
|
167
|
+
defaultId: "snowflake.scheduled_delta",
|
|
181
168
|
defaultDescription: "Emits new rows from a Snowflake table where a cursor column exceeds the last observed high-water mark.",
|
|
182
169
|
defaultSchedule: "1m",
|
|
183
170
|
poll: async (params, credentials, ctx) => {
|
|
@@ -215,9 +202,7 @@ const scheduledDeltaDefinition = defineSnowflakePollingTrigger({
|
|
|
215
202
|
rowCount: rows.length,
|
|
216
203
|
fetchedAt: isoNow()
|
|
217
204
|
};
|
|
218
|
-
}
|
|
219
|
-
idempotencyKey: (response) => `${response.table}:${response.cursorColumn}:${response.highWaterValue ?? "initial"}`,
|
|
220
|
-
hasPayload: (response) => response.rowCount > 0
|
|
205
|
+
}
|
|
221
206
|
});
|
|
222
207
|
function scheduledDelta(input) {
|
|
223
208
|
return bindSnowflakePollingTrigger(scheduledDeltaDefinition, input, input);
|
|
@@ -234,7 +219,7 @@ const taskRunCompletedDefinition = defineSnowflakePollingTrigger({
|
|
|
234
219
|
highWaterValue: z.string().nullable(),
|
|
235
220
|
fetchedAt: z.string()
|
|
236
221
|
}).catchall(z.unknown()),
|
|
237
|
-
|
|
222
|
+
defaultId: "snowflake.task_run_completed",
|
|
238
223
|
defaultDescription: "Emits terminal task runs polled from INFORMATION_SCHEMA.TASK_HISTORY (SUCCEEDED / FAILED / CANCELLED).",
|
|
239
224
|
defaultSchedule: "1m",
|
|
240
225
|
poll: async (params, credentials, ctx) => {
|
|
@@ -272,9 +257,7 @@ const taskRunCompletedDefinition = defineSnowflakePollingTrigger({
|
|
|
272
257
|
highWaterValue: readStringField(last ?? {}, "COMPLETED_TIME", "completed_time") ?? readStringField(last ?? {}, "SCHEDULED_TIME", "scheduled_time") ?? priorHighWater,
|
|
273
258
|
fetchedAt: isoNow()
|
|
274
259
|
};
|
|
275
|
-
}
|
|
276
|
-
idempotencyKey: (response) => `task-run:${response.highWaterValue ?? "initial"}:${response.events.length}`,
|
|
277
|
-
hasPayload: (response) => response.events.length > 0
|
|
260
|
+
}
|
|
278
261
|
});
|
|
279
262
|
function taskRunCompleted(input) {
|
|
280
263
|
return bindSnowflakePollingTrigger(taskRunCompletedDefinition, {
|
|
@@ -301,7 +284,7 @@ const statementCompletedDefinition = defineSnowflakePollingTrigger({
|
|
|
301
284
|
highWaterValue: z.string().nullable(),
|
|
302
285
|
fetchedAt: z.string()
|
|
303
286
|
}).catchall(z.unknown()),
|
|
304
|
-
|
|
287
|
+
defaultId: "snowflake.statement_completed",
|
|
305
288
|
defaultDescription: "Emits terminal statements from INFORMATION_SCHEMA.QUERY_HISTORY, optionally filtered by user / text / warehouse.",
|
|
306
289
|
defaultSchedule: "1m",
|
|
307
290
|
poll: async (params, credentials, ctx) => {
|
|
@@ -349,9 +332,7 @@ const statementCompletedDefinition = defineSnowflakePollingTrigger({
|
|
|
349
332
|
highWaterValue: readStringField(last ?? {}, "END_TIME", "end_time") ?? readStringField(last ?? {}, "START_TIME", "start_time") ?? priorHighWater,
|
|
350
333
|
fetchedAt: isoNow()
|
|
351
334
|
};
|
|
352
|
-
}
|
|
353
|
-
idempotencyKey: (response) => `stmt:${response.highWaterValue ?? "initial"}:${response.events.length}`,
|
|
354
|
-
hasPayload: (response) => response.events.length > 0
|
|
335
|
+
}
|
|
355
336
|
});
|
|
356
337
|
function statementCompleted(input) {
|
|
357
338
|
return bindSnowflakePollingTrigger(statementCompletedDefinition, {
|
|
@@ -372,10 +353,10 @@ const pipeLoadBatchSchema = z.object({
|
|
|
372
353
|
highWaterValue: z.string().nullable(),
|
|
373
354
|
fetchedAt: z.string()
|
|
374
355
|
}).catchall(z.unknown());
|
|
375
|
-
function buildPipeLoadDefinition(
|
|
356
|
+
function buildPipeLoadDefinition(defaultId, defaultDescription, statusPredicate) {
|
|
376
357
|
return defineSnowflakePollingTrigger({
|
|
377
358
|
responseSchema: pipeLoadBatchSchema,
|
|
378
|
-
|
|
359
|
+
defaultId,
|
|
379
360
|
defaultDescription,
|
|
380
361
|
defaultSchedule: "1m",
|
|
381
362
|
poll: async (params, credentials, ctx) => {
|
|
@@ -418,13 +399,11 @@ function buildPipeLoadDefinition(defaultName, defaultDescription, statusPredicat
|
|
|
418
399
|
highWaterValue: readStringField(last ?? {}, "LAST_LOAD_TIME", "last_load_time") ?? priorHighWater,
|
|
419
400
|
fetchedAt: isoNow()
|
|
420
401
|
};
|
|
421
|
-
}
|
|
422
|
-
idempotencyKey: (response) => `pipe-load:${response.highWaterValue ?? "initial"}:${response.events.length}`,
|
|
423
|
-
hasPayload: (response) => response.events.length > 0
|
|
402
|
+
}
|
|
424
403
|
});
|
|
425
404
|
}
|
|
426
|
-
const pipeLoadCompletedDefinition = buildPipeLoadDefinition("
|
|
427
|
-
const pipeLoadFailedDefinition = buildPipeLoadDefinition("
|
|
405
|
+
const pipeLoadCompletedDefinition = buildPipeLoadDefinition("snowflake.pipe_load_completed", "Emits successful Snowpipe loads polled from INFORMATION_SCHEMA.COPY_HISTORY.", `STATUS = 'LOADED'`);
|
|
406
|
+
const pipeLoadFailedDefinition = buildPipeLoadDefinition("snowflake.pipe_load_failed", "Emits failed Snowpipe loads polled from INFORMATION_SCHEMA.COPY_HISTORY.", `STATUS IN ('LOAD_FAILED', 'PARTIALLY_LOADED')`);
|
|
428
407
|
function pipeLoadCompleted(input) {
|
|
429
408
|
return bindSnowflakePollingTrigger(pipeLoadCompletedDefinition, {
|
|
430
409
|
pipeName: input?.pipeName,
|
|
@@ -493,9 +472,6 @@ function toSnapshotEntries(entries) {
|
|
|
493
472
|
...e.signature !== void 0 ? { signature: e.signature } : {}
|
|
494
473
|
}));
|
|
495
474
|
}
|
|
496
|
-
function sortedIdempotencyKey(prefix, events, getIdentity = (e) => e.identity ?? "") {
|
|
497
|
-
return `${prefix}:${events.map(getIdentity).slice().sort().join(",") || "empty"}`;
|
|
498
|
-
}
|
|
499
475
|
const lifecycleBatchSchema = buildDiffBatchSchema(snowflakeObjectLifecycleEventSchema);
|
|
500
476
|
function rowToLifecycleEntry(kind, row) {
|
|
501
477
|
const name = readStringField(row, "name", "NAME") ?? "";
|
|
@@ -527,7 +503,7 @@ function lifecycleEventFromEntry(kind, change, entry, detectedAt) {
|
|
|
527
503
|
function buildLifecycleDefinition(config) {
|
|
528
504
|
return defineSnowflakePollingTrigger({
|
|
529
505
|
responseSchema: lifecycleBatchSchema,
|
|
530
|
-
|
|
506
|
+
defaultId: config.defaultId,
|
|
531
507
|
defaultDescription: config.defaultDescription,
|
|
532
508
|
defaultSchedule: "5m",
|
|
533
509
|
poll: async (params, credentials, ctx) => {
|
|
@@ -548,9 +524,7 @@ function buildLifecycleDefinition(config) {
|
|
|
548
524
|
snapshot: toSnapshotEntries(current),
|
|
549
525
|
fetchedAt: detectedAt
|
|
550
526
|
};
|
|
551
|
-
}
|
|
552
|
-
idempotencyKey: (response) => sortedIdempotencyKey(`${config.kind}:${config.change}`, response.events),
|
|
553
|
-
hasPayload: (response) => response.events.length > 0
|
|
527
|
+
}
|
|
554
528
|
});
|
|
555
529
|
}
|
|
556
530
|
function likeClause(like) {
|
|
@@ -566,14 +540,14 @@ function inScope(params) {
|
|
|
566
540
|
const userCreatedDefinition = buildLifecycleDefinition({
|
|
567
541
|
kind: "USER",
|
|
568
542
|
change: "created",
|
|
569
|
-
|
|
543
|
+
defaultId: "snowflake.user_created",
|
|
570
544
|
defaultDescription: "Emits every new user observed via SHOW USERS since the prior poll.",
|
|
571
545
|
buildStatement: (params) => `SHOW USERS${likeClause(params.like)}`
|
|
572
546
|
});
|
|
573
547
|
const userDroppedDefinition = buildLifecycleDefinition({
|
|
574
548
|
kind: "USER",
|
|
575
549
|
change: "dropped",
|
|
576
|
-
|
|
550
|
+
defaultId: "snowflake.user_dropped",
|
|
577
551
|
defaultDescription: "Emits every user that has disappeared from SHOW USERS since the prior poll.",
|
|
578
552
|
buildStatement: (params) => `SHOW USERS${likeClause(params.like)}`
|
|
579
553
|
});
|
|
@@ -599,14 +573,14 @@ function userDropped(input) {
|
|
|
599
573
|
const roleCreatedDefinition = buildLifecycleDefinition({
|
|
600
574
|
kind: "ROLE",
|
|
601
575
|
change: "created",
|
|
602
|
-
|
|
576
|
+
defaultId: "snowflake.role_created",
|
|
603
577
|
defaultDescription: "Emits every new role observed via SHOW ROLES since the prior poll.",
|
|
604
578
|
buildStatement: (params) => `SHOW ROLES${likeClause(params.like)}`
|
|
605
579
|
});
|
|
606
580
|
const roleDroppedDefinition = buildLifecycleDefinition({
|
|
607
581
|
kind: "ROLE",
|
|
608
582
|
change: "dropped",
|
|
609
|
-
|
|
583
|
+
defaultId: "snowflake.role_dropped",
|
|
610
584
|
defaultDescription: "Emits every role that has disappeared from SHOW ROLES since the prior poll.",
|
|
611
585
|
buildStatement: (params) => `SHOW ROLES${likeClause(params.like)}`
|
|
612
586
|
});
|
|
@@ -628,14 +602,14 @@ function roleDropped(input) {
|
|
|
628
602
|
const databaseCreatedDefinition = buildLifecycleDefinition({
|
|
629
603
|
kind: "DATABASE",
|
|
630
604
|
change: "created",
|
|
631
|
-
|
|
605
|
+
defaultId: "snowflake.database_created",
|
|
632
606
|
defaultDescription: "Emits every new database observed via SHOW DATABASES since the prior poll.",
|
|
633
607
|
buildStatement: (params) => `SHOW DATABASES${likeClause(params.like)}`
|
|
634
608
|
});
|
|
635
609
|
const databaseDroppedDefinition = buildLifecycleDefinition({
|
|
636
610
|
kind: "DATABASE",
|
|
637
611
|
change: "dropped",
|
|
638
|
-
|
|
612
|
+
defaultId: "snowflake.database_dropped",
|
|
639
613
|
defaultDescription: "Emits every database that disappeared from SHOW DATABASES since the prior poll.",
|
|
640
614
|
buildStatement: (params) => `SHOW DATABASES${likeClause(params.like)}`
|
|
641
615
|
});
|
|
@@ -657,14 +631,14 @@ function databaseDropped(input) {
|
|
|
657
631
|
const tableCreatedDefinition = buildLifecycleDefinition({
|
|
658
632
|
kind: "TABLE",
|
|
659
633
|
change: "created",
|
|
660
|
-
|
|
634
|
+
defaultId: "snowflake.table_created",
|
|
661
635
|
defaultDescription: "Emits every new table observed via SHOW TABLES since the prior poll.",
|
|
662
636
|
buildStatement: (params) => `SHOW TABLES${likeClause(params.like)}${inScope(params)}`
|
|
663
637
|
});
|
|
664
638
|
const tableDroppedDefinition = buildLifecycleDefinition({
|
|
665
639
|
kind: "TABLE",
|
|
666
640
|
change: "dropped",
|
|
667
|
-
|
|
641
|
+
defaultId: "snowflake.table_dropped",
|
|
668
642
|
defaultDescription: "Emits every table that disappeared from SHOW TABLES since the prior poll.",
|
|
669
643
|
buildStatement: (params) => `SHOW TABLES${likeClause(params.like)}${inScope(params)}`
|
|
670
644
|
});
|
|
@@ -707,7 +681,7 @@ function signatureOf(row, fields) {
|
|
|
707
681
|
}
|
|
708
682
|
const shareChangedDefinition = defineSnowflakePollingTrigger({
|
|
709
683
|
responseSchema: shareChangedBatchSchema,
|
|
710
|
-
|
|
684
|
+
defaultId: "snowflake.share_changed",
|
|
711
685
|
defaultDescription: "Emits share create / drop / modify events observed via SHOW SHARES. Modifications diff against a signature of kind / owner / comment / accounts / listing.",
|
|
712
686
|
defaultSchedule: "5m",
|
|
713
687
|
poll: async (params, credentials, ctx) => {
|
|
@@ -764,9 +738,7 @@ const shareChangedDefinition = defineSnowflakePollingTrigger({
|
|
|
764
738
|
snapshot: toSnapshotEntries(current),
|
|
765
739
|
fetchedAt: detectedAt
|
|
766
740
|
};
|
|
767
|
-
}
|
|
768
|
-
idempotencyKey: (response) => sortedIdempotencyKey("share-changed", response.events),
|
|
769
|
-
hasPayload: (response) => response.events.length > 0
|
|
741
|
+
}
|
|
770
742
|
});
|
|
771
743
|
function shareChanged(input) {
|
|
772
744
|
return bindSnowflakePollingTrigger(shareChangedDefinition, {
|
|
@@ -787,7 +759,7 @@ function grantStatement(params) {
|
|
|
787
759
|
}
|
|
788
760
|
const grantsChangedDefinition = defineSnowflakePollingTrigger({
|
|
789
761
|
responseSchema: grantsChangedBatchSchema,
|
|
790
|
-
|
|
762
|
+
defaultId: "snowflake.grants_changed",
|
|
791
763
|
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
764
|
defaultSchedule: "5m",
|
|
793
765
|
poll: async (params, credentials, ctx) => {
|
|
@@ -826,9 +798,7 @@ const grantsChangedDefinition = defineSnowflakePollingTrigger({
|
|
|
826
798
|
snapshot: toSnapshotEntries(current),
|
|
827
799
|
fetchedAt: detectedAt
|
|
828
800
|
};
|
|
829
|
-
}
|
|
830
|
-
idempotencyKey: (response) => sortedIdempotencyKey("grants-changed", response.events),
|
|
831
|
-
hasPayload: (response) => response.events.length > 0
|
|
801
|
+
}
|
|
832
802
|
});
|
|
833
803
|
function buildGrantEvent(row, change, identity, detectedAt) {
|
|
834
804
|
return {
|
|
@@ -858,7 +828,7 @@ const warehouseQueueBatchSchema = z.object({
|
|
|
858
828
|
}).catchall(z.unknown());
|
|
859
829
|
const warehouseCreditsUsageDefinition = defineSnowflakePollingTrigger({
|
|
860
830
|
responseSchema: warehouseCreditsBatchSchema,
|
|
861
|
-
|
|
831
|
+
defaultId: "snowflake.warehouse_credits_usage",
|
|
862
832
|
defaultDescription: "Tails INFORMATION_SCHEMA.WAREHOUSE_METERING_HISTORY and emits per-warehouse credit buckets above an optional minimum.",
|
|
863
833
|
defaultSchedule: "5m",
|
|
864
834
|
poll: async (params, credentials, ctx) => {
|
|
@@ -893,13 +863,11 @@ const warehouseCreditsUsageDefinition = defineSnowflakePollingTrigger({
|
|
|
893
863
|
highWaterValue: readStringField(last ?? {}, "END_TIME", "end_time") ?? priorHighWater,
|
|
894
864
|
fetchedAt: isoNow()
|
|
895
865
|
};
|
|
896
|
-
}
|
|
897
|
-
idempotencyKey: (response) => `wh-credits:${response.highWaterValue ?? "initial"}:${response.events.length}`,
|
|
898
|
-
hasPayload: (response) => response.events.length > 0
|
|
866
|
+
}
|
|
899
867
|
});
|
|
900
868
|
const warehouseQueueDepthDefinition = defineSnowflakePollingTrigger({
|
|
901
869
|
responseSchema: warehouseQueueBatchSchema,
|
|
902
|
-
|
|
870
|
+
defaultId: "snowflake.warehouse_queue_depth",
|
|
903
871
|
defaultDescription: "Tails INFORMATION_SCHEMA.WAREHOUSE_LOAD_HISTORY and emits per-interval queue depth above an optional minimum.",
|
|
904
872
|
defaultSchedule: "5m",
|
|
905
873
|
poll: async (params, credentials, ctx) => {
|
|
@@ -935,9 +903,7 @@ const warehouseQueueDepthDefinition = defineSnowflakePollingTrigger({
|
|
|
935
903
|
highWaterValue: readStringField(last ?? {}, "END_TIME", "end_time") ?? priorHighWater,
|
|
936
904
|
fetchedAt: isoNow()
|
|
937
905
|
};
|
|
938
|
-
}
|
|
939
|
-
idempotencyKey: (response) => `wh-queue:${response.highWaterValue ?? "initial"}:${response.events.length}`,
|
|
940
|
-
hasPayload: (response) => response.events.length > 0
|
|
906
|
+
}
|
|
941
907
|
});
|
|
942
908
|
function warehouseCreditsUsage(input) {
|
|
943
909
|
return bindSnowflakePollingTrigger(warehouseCreditsUsageDefinition, input ?? {}, input);
|
|
@@ -974,7 +940,7 @@ function extractStatusPageOptions(params) {
|
|
|
974
940
|
}
|
|
975
941
|
const statusPageIncidentDefinition = {
|
|
976
942
|
responseSchema: statusPageIncidentBatchSchema,
|
|
977
|
-
|
|
943
|
+
defaultId: "snowflake.status_page_incident",
|
|
978
944
|
defaultDescription: "Polls https://status.snowflake.com/api/v2/incidents/unresolved.json and emits newly-appearing or status-changed incidents.",
|
|
979
945
|
defaultSchedule: "5m",
|
|
980
946
|
poll: async (params, _credentials, ctx) => {
|
|
@@ -1007,13 +973,11 @@ const statusPageIncidentDefinition = {
|
|
|
1007
973
|
snapshot: toSnapshotEntries(current),
|
|
1008
974
|
fetchedAt: detectedAt
|
|
1009
975
|
};
|
|
1010
|
-
}
|
|
1011
|
-
idempotencyKey: (response) => sortedIdempotencyKey("status-incident", response.events, (e) => `${e.id}:${e.updatedAt ?? ""}`),
|
|
1012
|
-
hasPayload: (response) => response.events.length > 0
|
|
976
|
+
}
|
|
1013
977
|
};
|
|
1014
978
|
const statusPageComponentChangeDefinition = {
|
|
1015
979
|
responseSchema: statusPageComponentChangeBatchSchema,
|
|
1016
|
-
|
|
980
|
+
defaultId: "snowflake.status_page_component_changed",
|
|
1017
981
|
defaultDescription: "Polls https://status.snowflake.com/api/v2/summary.json and emits a change event when any component transitions status.",
|
|
1018
982
|
defaultSchedule: "2m",
|
|
1019
983
|
poll: async (params, _credentials, ctx) => {
|
|
@@ -1045,15 +1009,13 @@ const statusPageComponentChangeDefinition = {
|
|
|
1045
1009
|
snapshot: toSnapshotEntries(current),
|
|
1046
1010
|
fetchedAt: detectedAt
|
|
1047
1011
|
};
|
|
1048
|
-
}
|
|
1049
|
-
idempotencyKey: (response) => sortedIdempotencyKey("status-component", response.events, (e) => `${e.id}:${e.currentStatus}`),
|
|
1050
|
-
hasPayload: (response) => response.events.length > 0
|
|
1012
|
+
}
|
|
1051
1013
|
};
|
|
1052
1014
|
/**
|
|
1053
1015
|
* Status-page pollers don't need Snowflake credentials, but they still
|
|
1054
1016
|
* flow through the Snowflake binding factory so they show up in the
|
|
1055
1017
|
* polling manifest alongside the rest. Credentials are declared but
|
|
1056
|
-
* unused — the poll callback ignores `ctx.credentials
|
|
1018
|
+
* unused — the poll callback ignores `ctx.credentials['keystroke:snowflake']`.
|
|
1057
1019
|
*/
|
|
1058
1020
|
function statusPageIncident(input) {
|
|
1059
1021
|
return bindSnowflakePollingTrigger(statusPageIncidentDefinition, input ?? {}, input);
|
|
@@ -1061,262 +1023,6 @@ function statusPageIncident(input) {
|
|
|
1061
1023
|
function statusPageComponentChanged(input) {
|
|
1062
1024
|
return bindSnowflakePollingTrigger(statusPageComponentChangeDefinition, input ?? {}, input);
|
|
1063
1025
|
}
|
|
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
1026
|
const polling = Object.freeze({
|
|
1321
1027
|
streamHasNewRows,
|
|
1322
1028
|
streamAppendOnly,
|
|
@@ -1341,23 +1047,6 @@ const polling = Object.freeze({
|
|
|
1341
1047
|
statusPageIncident,
|
|
1342
1048
|
statusPageComponentChanged
|
|
1343
1049
|
});
|
|
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
1050
|
|
|
1362
1051
|
//#endregion
|
|
1363
|
-
export {
|
|
1052
|
+
export { databaseCreated, databaseDropped, grantsChanged, pipeLoadCompleted, pipeLoadFailed, polling, roleCreated, roleDropped, scheduledDelta, scheduledSql, shareChanged, statementCompleted, statusPageComponentChanged, statusPageIncident, streamAppendOnly, streamHasNewRows, tableCreated, tableDropped, taskRunCompleted, userCreated, userDropped, warehouseCreditsUsage, warehouseQueueDepth };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@keystrokehq/snowflake",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.5",
|
|
4
4
|
"private": false,
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"type": "module",
|
|
@@ -147,27 +147,21 @@
|
|
|
147
147
|
"README.md",
|
|
148
148
|
"LICENSE"
|
|
149
149
|
],
|
|
150
|
-
"scripts": {
|
|
151
|
-
"typecheck": "tsgo --build",
|
|
152
|
-
"build": "tsdown",
|
|
153
|
-
"lint": "biome check .",
|
|
154
|
-
"test:unit": "vitest run --passWithNoTests --project unit",
|
|
155
|
-
"test:int": "vitest run --passWithNoTests --project int",
|
|
156
|
-
"prepublishOnly": "pnpm build && pnpm test:unit",
|
|
157
|
-
"lint:fix": "biome check --write ."
|
|
158
|
-
},
|
|
159
150
|
"dependencies": {
|
|
160
|
-
"@keystrokehq/integration-authoring": "^0.0.
|
|
161
|
-
"@keystrokehq/core": "^0.0.5",
|
|
151
|
+
"@keystrokehq/integration-authoring": "^0.0.5",
|
|
162
152
|
"zod": "^4.3.6"
|
|
163
153
|
},
|
|
154
|
+
"peerDependencies": {
|
|
155
|
+
"@keystrokehq/core": ">=0.0.7 <0.1.0"
|
|
156
|
+
},
|
|
164
157
|
"devDependencies": {
|
|
165
|
-
"@types/node": "
|
|
166
|
-
"
|
|
167
|
-
"
|
|
168
|
-
"
|
|
169
|
-
"
|
|
170
|
-
"
|
|
158
|
+
"@types/node": "^22.19.11",
|
|
159
|
+
"tsdown": "^0.20.3",
|
|
160
|
+
"typescript": "^5.9.3",
|
|
161
|
+
"vitest": "^4.0.18",
|
|
162
|
+
"@keystrokehq/core": "^0.0.7",
|
|
163
|
+
"@keystrokehq/test-utils": "0.0.0",
|
|
164
|
+
"@keystrokehq/typescript-config": "0.0.0"
|
|
171
165
|
},
|
|
172
166
|
"keywords": [
|
|
173
167
|
"snowflake",
|
|
@@ -186,5 +180,13 @@
|
|
|
186
180
|
"publishConfig": {
|
|
187
181
|
"access": "public",
|
|
188
182
|
"registry": "https://registry.npmjs.org/"
|
|
183
|
+
},
|
|
184
|
+
"scripts": {
|
|
185
|
+
"typecheck": "tsgo --build",
|
|
186
|
+
"build": "tsdown",
|
|
187
|
+
"lint": "biome check .",
|
|
188
|
+
"test:unit": "vitest run --passWithNoTests --project unit",
|
|
189
|
+
"test:int": "vitest run --passWithNoTests --project int",
|
|
190
|
+
"lint:fix": "biome check --write ."
|
|
189
191
|
}
|
|
190
|
-
}
|
|
192
|
+
}
|