@jskit-ai/connectors-catalog 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +192 -0
- package/docs/ai-upstream-notices.md +51 -0
- package/docs/ai.md +191 -0
- package/docs/airtable.md +130 -0
- package/docs/algolia.md +190 -0
- package/docs/amazon-redshift.md +229 -0
- package/docs/amplitude.md +210 -0
- package/docs/apify.md +162 -0
- package/docs/apollo-io.md +200 -0
- package/docs/asana.md +126 -0
- package/docs/ashby.md +188 -0
- package/docs/atlassian.md +224 -0
- package/docs/attention.md +162 -0
- package/docs/aws-athena.md +150 -0
- package/docs/aws-credentials.md +75 -0
- package/docs/aws-s3.md +193 -0
- package/docs/bigquery.md +146 -0
- package/docs/brevo.md +122 -0
- package/docs/calendly.md +158 -0
- package/docs/canva.md +191 -0
- package/docs/chargebee.md +192 -0
- package/docs/clay.md +194 -0
- package/docs/clickhouse.md +242 -0
- package/docs/confidence-exp.md +74 -0
- package/docs/confidence-flags.md +75 -0
- package/docs/confidence-oauth.md +137 -0
- package/docs/contentful.md +123 -0
- package/docs/databricks.md +180 -0
- package/docs/dbt-semantic-layer.md +175 -0
- package/docs/elevenlabs.md +102 -0
- package/docs/figma.md +132 -0
- package/docs/firebase-cloud-messaging.md +262 -0
- package/docs/firecrawl.md +137 -0
- package/docs/fireflies.md +95 -0
- package/docs/fireworks-ai.md +53 -0
- package/docs/gatewayapi.md +113 -0
- package/docs/gemini-enterprise.md +224 -0
- package/docs/github-api.md +133 -0
- package/docs/gitlab-api.md +138 -0
- package/docs/gmail.md +133 -0
- package/docs/gong.md +205 -0
- package/docs/google-ads.md +156 -0
- package/docs/google-analytics.md +202 -0
- package/docs/google-docs.md +105 -0
- package/docs/google-drive.md +108 -0
- package/docs/google-maps-platform.md +208 -0
- package/docs/google-oauth.md +123 -0
- package/docs/google-search-console.md +124 -0
- package/docs/google-sheets.md +94 -0
- package/docs/google-slides.md +95 -0
- package/docs/granola.md +178 -0
- package/docs/hex.md +161 -0
- package/docs/heygen.md +124 -0
- package/docs/hubspot.md +142 -0
- package/docs/incident-io.md +118 -0
- package/docs/inngest.md +178 -0
- package/docs/klipy.md +149 -0
- package/docs/lexware.md +144 -0
- package/docs/lightspeed.md +193 -0
- package/docs/linear.md +137 -0
- package/docs/linkedin.md +196 -0
- package/docs/logo-dev.md +152 -0
- package/docs/mailgun.md +153 -0
- package/docs/mapbox.md +174 -0
- package/docs/microsoft-excel.md +97 -0
- package/docs/microsoft-fabric.md +249 -0
- package/docs/microsoft-oauth.md +143 -0
- package/docs/microsoft-onedrive.md +76 -0
- package/docs/microsoft-onenote.md +75 -0
- package/docs/microsoft-outlook.md +90 -0
- package/docs/microsoft-powerpoint.md +119 -0
- package/docs/microsoft-sharepoint.md +75 -0
- package/docs/microsoft-teams.md +72 -0
- package/docs/microsoft-word.md +119 -0
- package/docs/miro.md +117 -0
- package/docs/n8n.md +169 -0
- package/docs/notion.md +137 -0
- package/docs/oura.md +161 -0
- package/docs/paddle.md +168 -0
- package/docs/perplexity.md +85 -0
- package/docs/pipedrive.md +120 -0
- package/docs/polar.md +63 -0
- package/docs/posthog.md +227 -0
- package/docs/prestashop.md +151 -0
- package/docs/replicate.md +60 -0
- package/docs/resend.md +153 -0
- package/docs/salesforce.md +234 -0
- package/docs/sanity.md +122 -0
- package/docs/semrush.md +276 -0
- package/docs/sentry.md +112 -0
- package/docs/sevdesk.md +107 -0
- package/docs/shopify.md +206 -0
- package/docs/slack.md +257 -0
- package/docs/snowflake.md +271 -0
- package/docs/storyblok.md +130 -0
- package/docs/stripe.md +77 -0
- package/docs/supabase.md +51 -0
- package/docs/tally.md +105 -0
- package/docs/telegram.md +165 -0
- package/docs/tiktok.md +201 -0
- package/docs/twilio.md +284 -0
- package/docs/twitch.md +428 -0
- package/docs/wave.md +440 -0
- package/docs/wix.md +1249 -0
- package/docs/wiz.md +177 -0
- package/docs/woocommerce.md +400 -0
- package/docs/wordpress-com.md +358 -0
- package/docs/wordpress-self-hosted.md +252 -0
- package/docs/workday.md +292 -0
- package/docs/x-twitter.md +188 -0
- package/docs/xero.md +429 -0
- package/docs/zoho-books.md +214 -0
- package/docs/zoho-crm.md +192 -0
- package/package.json +153 -0
- package/patterns/ai-connections/PATTERN.md +84 -0
- package/patterns/ai-connections/example/ai-model.js +11 -0
- package/patterns/ai-connections/example/perplexity-answer.js +29 -0
- package/patterns/api-key-connection/PATTERN.md +368 -0
- package/patterns/api-key-connection/example/connections.js +18 -0
- package/patterns/api-key-connection/example/integrations.json +19 -0
- package/patterns/assistant-mcp/PATTERN.md +110 -0
- package/patterns/assistant-mcp/example/integrations.json +13 -0
- package/patterns/assistant-mcp-oauth/PATTERN.md +235 -0
- package/patterns/assistant-mcp-oauth/example/integrations.json +102 -0
- package/patterns/aws-storage-queries/PATTERN.md +169 -0
- package/patterns/aws-storage-queries/example/formats/data-formats.js +35 -0
- package/patterns/aws-storage-queries/example/formats/package-lock.json +49 -0
- package/patterns/aws-storage-queries/example/formats/package.json +12 -0
- package/patterns/aws-storage-queries/example/formats/verify-formats.mjs +37 -0
- package/patterns/aws-storage-queries/example/integrations.json +38 -0
- package/patterns/aws-storage-queries/example/s3-transfer.js +34 -0
- package/patterns/event-delivery/PATTERN.md +151 -0
- package/patterns/event-delivery/example/integrations.json +17 -0
- package/patterns/firebase-messaging/PATTERN.md +156 -0
- package/patterns/firebase-messaging/example/server/notifications.js +23 -0
- package/patterns/google-ads-search/PATTERN.md +92 -0
- package/patterns/google-ads-search/example/ads-setup.js +25 -0
- package/patterns/oauth-connection/PATTERN.md +724 -0
- package/patterns/oauth-connection/example/integrations.json +20 -0
- package/patterns/paddle-catalogue/PATTERN.md +78 -0
- package/patterns/paddle-catalogue/example/create-products.js +38 -0
- package/patterns/public-image/PATTERN.md +84 -0
- package/patterns/public-image/example/integrations.json +13 -0
- package/patterns/public-image/example/logo-url.js +16 -0
- package/patterns/redshift-queries/PATTERN.md +150 -0
- package/patterns/redshift-queries/example/integrations.json +33 -0
- package/patterns/source-scanning/PATTERN.md +83 -0
- package/patterns/source-scanning/example/source-scanner.js +13 -0
- package/src/client/firebase-cloud-messaging.js +10 -0
- package/src/client/google-analytics.js +72 -0
- package/src/client/google-maps-platform.js +39 -0
- package/src/client/logo-dev.js +36 -0
- package/src/server/ai.js +46 -0
- package/src/server/airtable.js +67 -0
- package/src/server/algolia.js +63 -0
- package/src/server/amazon-redshift.js +96 -0
- package/src/server/amplitude.js +31 -0
- package/src/server/apify.js +68 -0
- package/src/server/apollo-io.js +85 -0
- package/src/server/asana.js +64 -0
- package/src/server/ashby.js +61 -0
- package/src/server/atlassian.js +32 -0
- package/src/server/attention.js +93 -0
- package/src/server/aws-athena.js +82 -0
- package/src/server/aws-s3.js +49 -0
- package/src/server/awsTransport.js +75 -0
- package/src/server/bigquery.js +97 -0
- package/src/server/brevo.js +97 -0
- package/src/server/calendly.js +77 -0
- package/src/server/canva.js +13 -0
- package/src/server/chargebee.js +88 -0
- package/src/server/clay.js +78 -0
- package/src/server/clickhouse.js +66 -0
- package/src/server/confidence.js +29 -0
- package/src/server/contentful.js +40 -0
- package/src/server/databricks.js +55 -0
- package/src/server/dbt-semantic-layer.js +95 -0
- package/src/server/elevenlabs.js +81 -0
- package/src/server/figma.js +24 -0
- package/src/server/firebase-cloud-messaging.js +111 -0
- package/src/server/firecrawl.js +70 -0
- package/src/server/fireflies.js +31 -0
- package/src/server/fireworks-ai.js +16 -0
- package/src/server/gatewayapi.js +48 -0
- package/src/server/gemini-enterprise.js +47 -0
- package/src/server/github-api.js +90 -0
- package/src/server/gitlab-api.js +67 -0
- package/src/server/gmail.js +60 -0
- package/src/server/gong.js +52 -0
- package/src/server/google-ads-search.js +134 -0
- package/src/server/google-ads.js +80 -0
- package/src/server/google-docs.js +36 -0
- package/src/server/google-drive.js +71 -0
- package/src/server/google-maps-platform.js +90 -0
- package/src/server/google-search-console.js +31 -0
- package/src/server/google-sheets.js +60 -0
- package/src/server/google-slides.js +36 -0
- package/src/server/google.js +61 -0
- package/src/server/granola.js +115 -0
- package/src/server/graphqlOperation.js +15 -0
- package/src/server/hex.js +29 -0
- package/src/server/heygen.js +54 -0
- package/src/server/hubspot.js +85 -0
- package/src/server/incident-io.js +68 -0
- package/src/server/inngest.js +86 -0
- package/src/server/jsonOperation.js +34 -0
- package/src/server/klipy.js +34 -0
- package/src/server/lexware.js +59 -0
- package/src/server/lightspeed.js +82 -0
- package/src/server/linear.js +52 -0
- package/src/server/linkedin.js +74 -0
- package/src/server/mailgun.js +76 -0
- package/src/server/mapbox.js +46 -0
- package/src/server/mcpRegistration.js +41 -0
- package/src/server/mcpTransport.js +60 -0
- package/src/server/microsoft-excel.js +67 -0
- package/src/server/microsoft-fabric.js +81 -0
- package/src/server/microsoft-onedrive.js +58 -0
- package/src/server/microsoft-onenote.js +52 -0
- package/src/server/microsoft-outlook.js +58 -0
- package/src/server/microsoft-powerpoint.js +5 -0
- package/src/server/microsoft-sharepoint.js +46 -0
- package/src/server/microsoft-teams.js +29 -0
- package/src/server/microsoft-word.js +5 -0
- package/src/server/microsoft.js +88 -0
- package/src/server/miro.js +29 -0
- package/src/server/n8n.js +78 -0
- package/src/server/notion.js +76 -0
- package/src/server/oura.js +48 -0
- package/src/server/paddle.js +83 -0
- package/src/server/perplexity.js +15 -0
- package/src/server/pipedrive.js +95 -0
- package/src/server/polar.js +20 -0
- package/src/server/posthog.js +44 -0
- package/src/server/prestashop.js +42 -0
- package/src/server/replicate.js +42 -0
- package/src/server/resend.js +93 -0
- package/src/server/salesforce.js +84 -0
- package/src/server/sanity.js +31 -0
- package/src/server/semrush.js +359 -0
- package/src/server/sentry.js +30 -0
- package/src/server/sevdesk.js +20 -0
- package/src/server/shopify.js +99 -0
- package/src/server/slack.js +143 -0
- package/src/server/snowflake.js +139 -0
- package/src/server/storyblok.js +49 -0
- package/src/server/stripe.js +13 -0
- package/src/server/supabase.js +13 -0
- package/src/server/tally.js +78 -0
- package/src/server/telegram.js +55 -0
- package/src/server/tiktok.js +86 -0
- package/src/server/twilio.js +90 -0
- package/src/server/twitch.js +409 -0
- package/src/server/wave.js +520 -0
- package/src/server/wix.js +1048 -0
- package/src/server/wiz.js +117 -0
- package/src/server/woocommerce.js +412 -0
- package/src/server/wordpress-com.js +236 -0
- package/src/server/wordpress-self-hosted.js +134 -0
- package/src/server/workday.js +136 -0
- package/src/server/x-twitter.js +110 -0
- package/src/server/xero.js +397 -0
- package/src/server/zoho-books.js +90 -0
- package/src/server/zoho-crm.js +89 -0
- package/src/server/zohoOAuth.js +21 -0
- package/src/shared/ai-models.json +8049 -0
- package/src/shared/ai.js +63 -0
- package/src/shared/algolia.js +29 -0
- package/src/shared/amazon-redshift.js +51 -0
- package/src/shared/amplitude.js +28 -0
- package/src/shared/atlassian.js +43 -0
- package/src/shared/aws.js +72 -0
- package/src/shared/canva.js +66 -0
- package/src/shared/chargebee.js +26 -0
- package/src/shared/clickhouse.js +38 -0
- package/src/shared/confidence.js +28 -0
- package/src/shared/databricks.js +33 -0
- package/src/shared/dbt-semantic-layer.js +32 -0
- package/src/shared/definitions.js +140 -0
- package/src/shared/figma.js +21 -0
- package/src/shared/firebase-cloud-messaging.js +45 -0
- package/src/shared/gemini-enterprise.js +33 -0
- package/src/shared/gong.js +33 -0
- package/src/shared/google-ads-search.js +31 -0
- package/src/shared/google-ads.js +28 -0
- package/src/shared/google-maps-platform.js +28 -0
- package/src/shared/google.js +133 -0
- package/src/shared/granola.js +25 -0
- package/src/shared/hex.js +28 -0
- package/src/shared/inngest.js +35 -0
- package/src/shared/lightspeed.js +75 -0
- package/src/shared/linkedin.js +27 -0
- package/src/shared/logo-dev.js +21 -0
- package/src/shared/mapbox.js +38 -0
- package/src/shared/mcp.js +92 -0
- package/src/shared/microsoft-fabric.js +47 -0
- package/src/shared/microsoft.js +231 -0
- package/src/shared/miro.js +27 -0
- package/src/shared/oura.js +23 -0
- package/src/shared/posthog.js +33 -0
- package/src/shared/prestashop.js +23 -0
- package/src/shared/salesforce.js +36 -0
- package/src/shared/semrush.js +24 -0
- package/src/shared/shopify.js +51 -0
- package/src/shared/siteUrl.js +12 -0
- package/src/shared/slack.js +88 -0
- package/src/shared/snowflake.js +55 -0
- package/src/shared/tiktok.js +22 -0
- package/src/shared/tokens.js +747 -0
- package/src/shared/twilio.js +41 -0
- package/src/shared/twitch.js +49 -0
- package/src/shared/wave.js +52 -0
- package/src/shared/wix.js +40 -0
- package/src/shared/wiz.js +41 -0
- package/src/shared/wordpress-com.js +30 -0
- package/src/shared/wordpress.js +69 -0
- package/src/shared/workday.js +50 -0
- package/src/shared/x-twitter.js +19 -0
- package/src/shared/xero.js +46 -0
- package/src/shared/zoho-books.js +39 -0
- package/src/shared/zoho-crm.js +43 -0
- package/src/shared/zoho-regions.js +12 -0
- package/test/ai.test.js +136 -0
- package/test/airtable.test.js +125 -0
- package/test/algolia.test.js +207 -0
- package/test/amazon-redshift.test.js +272 -0
- package/test/amplitude.test.js +239 -0
- package/test/apify.test.js +93 -0
- package/test/apollo-io.test.js +125 -0
- package/test/asana.test.js +99 -0
- package/test/ashby.test.js +97 -0
- package/test/atlassian.test.js +249 -0
- package/test/attention.test.js +79 -0
- package/test/aws.test.js +405 -0
- package/test/brevo.test.js +71 -0
- package/test/calendly.test.js +136 -0
- package/test/canva.test.js +258 -0
- package/test/chargebee.test.js +195 -0
- package/test/clickhouse.test.js +278 -0
- package/test/confidence.test.js +205 -0
- package/test/databricks.test.js +253 -0
- package/test/dbt-semantic-layer.test.js +221 -0
- package/test/designMcp.test.js +336 -0
- package/test/elevenlabs.test.js +52 -0
- package/test/firebase-cloud-messaging.test.js +242 -0
- package/test/firecrawl.test.js +66 -0
- package/test/gemini-enterprise.test.js +218 -0
- package/test/github-api.test.js +167 -0
- package/test/gitlab-api.test.js +160 -0
- package/test/gong.test.js +192 -0
- package/test/google-ads.test.js +350 -0
- package/test/google-maps-platform.test.js +191 -0
- package/test/google.test.js +710 -0
- package/test/granola.test.js +240 -0
- package/test/hex.test.js +253 -0
- package/test/heygen.test.js +121 -0
- package/test/hubspot.test.js +103 -0
- package/test/inngest.test.js +219 -0
- package/test/lightspeed.test.js +250 -0
- package/test/linear.test.js +146 -0
- package/test/linkedin.test.js +243 -0
- package/test/logo-dev.test.js +79 -0
- package/test/mailgun.test.js +107 -0
- package/test/mapbox.test.js +181 -0
- package/test/mcp.test.js +227 -0
- package/test/microsoft-fabric.test.js +258 -0
- package/test/microsoft.test.js +554 -0
- package/test/n8nOAuth.test.js +188 -0
- package/test/notionOAuth.test.js +183 -0
- package/test/oura.test.js +211 -0
- package/test/paddleCatalogue.test.js +51 -0
- package/test/paddlePermissions.test.js +101 -0
- package/test/perplexityComposition.test.js +39 -0
- package/test/pipedriveOAuth.test.js +140 -0
- package/test/posthog.test.js +200 -0
- package/test/prestashop.test.js +207 -0
- package/test/resend.test.js +163 -0
- package/test/salesforce.test.js +276 -0
- package/test/semrush.test.js +479 -0
- package/test/sentry.test.js +35 -0
- package/test/shopify.test.js +247 -0
- package/test/slack.test.js +320 -0
- package/test/snowflake.test.js +391 -0
- package/test/tally.test.js +75 -0
- package/test/telegram.test.js +58 -0
- package/test/tiktok.test.js +243 -0
- package/test/tokens.test.js +1079 -0
- package/test/twilio.test.js +221 -0
- package/test/twitch.test.js +573 -0
- package/test/wave.test.js +678 -0
- package/test/wix.test.js +1099 -0
- package/test/wiz.test.js +185 -0
- package/test/wordpress-com.test.js +469 -0
- package/test/wordpress.test.js +660 -0
- package/test/workday.test.js +314 -0
- package/test/x-twitter.test.js +256 -0
- package/test/xero.test.js +700 -0
- package/test/zoho-books.test.js +253 -0
- package/test/zoho-crm.test.js +223 -0
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { ConnectorError } from "@jskit-ai/connectors-core/server";
|
|
2
|
+
import { ashbyDefinition } from "../shared/tokens.js";
|
|
3
|
+
import { jsonOperation } from "./jsonOperation.js";
|
|
4
|
+
|
|
5
|
+
const text = { type: "string", minLength: 1, maxLength: 256 };
|
|
6
|
+
const uuid = { type: "string", required: true,
|
|
7
|
+
pattern: "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$" };
|
|
8
|
+
const page = { limit: { type: "integer", min: 1, max: 100, defaultTo: 25 },
|
|
9
|
+
cursor: { type: "string", minLength: 1, maxLength: 8192 }, syncToken: { type: "string", minLength: 1, maxLength: 8192 } };
|
|
10
|
+
const candidate = { name: text, email: { ...text, pattern: "^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$" }, phoneNumber: text };
|
|
11
|
+
function response(check) {
|
|
12
|
+
return result => {
|
|
13
|
+
if (result?.success === false) throw new ConnectorError("connector_provider_failed", "Ashby could not complete the operation.", { statusCode: 502 });
|
|
14
|
+
return result?.success === true && check(result);
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
const record = response(result => typeof result.results?.id === "string");
|
|
18
|
+
const paged = response(result => Array.isArray(result.results) && typeof result.moreDataAvailable === "boolean" &&
|
|
19
|
+
(!result.moreDataAvailable || typeof result.nextCursor === "string" && result.nextCursor.length > 0) &&
|
|
20
|
+
(result.syncToken === undefined || typeof result.syncToken === "string" && result.syncToken.length > 0));
|
|
21
|
+
const call = (endpoint, fields, validate = record) => jsonOperation(`https://api.ashbyhq.com/${endpoint}`, fields, validate, "POST");
|
|
22
|
+
|
|
23
|
+
const candidateUpdate = call("candidate.update", { candidateId: uuid, ...candidate,
|
|
24
|
+
sendNotifications: { type: "boolean", defaultTo: false } });
|
|
25
|
+
const ashbyProvider = Object.freeze({
|
|
26
|
+
...ashbyDefinition,
|
|
27
|
+
apiOrigins: ["https://api.ashbyhq.com"],
|
|
28
|
+
apiKey: { headers: (key) => {
|
|
29
|
+
if (/[:\s\p{Cc}]/u.test(key)) throw new ConnectorError("connector_binding_missing", "Use the Ashby API key without spaces, control characters or colons.");
|
|
30
|
+
return { Authorization: `Basic ${Buffer.from(`${key}:`).toString("base64")}`, Accept: "application/json; version=1" };
|
|
31
|
+
} },
|
|
32
|
+
checkOperation: "jobs.list",
|
|
33
|
+
operations: {
|
|
34
|
+
"jobs.list": call("job.list", page, paged),
|
|
35
|
+
"jobs.get": call("job.info", { id: uuid }),
|
|
36
|
+
"candidates.list": call("candidate.list", page, paged),
|
|
37
|
+
"candidates.get": call("candidate.info", { id: uuid }),
|
|
38
|
+
"candidates.create": call("candidate.create", { ...candidate, name: { ...text, required: true } }),
|
|
39
|
+
"candidates.update": { ...candidateUpdate, request(input) {
|
|
40
|
+
const request = candidateUpdate.request(input);
|
|
41
|
+
if (!["name", "email", "phoneNumber"].some(key => Object.hasOwn(request.body, key))) {
|
|
42
|
+
throw new ConnectorError("connector_input_invalid", "Supply at least one candidate profile change.", { statusCode: 422 });
|
|
43
|
+
}
|
|
44
|
+
return request;
|
|
45
|
+
} },
|
|
46
|
+
"applications.list": call("application.list", { ...page, jobId: { ...uuid, required: false },
|
|
47
|
+
status: { type: "string", enum: ["Hired", "Archived", "Active", "Lead"] }
|
|
48
|
+
}, paged),
|
|
49
|
+
"applications.get": call("application.info", { applicationId: uuid }),
|
|
50
|
+
"applications.create": call("application.create", { candidateId: uuid, jobId: uuid,
|
|
51
|
+
interviewPlanId: { ...uuid, required: false }, interviewStageId: { ...uuid, required: false }
|
|
52
|
+
}),
|
|
53
|
+
"applications.changeStage": call("application.changeStage", { applicationId: uuid, interviewStageId: uuid,
|
|
54
|
+
archiveReasonId: { ...uuid, required: false }
|
|
55
|
+
}),
|
|
56
|
+
"interviewPlans.list": call("interviewPlan.list", { ...page, includeArchived: { type: "boolean", defaultTo: false } }, paged),
|
|
57
|
+
"archiveReasons.list": call("archiveReason.list", { includeArchived: { type: "boolean", defaultTo: false } }, response(result => Array.isArray(result.results))),
|
|
58
|
+
"interviewStages.list": call("interviewStage.list", { interviewPlanId: uuid }, response(result => Array.isArray(result.results)))
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
export { ashbyProvider };
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { createSchema } from "json-rest-schema";
|
|
2
|
+
import { validateSchemaPayload } from "@jskit-ai/kernel/shared/validators";
|
|
3
|
+
import { atlassianDefinition } from "../shared/atlassian.js";
|
|
4
|
+
import { mcpOperations, exchangeMcp } from "./mcpTransport.js";
|
|
5
|
+
import { mcpRegistrationFields, registerMcpClient } from "./mcpRegistration.js";
|
|
6
|
+
|
|
7
|
+
const resource = "https://mcp.atlassian.com/v2/mcp";
|
|
8
|
+
const oauth = Object.freeze({
|
|
9
|
+
issuer: "https://auth.atlassian.com/VCeDsk8ZHncYF1g234fKtc4lNipbBhu3",
|
|
10
|
+
authorization_endpoint: "https://auth.atlassian.com/authorize",
|
|
11
|
+
token_endpoint: "https://auth.atlassian.com/oauth/token",
|
|
12
|
+
registration_endpoint: atlassianDefinition.setup.clientRegistrationEndpoint,
|
|
13
|
+
response_types_supported: ["code"]
|
|
14
|
+
});
|
|
15
|
+
const atlassianProvider = Object.freeze({
|
|
16
|
+
...atlassianDefinition, oauth, oauthResource: resource,
|
|
17
|
+
apiOrigins: ["https://mcp.atlassian.com"], checkOperation: "tools.list", exchange: exchangeMcp,
|
|
18
|
+
operations: mcpOperations(() => `${resource}?tools=all`)
|
|
19
|
+
});
|
|
20
|
+
const registrationSchema = createSchema({
|
|
21
|
+
...mcpRegistrationFields,
|
|
22
|
+
scopes: { type: "array", required: true, minLength: 1,
|
|
23
|
+
items: { type: "string", enum: atlassianDefinition.scopes.map((scope) => scope.value) },
|
|
24
|
+
validator: (scopes) => scopes.length > 0 && new Set(scopes).size === scopes.length || "Select at least one permission and include each once." }
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
async function registerAtlassianClient(input, options) {
|
|
28
|
+
const values = validateSchemaPayload({ schema: registrationSchema, mode: "replace" }, input, { statusCode: 422 });
|
|
29
|
+
return registerMcpClient("Atlassian", oauth, values, options);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export { atlassianProvider, registerAtlassianClient };
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { ConnectorError } from "@jskit-ai/connectors-core/server";
|
|
2
|
+
import { createSchema } from "json-rest-schema";
|
|
3
|
+
import { validateSchemaPayload } from "@jskit-ai/kernel/shared/validators";
|
|
4
|
+
import { attentionDefinition } from "../shared/tokens.js";
|
|
5
|
+
import { jsonOperation } from "./jsonOperation.js";
|
|
6
|
+
|
|
7
|
+
const text = { type: "string", minLength: 1, maxLength: 2000 };
|
|
8
|
+
const id = { type: "string", minLength: 1, maxLength: 100, pattern: "^[A-Za-z0-9_-]+$" };
|
|
9
|
+
const requiredId = { ...id, required: true };
|
|
10
|
+
const ids = { type: "array", items: id, validator: value => value.length <= 100 || "Use at most 100 IDs." };
|
|
11
|
+
const bool = { type: "boolean" };
|
|
12
|
+
const page = { page: { type: "integer", min: 1, defaultTo: 1 }, size: { type: "integer", min: 1, max: 200, defaultTo: 50 } };
|
|
13
|
+
const dateTime = { ...text, validator: value => /^\d{4}-\d{2}-\d{2}T/.test(value) && Number.isFinite(Date.parse(value)) || "Use an ISO timestamp." };
|
|
14
|
+
const list = result => Array.isArray(result?.data);
|
|
15
|
+
const record = result => result?.data !== null && typeof result?.data === "object" && !Array.isArray(result.data) && typeof (result.data.uuid ?? result.data.id) === "string";
|
|
16
|
+
const conversation = result => result?.type === "conversations" && typeof result.id === "string" && result.attributes !== null && typeof result.attributes === "object" && !Array.isArray(result.attributes);
|
|
17
|
+
function operation(method, endpoint, fields, validateResult, prepare = value => value) {
|
|
18
|
+
const schema = createSchema(fields);
|
|
19
|
+
return { scopes: [], request(input) {
|
|
20
|
+
const values = prepare(validateSchemaPayload({ schema, mode: "replace" }, input, { statusCode: 422 }));
|
|
21
|
+
const { id: resource, ...body } = values;
|
|
22
|
+
const url = new URL(`https://api.attention.tech/v2/${endpoint.replace("{id}", resource || "")}`);
|
|
23
|
+
if (method === "GET") { for (const [key, value] of Object.entries(body)) url.searchParams.set(key, String(value)); return { method, url: url.href }; }
|
|
24
|
+
if (method === "DELETE") return { method, url: url.href };
|
|
25
|
+
if (!Object.keys(body).length) throw new ConnectorError("connector_input_invalid", "Supply at least one change.", { statusCode: 422 });
|
|
26
|
+
return { method, url: url.href, body };
|
|
27
|
+
}, validateResult };
|
|
28
|
+
}
|
|
29
|
+
const team = { type: "object", schema: createSchema({ uuid: requiredId, primary: bool }) };
|
|
30
|
+
const attentionProvider = Object.freeze({
|
|
31
|
+
...attentionDefinition,
|
|
32
|
+
apiOrigins: ["https://api.attention.tech"],
|
|
33
|
+
apiKey: { headers: (key) => ({ Authorization: `Bearer ${key}` }) },
|
|
34
|
+
checkOperation: "conversations.list",
|
|
35
|
+
operations: {
|
|
36
|
+
"conversations.get": operation("GET", "conversations/{id}", { id: requiredId, by: { type: "string", enum: ["id", "external_id"] },
|
|
37
|
+
detailedTranscript: { ...bool, defaultTo: true }, "filter[include_internal_participants]": bool,
|
|
38
|
+
"filter[include_zoom_metadata]": bool, "filter[include_import_metadata]": bool }, conversation),
|
|
39
|
+
"conversations.update": operation("PUT", "conversations/{id}", { id: requiredId, title: text, labels: { type: "object", additionalProperties: true } }, conversation),
|
|
40
|
+
"conversations.archive": operation("DELETE", "conversations/{id}", { id: requiredId }, conversation),
|
|
41
|
+
"conversations.import": operation("POST", "conversations/import", {
|
|
42
|
+
mediaURL: { ...text, required: true, validator: value => { try { const url = new URL(value); return url.protocol === "https:" && !url.username && !url.password || "Use an HTTPS media URL without embedded credentials."; } catch { return "Use an HTTPS media URL."; } } },
|
|
43
|
+
userID: requiredId, applicationName: text, applicationExternalID: id, conversationTitle: text,
|
|
44
|
+
conversationStartedAt: dateTime, opportunityID: id, skipScorecardCalculation: bool,
|
|
45
|
+
skipCRMFieldsCalculation: bool, skipOpportunitiesExport: bool
|
|
46
|
+
}, result => typeof result?.uuid === "string"),
|
|
47
|
+
"snippets.create": operation("POST", "snippets", { user_uuid: requiredId, conversation_id: requiredId,
|
|
48
|
+
internal: { ...bool, required: true }, notify_views: { ...bool, required: true }, title: text, notes: { type: "string", maxLength: 20000 },
|
|
49
|
+
video: { type: "object", required: true, schema: createSchema({ start_time: { type: "number", required: true, min: 0 }, end_time: { type: "number", required: true, min: 0 } }),
|
|
50
|
+
validator: value => value.end_time > value.start_time || "End time must follow start time." }
|
|
51
|
+
}, result => typeof result?.id === "string" && typeof result.url === "string"),
|
|
52
|
+
"analysis.ask": operation("POST", "ask_attention/v2", { conversations_ids: { ...ids, required: true },
|
|
53
|
+
deal_id: { type: "string", required: true, maxLength: 100 }, prompt: { ...text, required: true, maxLength: 20000 }
|
|
54
|
+
}, result => Array.isArray(result) && result.every(item => typeof item?.output === "string" && typeof item.error === "string" && typeof item.conversation_id === "string")),
|
|
55
|
+
"scorecards.list": operation("GET", "scorecards", page, list),
|
|
56
|
+
"scorecards.get": operation("GET", "scorecards/{id}", { id: requiredId }, record),
|
|
57
|
+
"scorecardItems.list": operation("GET", "scorecards/{id}/items", { id: requiredId, ...page }, list),
|
|
58
|
+
"scorecards.summary": operation("POST", "scorecards/summary", { fromDateTime: { ...dateTime, required: true }, toDateTime: { ...dateTime, required: true },
|
|
59
|
+
scorecardUUID: requiredId, scorecardsItemsUUIDs: { ...ids, required: true }, teamUUIDs: { ...ids, required: true }, userUUIDs: { ...ids, required: true }
|
|
60
|
+
}, list, value => { if (Date.parse(value.fromDateTime) >= Date.parse(value.toDateTime)) throw new ConnectorError("connector_input_invalid", "End time must follow start time.", { statusCode: 422 }); return value; }),
|
|
61
|
+
"scorecardResults.create": operation("POST", "createScorecardResult", { scorecard_uuid: requiredId, conversation_uuid: requiredId,
|
|
62
|
+
summary: { ...text, required: true, maxLength: 20000 }, items: { type: "array", required: true,
|
|
63
|
+
items: { type: "object", schema: createSchema({ scorecard_item_uuid: requiredId, numeric_result: { type: "integer" }, description: { ...text, required: true } }) },
|
|
64
|
+
validator: value => value.length > 0 && value.length <= 100 || "Supply 1–100 scoring items." }
|
|
65
|
+
}, result => result?.success === true),
|
|
66
|
+
"users.list": operation("GET", "organizations/users", { teamUUID: id }, list),
|
|
67
|
+
"roles.list": operation("GET", "organizations/roles", {}, list),
|
|
68
|
+
"users.create": operation("POST", "organizations/users", { email: { ...text, required: true, pattern: "^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$" },
|
|
69
|
+
first_name: { ...text, required: true }, last_name: { ...text, required: true }, roleUUID: requiredId,
|
|
70
|
+
seat_type: { type: "string", required: true, enum: ["listener", "recording"] },
|
|
71
|
+
teams: { type: "array", required: true, items: team, validator: value => value.length > 0 && value.length <= 100 || "Supply 1–100 teams." }
|
|
72
|
+
}, record),
|
|
73
|
+
"users.update": operation("PATCH", "organizations/users/{id}", { id: requiredId, firstName: text, lastName: text,
|
|
74
|
+
roleUUID: id, seat_type: { type: "string", enum: ["listener", "recording"] }, teamUUIDsToRemove: ids,
|
|
75
|
+
teamsToAdd: { type: "array", items: team, validator: value => value.length <= 100 || "Use at most 100 teams." }
|
|
76
|
+
}, record),
|
|
77
|
+
"users.delete": operation("DELETE", "organizations/users/{id}", { id: requiredId }, result => result === null || result !== null && typeof result === "object" && !Array.isArray(result) && Object.keys(result).length === 0),
|
|
78
|
+
"teams.list": operation("GET", "organizations/teams", {}, list),
|
|
79
|
+
"teams.create": operation("POST", "organizations/teams", { name: { ...text, required: true }, parentTeamUUID: id }, record),
|
|
80
|
+
"teams.update": operation("PATCH", "organizations/teams/{id}", { id: requiredId, name: text, parentTeamUUID: id }, record),
|
|
81
|
+
"conversations.list": jsonOperation("https://api.attention.tech/v2/conversations/list", {
|
|
82
|
+
page: { type: "integer", min: 1, max: 100000, defaultTo: 1 },
|
|
83
|
+
size: { type: "integer", min: 1, max: 50, defaultTo: 20 },
|
|
84
|
+
"filter[title]": { type: "string", minLength: 1, maxLength: 500 },
|
|
85
|
+
"filter[hide_internal]": { type: "boolean" },
|
|
86
|
+
detailedTranscript: { type: "boolean", defaultTo: false }
|
|
87
|
+
}, (result) => Array.isArray(result?.data) && (result.meta === undefined ||
|
|
88
|
+
result.meta !== null && typeof result.meta === "object" && !Array.isArray(result.meta) &&
|
|
89
|
+
["totalRecords", "pageCount", "pageNumber", "pageSize"].every((field) => result.meta[field] === undefined ||
|
|
90
|
+
Number.isInteger(result.meta[field]) && result.meta[field] >= 0)))
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
export { attentionProvider };
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { AthenaClient, GetWorkGroupCommand, StartQueryExecutionCommand, GetQueryExecutionCommand, GetQueryResultsCommand, StopQueryExecutionCommand, ListDataCatalogsCommand, ListDatabasesCommand, ListTableMetadataCommand } from "@aws-sdk/client-athena";
|
|
2
|
+
import { ConnectorError } from "@jskit-ai/connectors-core/server";
|
|
3
|
+
import { awsAthenaDefinition } from "../shared/aws.js";
|
|
4
|
+
import { awsOrigin, awsOperation, awsExchange } from "./awsTransport.js";
|
|
5
|
+
|
|
6
|
+
const queryId = { type: "string", required: true, minLength: 1, maxLength: 128, pattern: "^\\S+$" };
|
|
7
|
+
const validQuery = (r) => typeof r?.QueryExecution?.QueryExecutionId === "string" && typeof r.QueryExecution.WorkGroup === "string" &&
|
|
8
|
+
["QUEUED", "RUNNING", "SUCCEEDED", "FAILED", "CANCELLED"].includes(r.QueryExecution.Status?.State);
|
|
9
|
+
const metadataPage = {
|
|
10
|
+
maxResults: { type: "integer", min: 1, max: 50, defaultTo: 50 },
|
|
11
|
+
nextToken: { type: "string", noTrim: true, minLength: 1, maxLength: 1024 }
|
|
12
|
+
};
|
|
13
|
+
const catalog = { type: "string", required: true, noTrim: true, minLength: 1, maxLength: 256 };
|
|
14
|
+
const metadataInput = (v, s) => ({ input: { WorkGroup: s.workgroup, MaxResults: v.maxResults,
|
|
15
|
+
...(v.nextToken ? { NextToken: v.nextToken } : {}),
|
|
16
|
+
...(v.catalog ? { CatalogName: v.catalog } : {}),
|
|
17
|
+
...(v.database ? { DatabaseName: v.database } : {}) } });
|
|
18
|
+
const validCursor = (r) => r.NextToken === undefined || typeof r.NextToken === "string" && r.NextToken.length > 0 && r.NextToken.length <= 1024;
|
|
19
|
+
const validColumns = (columns) => columns === undefined || Array.isArray(columns) && columns.every((c) => typeof c?.Name === "string" && typeof c.Type === "string");
|
|
20
|
+
const queryInput = (v) => ({ input: { QueryExecutionId: v.queryId } });
|
|
21
|
+
const awsAthenaProvider = Object.freeze({
|
|
22
|
+
...awsAthenaDefinition,
|
|
23
|
+
apiOrigins: (settings) => [awsOrigin("athena", settings)], apiKey: { headers: () => ({}) }, checkOperation: "workgroup.get",
|
|
24
|
+
exchange: awsExchange("athena", AthenaClient, async (client, body, { settings, signal }) => {
|
|
25
|
+
const send = (command) => client.send(command, { abortSignal: signal });
|
|
26
|
+
if (body.command === "GetWorkGroup") {
|
|
27
|
+
const result = await send(new GetWorkGroupCommand(body.input));
|
|
28
|
+
if (result.WorkGroup?.Name !== settings.workgroup) throw new ConnectorError("connector_response_invalid", "Athena returned a different workgroup.", { statusCode: 502 });
|
|
29
|
+
return result;
|
|
30
|
+
}
|
|
31
|
+
if (body.command === "ListDataCatalogs") return send(new ListDataCatalogsCommand(body.input));
|
|
32
|
+
if (body.command === "ListDatabases") return send(new ListDatabasesCommand(body.input));
|
|
33
|
+
if (body.command === "ListTableMetadata") return send(new ListTableMetadataCommand(body.input));
|
|
34
|
+
if (body.command === "StartQueryExecution") return send(new StartQueryExecutionCommand(body.input));
|
|
35
|
+
const query = await send(new GetQueryExecutionCommand({ QueryExecutionId: body.input.QueryExecutionId }));
|
|
36
|
+
if (!validQuery(query) || query.QueryExecution.QueryExecutionId !== body.input.QueryExecutionId) {
|
|
37
|
+
throw new ConnectorError("connector_response_invalid", "Athena returned an unexpected query.", { statusCode: 502 });
|
|
38
|
+
}
|
|
39
|
+
if (query.QueryExecution.WorkGroup !== settings.workgroup) {
|
|
40
|
+
throw new ConnectorError("connector_access_denied", "This query belongs to a different workgroup.", { statusCode: 403 });
|
|
41
|
+
}
|
|
42
|
+
if (body.command === "GetQueryExecution") return query;
|
|
43
|
+
if (body.command === "GetQueryResults") return send(new GetQueryResultsCommand(body.input));
|
|
44
|
+
return send(new StopQueryExecutionCommand(body.input));
|
|
45
|
+
}),
|
|
46
|
+
operations: {
|
|
47
|
+
"catalogs.list": awsOperation("athena", "ListDataCatalogs", {
|
|
48
|
+
...metadataPage, maxResults: { ...metadataPage.maxResults, min: 2 }
|
|
49
|
+
}, metadataInput, (r) => Array.isArray(r?.DataCatalogsSummary) && r.DataCatalogsSummary.length <= 50 &&
|
|
50
|
+
r.DataCatalogsSummary.every((c) => typeof c?.CatalogName === "string" && typeof c.Type === "string") && validCursor(r)),
|
|
51
|
+
"databases.list": awsOperation("athena", "ListDatabases", { ...metadataPage, catalog }, metadataInput,
|
|
52
|
+
(r) => Array.isArray(r?.DatabaseList) && r.DatabaseList.length <= 50 &&
|
|
53
|
+
r.DatabaseList.every((d) => typeof d?.Name === "string") && validCursor(r)),
|
|
54
|
+
"tables.list": awsOperation("athena", "ListTableMetadata", { ...metadataPage, catalog,
|
|
55
|
+
database: { type: "string", required: true, noTrim: true, minLength: 1, maxLength: 128 }
|
|
56
|
+
}, metadataInput, (r) => Array.isArray(r?.TableMetadataList) && r.TableMetadataList.length <= 50 &&
|
|
57
|
+
r.TableMetadataList.every((t) => typeof t?.Name === "string" && validColumns(t.Columns) && validColumns(t.PartitionKeys)) && validCursor(r)),
|
|
58
|
+
"workgroup.get": awsOperation("athena", "GetWorkGroup", {}, (_, s) => ({ input: { WorkGroup: s.workgroup } }),
|
|
59
|
+
(r) => typeof r?.WorkGroup?.Name === "string" && ["ENABLED", "DISABLED"].includes(r.WorkGroup.State)),
|
|
60
|
+
"query.start": awsOperation("athena", "StartQueryExecution", {
|
|
61
|
+
sql: { type: "string", required: true, noTrim: true, minLength: 1, maxLength: 262144,
|
|
62
|
+
validator: (value) => (value.trim().length > 0 && Buffer.byteLength(value, "utf8") <= 262144) || "Enter SQL of at most 262144 UTF-8 bytes." },
|
|
63
|
+
clientRequestToken: { type: "string", required: true, noTrim: true, minLength: 32, maxLength: 128 },
|
|
64
|
+
database: { type: "string", minLength: 1, maxLength: 255 }, catalog: { type: "string", minLength: 1, maxLength: 256 }
|
|
65
|
+
}, (v, s) => ({ input: { QueryString: v.sql, ClientRequestToken: v.clientRequestToken, WorkGroup: s.workgroup,
|
|
66
|
+
...(v.database || v.catalog ? { QueryExecutionContext: { ...(v.database ? { Database: v.database } : {}), ...(v.catalog ? { Catalog: v.catalog } : {}) } } : {}),
|
|
67
|
+
...(s.resultLocation ? { ResultConfiguration: { OutputLocation: s.resultLocation } } : {}) } }),
|
|
68
|
+
(r) => typeof r?.QueryExecutionId === "string" && /^\S{1,128}$/u.test(r.QueryExecutionId)),
|
|
69
|
+
"query.status": awsOperation("athena", "GetQueryExecution", { queryId }, queryInput, validQuery),
|
|
70
|
+
"query.results": awsOperation("athena", "GetQueryResults", { queryId,
|
|
71
|
+
maxResults: { type: "integer", min: 1, max: 1000, defaultTo: 100 }, nextToken: { type: "string", noTrim: true, minLength: 1, maxLength: 1024 }
|
|
72
|
+
}, (v) => ({ input: { QueryExecutionId: v.queryId, MaxResults: v.maxResults, QueryResultType: "DATA_ROWS", ...(v.nextToken ? { NextToken: v.nextToken } : {}) } }),
|
|
73
|
+
(r) => Array.isArray(r?.ResultSet?.ResultSetMetadata?.ColumnInfo) &&
|
|
74
|
+
r.ResultSet.ResultSetMetadata.ColumnInfo.every((c) => typeof c.Name === "string" && typeof c.Type === "string") &&
|
|
75
|
+
Array.isArray(r.ResultSet.Rows) && r.ResultSet.Rows.length <= 1000 &&
|
|
76
|
+
r.ResultSet.Rows.every((row) => Array.isArray(row.Data) && row.Data.every((cell) => cell && (cell.VarCharValue === undefined || typeof cell.VarCharValue === "string"))) &&
|
|
77
|
+
(r.NextToken === undefined || typeof r.NextToken === "string" && r.NextToken.length > 0)),
|
|
78
|
+
"query.cancel": awsOperation("athena", "StopQueryExecution", { queryId }, queryInput, (r) => r && Object.keys(r).length === 0)
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
export { awsAthenaProvider };
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { S3Client, ListObjectsV2Command, GetObjectCommand, PutObjectCommand } from "@aws-sdk/client-s3";
|
|
2
|
+
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
|
|
3
|
+
import { ConnectorError } from "@jskit-ai/connectors-core/server";
|
|
4
|
+
import { awsS3Definition } from "../shared/aws.js";
|
|
5
|
+
import { awsOrigin, awsOperation, awsExchange } from "./awsTransport.js";
|
|
6
|
+
|
|
7
|
+
const key = { type: "string", required: true, noTrim: true, minLength: 1, maxLength: 1024,
|
|
8
|
+
validator: (value) => (Buffer.byteLength(value, "utf8") <= 1024 && !value.split("/").some((part) => part === "." || part === "..")) || "Use an object key of at most 1024 UTF-8 bytes, without standalone . or .. path segments." };
|
|
9
|
+
const expires = { type: "integer", min: 1, max: 900, defaultTo: 300 };
|
|
10
|
+
const validUrl = (result) => typeof result?.url === "string" && Number.isInteger(result.expiresInSeconds) && ["GET", "PUT"].includes(result.method);
|
|
11
|
+
const awsS3Provider = Object.freeze({
|
|
12
|
+
...awsS3Definition,
|
|
13
|
+
apiOrigins: (settings) => [awsOrigin("s3", settings)],
|
|
14
|
+
apiKey: { headers: () => ({}) },
|
|
15
|
+
checkOperation: "objects.list",
|
|
16
|
+
exchange: awsExchange("s3", S3Client, async (client, body, { settings, signal }) => {
|
|
17
|
+
if (body.command === "ListObjectsV2") {
|
|
18
|
+
const result = await client.send(new ListObjectsV2Command(body.input), { abortSignal: signal });
|
|
19
|
+
if (result.Name !== settings.bucket || (result.Contents?.length || 0) > body.input.MaxKeys) {
|
|
20
|
+
throw new ConnectorError("connector_response_invalid", "S3 returned an unexpected bucket or page size.", { statusCode: 502 });
|
|
21
|
+
}
|
|
22
|
+
return result;
|
|
23
|
+
}
|
|
24
|
+
const upload = body.command === "PutObject";
|
|
25
|
+
const command = upload ? new PutObjectCommand(body.input) : new GetObjectCommand(body.input);
|
|
26
|
+
const url = await getSignedUrl(client, command, { expiresIn: body.expiresInSeconds });
|
|
27
|
+
return { url, method: upload ? "PUT" : "GET", expiresInSeconds: body.expiresInSeconds };
|
|
28
|
+
}),
|
|
29
|
+
operations: {
|
|
30
|
+
"objects.list": awsOperation("s3", "ListObjectsV2", {
|
|
31
|
+
maxKeys: { type: "integer", min: 1, max: 1000, defaultTo: 100 },
|
|
32
|
+
prefix: { type: "string", noTrim: true, maxLength: 1024 },
|
|
33
|
+
delimiter: { type: "string", enum: ["/"] },
|
|
34
|
+
continuationToken: { type: "string", noTrim: true, minLength: 1, maxLength: 8192 }
|
|
35
|
+
}, (v, s) => ({ input: { Bucket: s.bucket, MaxKeys: v.maxKeys, EncodingType: "url",
|
|
36
|
+
...(v.prefix === undefined ? {} : { Prefix: v.prefix }), ...(v.delimiter ? { Delimiter: v.delimiter } : {}),
|
|
37
|
+
...(v.continuationToken ? { ContinuationToken: v.continuationToken } : {}) } }),
|
|
38
|
+
(r) => typeof r?.Name === "string" && typeof r.IsTruncated === "boolean" && Number.isInteger(r.KeyCount) && r.KeyCount >= 0 &&
|
|
39
|
+
(!r.IsTruncated || typeof r.NextContinuationToken === "string" && r.NextContinuationToken.length > 0) &&
|
|
40
|
+
(r.Contents === undefined || Array.isArray(r.Contents) && r.Contents.every((o) => typeof o.Key === "string" && Number.isInteger(o.Size) && o.Size >= 0)) &&
|
|
41
|
+
(r.CommonPrefixes === undefined || Array.isArray(r.CommonPrefixes) && r.CommonPrefixes.every((p) => typeof p.Prefix === "string")), ["read"]),
|
|
42
|
+
"objects.downloadUrl": awsOperation("s3", "GetObject", { key, expiresInSeconds: expires },
|
|
43
|
+
(v, s) => ({ input: { Bucket: s.bucket, Key: v.key }, expiresInSeconds: v.expiresInSeconds }), validUrl, ["read"]),
|
|
44
|
+
"objects.uploadUrl": awsOperation("s3", "PutObject", { key, expiresInSeconds: expires },
|
|
45
|
+
(v, s) => ({ input: { Bucket: s.bucket, Key: v.key }, expiresInSeconds: v.expiresInSeconds }), validUrl, ["write"])
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
export { awsS3Provider };
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { createSchema } from "json-rest-schema";
|
|
2
|
+
import { validateSchemaPayload } from "@jskit-ai/kernel/shared/validators";
|
|
3
|
+
import { FetchHttpHandler } from "@smithy/fetch-http-handler";
|
|
4
|
+
import { ConnectorError } from "@jskit-ai/connectors-core/server";
|
|
5
|
+
|
|
6
|
+
const awsOrigin = (service, settings) => `https://${service}.${settings.region}.amazonaws.com`;
|
|
7
|
+
|
|
8
|
+
function awsOperation(service, command, fields, makeInput, validateResult, scopes = []) {
|
|
9
|
+
const schema = createSchema(fields);
|
|
10
|
+
return {
|
|
11
|
+
scopes,
|
|
12
|
+
request(input, settings) {
|
|
13
|
+
const values = validateSchemaPayload({ schema, mode: "replace" }, input, { statusCode: 422 });
|
|
14
|
+
return { method: "POST", url: awsOrigin(service, settings), body: { command, ...makeInput(values, settings) } };
|
|
15
|
+
},
|
|
16
|
+
validateResult
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function awsExchange(service, Client, execute) {
|
|
21
|
+
return async (endpoint, options, { settings, apiKey, resolveReference, fetchImpl }) => {
|
|
22
|
+
let credentials;
|
|
23
|
+
try {
|
|
24
|
+
const accessKeyId = await resolveReference(settings.accessKeyIdRef);
|
|
25
|
+
const sessionToken = settings.sessionTokenRef ? await resolveReference(settings.sessionTokenRef) : undefined;
|
|
26
|
+
if (typeof accessKeyId !== "string" || !/^[A-Z0-9]{16,128}$/u.test(accessKeyId) || !apiKey ||
|
|
27
|
+
(settings.sessionTokenRef && (typeof sessionToken !== "string" || !sessionToken || /\s/u.test(sessionToken))) ||
|
|
28
|
+
(accessKeyId.startsWith("ASIA") && !sessionToken)) throw new Error("Invalid credentials");
|
|
29
|
+
credentials = { accessKeyId, secretAccessKey: apiKey, ...(sessionToken ? { sessionToken } : {}) };
|
|
30
|
+
} catch {
|
|
31
|
+
throw new ConnectorError("connector_binding_missing", "Provide the AWS access key ID, secret access key and any required session token bindings.");
|
|
32
|
+
}
|
|
33
|
+
const origin = awsOrigin(service, settings);
|
|
34
|
+
if (new URL(endpoint).origin !== origin) throw new ConnectorError("connector_destination_invalid", "The AWS region does not match the operation destination.");
|
|
35
|
+
const client = new Client({ region: settings.region, endpoint: origin, credentials, maxAttempts: 1,
|
|
36
|
+
...(service === "s3" ? { forcePathStyle: true, followRegionRedirects: false, requestChecksumCalculation: "WHEN_REQUIRED" } : {}),
|
|
37
|
+
requestHandler: new FetchHttpHandler({
|
|
38
|
+
requestInit: () => ({ credentials: "omit", redirect: "error" }),
|
|
39
|
+
customFetch: async (request) => {
|
|
40
|
+
if (new URL(request.url).origin !== origin) throw new ConnectorError("connector_destination_invalid", "The AWS SDK selected an unexpected destination.");
|
|
41
|
+
options.signal.throwIfAborted();
|
|
42
|
+
return fetchImpl(request.url, { method: request.method, headers: request.headers,
|
|
43
|
+
...(request.body ? { body: await request.text() } : {}), signal: options.signal, credentials: "omit", redirect: "error" });
|
|
44
|
+
}
|
|
45
|
+
})
|
|
46
|
+
});
|
|
47
|
+
try {
|
|
48
|
+
options.signal.throwIfAborted();
|
|
49
|
+
const result = await execute(client, options.body, { settings, signal: options.signal });
|
|
50
|
+
options.signal.throwIfAborted();
|
|
51
|
+
const { $metadata, ...data } = result;
|
|
52
|
+
return data;
|
|
53
|
+
} catch (error) {
|
|
54
|
+
options.signal.throwIfAborted();
|
|
55
|
+
if (error instanceof ConnectorError || ["AbortError", "TimeoutError"].includes(error.name)) throw error;
|
|
56
|
+
const status = error.$metadata?.httpStatusCode;
|
|
57
|
+
if (["ExpiredToken", "ExpiredTokenException", "InvalidAccessKeyId", "InvalidClientTokenId", "UnrecognizedClientException", "InvalidToken", "SignatureDoesNotMatch"].includes(error.name) || status === 401) {
|
|
58
|
+
throw new ConnectorError("connector_reconnect_required", "Renew the AWS credential bindings and connect again.", { statusCode: 401 });
|
|
59
|
+
}
|
|
60
|
+
if (["TooManyRequestsException", "ThrottlingException", "SlowDown", "RequestLimitExceeded", "ActiveStatementsExceededException", "ActiveSessionsExceededException", "ActiveWaitingRequestsExceededException"].includes(error.name) || status === 429) {
|
|
61
|
+
throw new ConnectorError("connector_rate_limited", "The AWS request limit was reached.", { statusCode: 429 });
|
|
62
|
+
}
|
|
63
|
+
if (status === 403) throw new ConnectorError("connector_permission_denied", "AWS denied this operation. Check IAM, resource and database permissions.", { statusCode: 403 });
|
|
64
|
+
if (status === 404 || error.name === "ResourceNotFoundException") throw new ConnectorError("connector_not_found", "The AWS resource was not found.", { statusCode: 404 });
|
|
65
|
+
if (status === 301 || status === 307 || ["AuthorizationHeaderMalformed", "PermanentRedirect", "InvalidRequestException", "ValidationException"].includes(error.name)) {
|
|
66
|
+
throw new ConnectorError("connector_configuration_invalid", "Check the AWS region, resource and operation settings.", { statusCode: 422 });
|
|
67
|
+
}
|
|
68
|
+
throw new ConnectorError("connector_provider_failed", "The AWS request failed or returned an unreadable response.", { statusCode: 502 });
|
|
69
|
+
} finally {
|
|
70
|
+
client.destroy();
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export { awsOrigin, awsOperation, awsExchange };
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { createSchema } from "json-rest-schema";
|
|
2
|
+
import { validateSchemaPayload } from "@jskit-ai/kernel/shared/validators";
|
|
3
|
+
import { jsonOperation } from "./jsonOperation.js";
|
|
4
|
+
import { bigqueryDefinition } from "../shared/google.js";
|
|
5
|
+
import { googleRead, googleProvider, pageToken } from "./google.js";
|
|
6
|
+
const origin = "https://bigquery.googleapis.com";
|
|
7
|
+
const queryScopes = ["https://www.googleapis.com/auth/bigquery", "https://www.googleapis.com/auth/cloud-platform"];
|
|
8
|
+
const resultFields = {
|
|
9
|
+
maxResults: { type: "integer", min: 1, max: 1000, defaultTo: 100 },
|
|
10
|
+
timeoutMs: { type: "integer", min: 0, max: 10000, defaultTo: 1000 },
|
|
11
|
+
location: { type: "string", minLength: 1, maxLength: 100, pattern: "^[a-zA-Z0-9-]+$" }
|
|
12
|
+
};
|
|
13
|
+
const resultsSchema = createSchema({ ...resultFields,
|
|
14
|
+
jobId: { type: "string", required: true, minLength: 1, maxLength: 1024, pattern: "^[a-zA-Z0-9_-]+$" }, pageToken: { ...pageToken, noTrim: true }
|
|
15
|
+
});
|
|
16
|
+
const validQueryResult = (value) => Boolean(value && typeof value === "object" && !Array.isArray(value) && !value.error &&
|
|
17
|
+
typeof value.jobComplete === "boolean" && (value.rows === undefined || Array.isArray(value.rows) && value.rows.every((row) =>
|
|
18
|
+
Array.isArray(row?.f) && row.f.every((cell) => cell && typeof cell === "object" && !Array.isArray(cell) && Object.hasOwn(cell, "v")))) &&
|
|
19
|
+
(value.schema === undefined || Array.isArray(value.schema?.fields)) &&
|
|
20
|
+
(value.pageToken === undefined || typeof value.pageToken === "string") &&
|
|
21
|
+
(value.totalRows === undefined || (typeof value.totalRows === "string" && /^\d+$/.test(value.totalRows))) &&
|
|
22
|
+
(value.jobComplete || (typeof value.jobReference?.jobId === "string" && value.jobReference.jobId.length > 0)));
|
|
23
|
+
const submitQuery = jsonOperation((settings) => `${origin}/bigquery/v2/projects/${encodeURIComponent(settings.projectId)}/queries`, {
|
|
24
|
+
...resultFields,
|
|
25
|
+
query: { type: "string", required: true, noTrim: true, minLength: 1, maxLength: 1000000, validator: (value) => value.trim().length > 0 || "Enter SQL." },
|
|
26
|
+
parameters: { type: "array", validator: (values) => (values.length <= 100 && new Set(values.map((value) => value.name)).size === values.length) || "Use at most 100 uniquely named parameters.",
|
|
27
|
+
items: { type: "object", schema: createSchema({
|
|
28
|
+
name: { type: "string", required: true, minLength: 1, maxLength: 128, pattern: "^[a-zA-Z_][a-zA-Z0-9_]*$" },
|
|
29
|
+
type: { type: "string", required: true, enum: ["STRING", "INT64", "FLOAT64", "NUMERIC", "BIGNUMERIC", "BOOL", "DATE", "DATETIME", "TIME", "TIMESTAMP", "BYTES"] },
|
|
30
|
+
value: { type: "string", required: true, noTrim: true, maxLength: 1000000 }
|
|
31
|
+
}) }
|
|
32
|
+
},
|
|
33
|
+
maximumBytesBilled: { type: "string", minLength: 1, maxLength: 19, pattern: "^[0-9]+$" },
|
|
34
|
+
requestId: { type: "string", minLength: 1, maxLength: 36, pattern: "^[a-zA-Z0-9_-]+$" }
|
|
35
|
+
}, validQueryResult, "POST");
|
|
36
|
+
const metadataScopes = [...queryScopes, "https://www.googleapis.com/auth/bigquery.readonly", "https://www.googleapis.com/auth/cloud-platform.read-only"];
|
|
37
|
+
const resourceId = { type: "string", required: true, noTrim: true, minLength: 1, maxLength: 1024,
|
|
38
|
+
// eslint-disable-next-line no-control-regex -- Reject literal control characters in provider input.
|
|
39
|
+
validator: (value) => (!/[\/\\\u0000-\u001f]/u.test(value) && value !== "." && value !== "..") || "Enter a resource ID, not a path." };
|
|
40
|
+
const metadataPage = { maxResults: resultFields.maxResults, pageToken: { ...pageToken, noTrim: true } };
|
|
41
|
+
const jobFields = { jobId: resultsSchema.getFieldDefinitions().jobId, location: resultFields.location };
|
|
42
|
+
const validJob = (job) => Boolean(typeof job?.jobReference?.jobId === "string" && job.jobReference.jobId.length > 0 && typeof job.jobReference.projectId === "string" &&
|
|
43
|
+
["PENDING", "RUNNING", "DONE"].includes(job.status?.state));
|
|
44
|
+
const validPage = (result, field, reference) => Boolean(result && typeof result === "object" && !Array.isArray(result) && !result.error &&
|
|
45
|
+
(result[field] === undefined || Array.isArray(result[field]) && result[field].every((item) => typeof item?.[reference]?.projectId === "string")) &&
|
|
46
|
+
(result.nextPageToken === undefined || typeof result.nextPageToken === "string"));
|
|
47
|
+
|
|
48
|
+
function projectOperation(fields, destination, validateResult, scopes = metadataScopes, method = "GET") {
|
|
49
|
+
const schema = createSchema(fields);
|
|
50
|
+
return { scopes, validateResult,
|
|
51
|
+
request(input, settings) {
|
|
52
|
+
const values = validateSchemaPayload({ schema, mode: "replace" }, input, { statusCode: 422 });
|
|
53
|
+
const { path, query = {} } = destination(values);
|
|
54
|
+
const url = new URL(`${origin}/bigquery/v2/projects/${encodeURIComponent(settings.projectId)}${path}`);
|
|
55
|
+
for (const [name, value] of Object.entries(query)) if (value !== undefined) url.searchParams.set(name, String(value));
|
|
56
|
+
return { method, url: url.href };
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
const bigqueryProvider = googleProvider(bigqueryDefinition, origin, "projects.list", {
|
|
61
|
+
"datasets.list": projectOperation(metadataPage, (query) => ({ path: "/datasets", query }),
|
|
62
|
+
(result) => validPage(result, "datasets", "datasetReference")),
|
|
63
|
+
"tables.list": projectOperation({ datasetId: resourceId, ...metadataPage }, ({ datasetId, ...query }) => ({ path: `/datasets/${encodeURIComponent(datasetId)}/tables`, query }),
|
|
64
|
+
(result) => validPage(result, "tables", "tableReference")),
|
|
65
|
+
"tables.get": projectOperation({ datasetId: resourceId, tableId: resourceId }, ({ datasetId, tableId }) => ({ path: `/datasets/${encodeURIComponent(datasetId)}/tables/${encodeURIComponent(tableId)}` }),
|
|
66
|
+
(result) => Boolean(result?.tableReference?.tableId && result.tableReference.datasetId && typeof result.tableReference.projectId === "string" &&
|
|
67
|
+
(result.schema === undefined || Array.isArray(result.schema.fields)))),
|
|
68
|
+
"jobs.get": projectOperation(jobFields, ({ jobId, ...query }) => ({ path: `/jobs/${encodeURIComponent(jobId)}`, query }), validJob),
|
|
69
|
+
"jobs.cancel": projectOperation(jobFields, ({ jobId, ...query }) => ({ path: `/jobs/${encodeURIComponent(jobId)}/cancel`, query }),
|
|
70
|
+
(result) => validJob(result?.job), queryScopes, "POST"),
|
|
71
|
+
"jobs.query": { ...submitQuery, scopes: queryScopes,
|
|
72
|
+
request(input, settings) {
|
|
73
|
+
const request = submitQuery.request(input, settings);
|
|
74
|
+
const { parameters, ...body } = request.body;
|
|
75
|
+
return { ...request, body: { ...body, useLegacySql: false,
|
|
76
|
+
...(parameters?.length ? { parameterMode: "NAMED", queryParameters: parameters.map(({ name, type, value }) => ({
|
|
77
|
+
name, parameterType: { type }, parameterValue: { value }
|
|
78
|
+
})) } : {})
|
|
79
|
+
} };
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
"jobs.getQueryResults": { scopes: queryScopes, validateResult: validQueryResult,
|
|
83
|
+
request(input, settings) {
|
|
84
|
+
const { jobId, ...query } = validateSchemaPayload({ schema: resultsSchema, mode: "replace" }, input, { statusCode: 422 });
|
|
85
|
+
const url = new URL(`${origin}/bigquery/v2/projects/${encodeURIComponent(settings.projectId)}/queries/${encodeURIComponent(jobId)}`);
|
|
86
|
+
for (const [key, value] of Object.entries(query)) if (value !== undefined) url.searchParams.set(key, String(value));
|
|
87
|
+
return { method: "GET", url: url.href };
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
"projects.list": googleRead(["bigquery.readonly", "bigquery", "cloud-platform.read-only", "cloud-platform"], {
|
|
91
|
+
maxResults: { type: "integer", min: 1, max: 50, defaultTo: 50 }, pageToken
|
|
92
|
+
}, (query) => ({ url: `${origin}/bigquery/v2/projects`, query }),
|
|
93
|
+
(result) => Boolean(result && typeof result === "object" && !Array.isArray(result) && !result.error
|
|
94
|
+
&& Number.isInteger(result.totalItems) && result.totalItems >= 0
|
|
95
|
+
&& (Array.isArray(result.projects) || (result.totalItems === 0 && result.projects === undefined))))
|
|
96
|
+
});
|
|
97
|
+
export { bigqueryProvider };
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { ConnectorError } from "@jskit-ai/connectors-core/server";
|
|
2
|
+
import { createSchema } from "json-rest-schema";
|
|
3
|
+
import { validateSchemaPayload } from "@jskit-ai/kernel/shared/validators";
|
|
4
|
+
import { brevoDefinition } from "../shared/tokens.js";
|
|
5
|
+
import { jsonOperation } from "./jsonOperation.js";
|
|
6
|
+
|
|
7
|
+
const text = { type: "string", minLength: 1, maxLength: 1000 };
|
|
8
|
+
const email = { ...text, maxLength: 320, pattern: "^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$" };
|
|
9
|
+
const id = { type: "integer", min: 1, required: true };
|
|
10
|
+
const bag = { type: "object", additionalProperties: true };
|
|
11
|
+
const bool = { type: "boolean" };
|
|
12
|
+
const page = { limit: { type: "integer", min: 1, max: 50, defaultTo: 50 }, offset: { type: "integer", min: 0, defaultTo: 0 } };
|
|
13
|
+
const mailbox = { type: "object", schema: createSchema({ email: { ...email, required: true }, name: text }) };
|
|
14
|
+
const ids = { type: "array", items: { type: "integer", min: 1 }, validator: value => value.length <= 100 || "Use at most 100 list IDs." };
|
|
15
|
+
const emails = { type: "array", items: email, validator: value => value.length > 0 && value.length <= 150 || "Supply 1–150 email addresses." };
|
|
16
|
+
const domain = { type: "string", minLength: 3, maxLength: 253, pattern: "^(?:[A-Za-z0-9](?:[A-Za-z0-9-]*[A-Za-z0-9])?\\.)+[A-Za-z]{2,}$", required: true };
|
|
17
|
+
const empty = result => result === null || result !== null && typeof result === "object" && !Array.isArray(result) && Object.keys(result).length === 0;
|
|
18
|
+
const record = result => Number.isInteger(result?.id);
|
|
19
|
+
function operation(method, endpoint, fields, validateResult, prepare = value => value) {
|
|
20
|
+
const schema = createSchema(fields);
|
|
21
|
+
return { scopes: [], request(input) {
|
|
22
|
+
const values = prepare(validateSchemaPayload({ schema, mode: "replace" }, input, { statusCode: 422 }));
|
|
23
|
+
const { resource, ...body } = values;
|
|
24
|
+
const url = new URL(`https://api.brevo.com/v3/${endpoint.replace("{resource}", encodeURIComponent(resource ?? ""))}`);
|
|
25
|
+
if (method === "GET") { for (const [key, value] of Object.entries(body)) url.searchParams.set(key, String(value)); return { method, url: url.href }; }
|
|
26
|
+
return { method, url: url.href, ...(Object.keys(body).length ? { body } : {}) };
|
|
27
|
+
}, validateResult };
|
|
28
|
+
}
|
|
29
|
+
function changed(value) {
|
|
30
|
+
if (Object.keys(value).every(key => key === "resource")) throw new ConnectorError("connector_input_invalid", "Supply at least one change.", { statusCode: 422 });
|
|
31
|
+
return value;
|
|
32
|
+
}
|
|
33
|
+
const content = value => {
|
|
34
|
+
if (value.templateId === undefined && !(value.subject && (value.htmlContent || value.textContent))) {
|
|
35
|
+
throw new ConnectorError("connector_input_invalid", "Choose a template or provide a subject and email content.", { statusCode: 422 });
|
|
36
|
+
}
|
|
37
|
+
return value;
|
|
38
|
+
};
|
|
39
|
+
const contact = { attributes: bag, emailBlacklisted: bool, smsBlacklisted: bool, listIds: ids };
|
|
40
|
+
const brevoProvider = Object.freeze({
|
|
41
|
+
...brevoDefinition, apiOrigins: ["https://api.brevo.com"],
|
|
42
|
+
apiKey: { headers: (key) => ({ "api-key": key }) },
|
|
43
|
+
checkOperation: "contacts.list",
|
|
44
|
+
operations: {
|
|
45
|
+
"email.send": operation("POST", "smtp/email", { sender: { ...mailbox, required: true },
|
|
46
|
+
to: { type: "array", required: true, items: mailbox, validator: value => value.length > 0 && value.length <= 50 || "Supply 1–50 recipients." },
|
|
47
|
+
subject: text, htmlContent: { ...text, maxLength: 1000000 }, textContent: { ...text, maxLength: 1000000 },
|
|
48
|
+
templateId: { ...id, required: false }, params: bag, replyTo: mailbox
|
|
49
|
+
}, result => typeof result?.messageId === "string", content),
|
|
50
|
+
"sms.send": operation("POST", "transactionalSMS/sms", { sender: { ...text, required: true, maxLength: 15,
|
|
51
|
+
validator: value => /^[A-Za-z0-9]{1,11}$/.test(value) || /^[0-9]{12,15}$/.test(value) || "Use 1–11 letters/digits or 12–15 digits for the SMS sender." },
|
|
52
|
+
recipient: { type: "string", required: true, pattern: "^\\+?[0-9]{6,15}$" }, type: { type: "string", required: true, enum: ["transactional", "marketing"] },
|
|
53
|
+
content: { ...text, maxLength: 10000 }, templateId: { ...id, required: false }, params: bag, unicodeEnabled: bool, organisationPrefix: text, tag: text
|
|
54
|
+
}, result => Number.isInteger(result?.messageId) && typeof result.reference === "string", value => {
|
|
55
|
+
if ((value.content === undefined) === (value.templateId === undefined)) throw new ConnectorError("connector_input_invalid", "Choose SMS content or a template, not both.", { statusCode: 422 }); return value;
|
|
56
|
+
}),
|
|
57
|
+
"contacts.get": operation("GET", "contacts/{resource}", { resource: { ...email, required: true } }, record),
|
|
58
|
+
"contacts.create": operation("POST", "contacts", { email: { ...email, required: true }, ...contact,
|
|
59
|
+
updateEnabled: { ...bool, defaultTo: false } }, result => empty(result) || record(result)),
|
|
60
|
+
"contacts.update": operation("PUT", "contacts/{resource}", { resource: { ...email, required: true }, ...contact, unlinkListIds: ids }, empty, changed),
|
|
61
|
+
"folders.list": operation("GET", "contacts/folders", page, result => Array.isArray(result?.folders)),
|
|
62
|
+
"lists.list": operation("GET", "contacts/lists", page, result => Array.isArray(result?.lists)),
|
|
63
|
+
"lists.create": operation("POST", "contacts/lists", { name: { ...text, required: true }, folderId: id }, record),
|
|
64
|
+
"lists.addContacts": operation("POST", "contacts/lists/{resource}/contacts/add", { resource: id, emails: { ...emails, required: true } }, result => result?.contacts !== null && typeof result?.contacts === "object"),
|
|
65
|
+
"lists.removeContacts": operation("POST", "contacts/lists/{resource}/contacts/remove", { resource: id, emails: { ...emails, required: true } }, result => result?.contacts !== null && typeof result?.contacts === "object"),
|
|
66
|
+
"senders.list": operation("GET", "senders", {}, result => Array.isArray(result?.senders)),
|
|
67
|
+
"senders.create": operation("POST", "senders", { email: { ...email, required: true }, name: { ...text, required: true } }, record),
|
|
68
|
+
"domains.create": operation("POST", "senders/domains", { name: domain }, result => typeof result?.domain_name === "string" && typeof result.message === "string"),
|
|
69
|
+
"domains.get": operation("GET", "senders/domains/{resource}", { resource: domain }, result => typeof result?.domain === "string" && typeof result.verified === "boolean" && typeof result.authenticated === "boolean" && result.dns_records !== null && typeof result.dns_records === "object"),
|
|
70
|
+
"domains.authenticate": operation("PUT", "senders/domains/{resource}/authenticate", { resource: domain }, result => typeof result?.domain_name === "string" && typeof result.message === "string"),
|
|
71
|
+
"campaigns.list": operation("GET", "emailCampaigns", page, result => Array.isArray(result?.campaigns)),
|
|
72
|
+
"campaigns.get": operation("GET", "emailCampaigns/{resource}", { resource: id }, record),
|
|
73
|
+
"campaigns.create": operation("POST", "emailCampaigns", { name: { ...text, required: true }, sender: { ...mailbox, required: true },
|
|
74
|
+
subject: text, htmlContent: { ...text, maxLength: 1000000 }, templateId: { ...id, required: false }, params: bag,
|
|
75
|
+
recipients: { type: "object", required: true, schema: createSchema({ listIds: { ...ids, required: true,
|
|
76
|
+
validator: value => value.length > 0 && value.length <= 100 || "Choose 1–100 recipient lists." }, exclusionListIds: ids }) },
|
|
77
|
+
replyTo: email, previewText: text, tag: text
|
|
78
|
+
}, record, content),
|
|
79
|
+
"campaigns.sendTest": operation("POST", "emailCampaigns/{resource}/sendTest", { resource: id, emailTo: { ...emails, required: true } }, empty),
|
|
80
|
+
"campaigns.sendNow": operation("POST", "emailCampaigns/{resource}/sendNow", { resource: id }, empty),
|
|
81
|
+
"email.events": operation("GET", "smtp/statistics/events", { ...page, messageId: text, email, days: { type: "integer", min: 1, max: 90 },
|
|
82
|
+
event: { type: "string", enum: ["blocked", "bounces", "deferred", "delivered", "hardBounces", "invalid", "opened", "requests", "softBounces", "spam", "unsubscribed", "clicks", "error", "loadedByProxy"] }
|
|
83
|
+
}, result => Array.isArray(result?.events)),
|
|
84
|
+
"sms.events": operation("GET", "transactionalSMS/statistics/events", { ...page, phoneNumber: text, days: { type: "integer", min: 1, max: 90 } }, result => Array.isArray(result?.events)),
|
|
85
|
+
"events.create": operation("POST", "events", { event_name: { type: "string", required: true, maxLength: 255, pattern: "^[A-Za-z0-9_-]+$" },
|
|
86
|
+
identifiers: { type: "object", required: true, schema: createSchema({ email_id: { ...email, required: true } }) },
|
|
87
|
+
event_properties: { ...bag, validator: value => Buffer.byteLength(JSON.stringify(value)) <= 50000 || "Event properties exceed 50KB." }, contact_properties: bag,
|
|
88
|
+
event_date: { ...text, validator: value => Number.isFinite(Date.parse(value)) && /^\d{4}-\d{2}-\d{2}T/.test(value) || "Use an ISO timestamp." }
|
|
89
|
+
}, empty),
|
|
90
|
+
"contacts.list": jsonOperation("https://api.brevo.com/v3/contacts", {
|
|
91
|
+
limit: { type: "integer", min: 1, max: 1000, defaultTo: 50 },
|
|
92
|
+
offset: { type: "integer", min: 0, defaultTo: 0 },
|
|
93
|
+
sort: { type: "string", enum: ["asc", "desc"], defaultTo: "desc" }
|
|
94
|
+
}, (result) => Array.isArray(result?.contacts) && Number.isInteger(result.count))
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
export { brevoProvider };
|