@jskit-ai/connectors-catalog 0.2.2 → 0.2.4
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 +4 -4
- package/docs/ai.md +3 -3
- package/docs/airtable.md +1 -1
- package/docs/algolia.md +2 -2
- package/docs/amazon-redshift.md +2 -2
- package/docs/apollo-io.md +2 -2
- package/docs/asana.md +2 -2
- package/docs/ashby.md +1 -1
- package/docs/attention.md +2 -2
- package/docs/aws-athena.md +1 -1
- package/docs/aws-credentials.md +3 -3
- package/docs/aws-s3.md +1 -1
- package/docs/bigquery.md +2 -2
- package/docs/brevo.md +2 -2
- package/docs/calendly.md +3 -3
- package/docs/canva.md +2 -2
- package/docs/chargebee.md +2 -2
- package/docs/clay.md +2 -2
- package/docs/clickhouse.md +1 -1
- package/docs/confidence-flags.md +1 -1
- package/docs/contentful.md +1 -1
- package/docs/dbt-semantic-layer.md +2 -2
- package/docs/elevenlabs.md +2 -2
- package/docs/firebase-cloud-messaging.md +3 -3
- package/docs/firecrawl.md +4 -4
- package/docs/fireflies.md +1 -1
- package/docs/gatewayapi.md +1 -1
- package/docs/gemini-enterprise.md +1 -1
- package/docs/github-api.md +1 -1
- package/docs/gitlab-api.md +1 -1
- package/docs/gmail.md +1 -1
- package/docs/gong.md +1 -1
- package/docs/google-ads.md +3 -3
- package/docs/google-analytics.md +2 -2
- package/docs/google-docs.md +1 -1
- package/docs/google-drive.md +1 -1
- package/docs/google-maps-platform.md +2 -2
- package/docs/google-search-console.md +1 -1
- package/docs/google-sheets.md +1 -1
- package/docs/google-slides.md +2 -2
- package/docs/granola.md +2 -2
- package/docs/hex.md +2 -2
- package/docs/heygen.md +2 -2
- package/docs/inngest.md +1 -1
- package/docs/klipy.md +1 -1
- package/docs/lightspeed.md +2 -2
- package/docs/linear.md +1 -1
- package/docs/linkedin.md +2 -2
- package/docs/mailgun.md +1 -1
- package/docs/microsoft-excel.md +2 -2
- package/docs/microsoft-fabric.md +1 -1
- package/docs/microsoft-outlook.md +1 -1
- package/docs/microsoft-powerpoint.md +1 -1
- package/docs/microsoft-teams.md +1 -1
- package/docs/microsoft-word.md +1 -1
- package/docs/notion.md +1 -1
- package/docs/oura.md +3 -3
- package/docs/paddle.md +3 -3
- package/docs/pipedrive.md +2 -2
- package/docs/prestashop.md +1 -1
- package/docs/replicate.md +1 -1
- package/docs/resend.md +1 -1
- package/docs/salesforce.md +1 -1
- package/docs/sanity.md +3 -3
- package/docs/semrush.md +2 -2
- package/docs/sentry.md +1 -1
- package/docs/slack.md +1 -1
- package/docs/snowflake.md +5 -5
- package/docs/storyblok.md +1 -1
- package/docs/stripe.md +2 -2
- package/docs/tally.md +1 -1
- package/docs/telegram.md +2 -2
- package/docs/tiktok.md +3 -3
- package/docs/twilio.md +3 -3
- package/docs/twitch.md +2 -2
- package/docs/wave.md +3 -3
- package/docs/wix.md +13 -13
- package/docs/wordpress-com.md +1 -1
- package/docs/wordpress-self-hosted.md +1 -1
- package/docs/workday.md +2 -2
- package/docs/x-twitter.md +3 -3
- package/docs/xero.md +3 -3
- package/docs/zoho-books.md +2 -2
- package/docs/zoho-crm.md +2 -2
- package/package.json +4 -3
- package/patterns/ai-connections/PATTERN.md +16 -1
- package/patterns/ai-connections/example/ai-model.js +12 -3
- package/patterns/ai-connections/example/perplexity-answer.js +15 -4
- package/patterns/api-key-connection/PATTERN.md +9 -1
- package/patterns/api-key-connection/example/connections.js +5 -2
- package/patterns/aws-storage-queries/PATTERN.md +13 -3
- package/patterns/aws-storage-queries/example/s3-transfer.js +14 -3
- package/patterns/event-delivery/PATTERN.md +1 -1
- package/patterns/firebase-messaging/PATTERN.md +10 -0
- package/patterns/firebase-messaging/example/server/notifications.js +8 -0
- package/patterns/google-ads-search/PATTERN.md +18 -7
- package/patterns/google-ads-search/example/ads-setup.js +13 -5
- package/patterns/oauth-connection/PATTERN.md +1 -1
- package/patterns/paddle-catalogue/PATTERN.md +11 -2
- package/patterns/paddle-catalogue/example/create-products.js +5 -2
- package/patterns/public-image/PATTERN.md +10 -1
- package/patterns/public-image/example/logo-url.js +4 -1
- package/patterns/redshift-queries/PATTERN.md +1 -1
- package/patterns/source-scanning/PATTERN.md +13 -2
- package/patterns/source-scanning/example/source-scanner.js +9 -0
- package/src/server/ai.js +19 -5
- package/src/server/apify.js +2 -2
- package/src/server/asana.js +6 -6
- package/src/server/ashby.js +2 -1
- package/src/server/attention.js +12 -11
- package/src/server/awsTransport.js +6 -0
- package/src/server/bigquery.js +5 -4
- package/src/server/brevo.js +2 -1
- package/src/server/clay.js +2 -1
- package/src/server/clickhouse.js +2 -1
- package/src/server/confidence.js +6 -1
- package/src/server/connectorValues.js +33 -0
- package/src/server/databricks.js +7 -7
- package/src/server/dbt-semantic-layer.js +5 -5
- package/src/server/elevenlabs.js +2 -2
- package/src/server/firebase-cloud-messaging.js +2 -2
- package/src/server/gemini-enterprise.js +5 -5
- package/src/server/google-ads-search.js +68 -48
- package/src/server/google-ads.js +15 -17
- package/src/server/google-drive.js +7 -7
- package/src/server/google-maps-platform.js +4 -2
- package/src/server/google-search-console.js +4 -3
- package/src/server/google.js +8 -4
- package/src/server/googleAdsApi.js +14 -0
- package/src/server/granola.js +2 -2
- package/src/server/hubspot.js +2 -1
- package/src/server/incident-io.js +3 -2
- package/src/server/lexware.js +5 -4
- package/src/server/lightspeed.js +7 -5
- package/src/server/linkedin.js +2 -2
- package/src/server/mcpTransport.js +8 -4
- package/src/server/microsoft-fabric.js +3 -3
- package/src/server/microsoft-onedrive.js +2 -2
- package/src/server/microsoft-onenote.js +4 -3
- package/src/server/microsoft-outlook.js +6 -6
- package/src/server/microsoft-sharepoint.js +4 -2
- package/src/server/microsoft-teams.js +2 -1
- package/src/server/microsoft.js +20 -13
- package/src/server/notion.js +2 -1
- package/src/server/pipedrive.js +2 -1
- package/src/server/salesforce.js +12 -12
- package/src/server/semrush.js +9 -8
- package/src/server/snowflake.js +20 -20
- package/src/server/storyblok.js +2 -1
- package/src/server/tally.js +2 -1
- package/src/server/telegram.js +2 -6
- package/src/server/tiktok.js +3 -3
- package/src/server/twilio.js +3 -2
- package/src/server/twitch.js +5 -3
- package/src/server/types.js +13 -0
- package/src/server/wix.js +20 -878
- package/src/server/wixBookings.js +449 -0
- package/src/server/wixCms.js +132 -0
- package/src/server/wixStores.js +296 -0
- package/src/server/wixValues.js +35 -0
- package/src/server/wiz.js +18 -4
- package/src/server/woocommerce.js +1 -1
- package/src/server/wordpress-com.js +2 -1
- package/src/server/workday.js +9 -9
- package/src/server/x-twitter.js +2 -1
- package/src/server/xero.js +40 -40
- package/src/server/zoho-books.js +6 -6
- package/src/server/zoho-crm.js +5 -5
- package/src/shared/confidence.js +6 -1
- package/src/shared/dbt-semantic-layer.js +1 -1
- package/src/shared/figma.js +1 -1
- package/src/shared/google.js +8 -4
- package/src/shared/googleScope.js +4 -0
- package/src/shared/microsoft-fabric.js +4 -5
- package/src/shared/microsoft.js +8 -3
- package/src/shared/salesforce.js +1 -1
- package/src/shared/tokens.js +7 -3
- package/src/shared/uuid.js +13 -0
- package/src/shared/wix.js +4 -3
- package/src/shared/zoho-books.js +3 -4
- package/src/shared/zoho-crm.js +1 -1
- package/src/shared/{zoho-regions.js → zoho.js} +4 -1
- package/test/ai.test.js +1 -1
- package/test/brevo.test.js +1 -1
- package/test/chargebee.test.js +1 -1
- package/test/google-ads.test.js +8 -4
- package/test/linkedin.test.js +1 -1
- package/test/resend.test.js +2 -2
- package/test/salesforce.test.js +1 -1
- package/test/snowflake.test.js +7 -7
- package/test/tiktok.test.js +1 -1
- package/test/wiz.test.js +1 -1
package/src/server/linkedin.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ConnectorError } from "@jskit-ai/connectors-core/server";
|
|
1
|
+
import { ConnectorError, providerStatusError } from "@jskit-ai/connectors-core/server";
|
|
2
2
|
import { linkedinDefinition } from "../shared/linkedin.js";
|
|
3
3
|
import { jsonOperation } from "./jsonOperation.js";
|
|
4
4
|
|
|
@@ -54,7 +54,7 @@ const linkedinProvider = Object.freeze({
|
|
|
54
54
|
specificContent: { "com.linkedin.ugc.ShareContent": { shareCommentary: { text: options.body.text }, shareMediaCategory: "NONE" } },
|
|
55
55
|
visibility: { "com.linkedin.ugc.MemberNetworkVisibility": options.body.visibility } }) });
|
|
56
56
|
await response.body?.cancel();
|
|
57
|
-
if (!response.ok) throw
|
|
57
|
+
if (!response.ok) throw providerStatusError(response.status);
|
|
58
58
|
if (response.status !== 201) throw new ConnectorError("connector_response_invalid", "LinkedIn did not confirm post creation.", { statusCode: 502, messageCode: "connectors.linkedin.didNotConfirmPostCreation" });
|
|
59
59
|
return { id: response.headers.get("x-restli-id") };
|
|
60
60
|
},
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
2
2
|
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
3
|
-
import { ConnectorError } from "@jskit-ai/connectors-core/server";
|
|
3
|
+
import { ConnectorError, providerStatusError } from "@jskit-ai/connectors-core/server";
|
|
4
4
|
import { jsonOperation } from "./jsonOperation.js";
|
|
5
5
|
|
|
6
6
|
function mcpOperations(endpoint) {
|
|
@@ -21,6 +21,12 @@ function mcpOperations(endpoint) {
|
|
|
21
21
|
};
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
+
/**
|
|
25
|
+
* Sends one MCP request to the provider's endpoint over the connection's fetch.
|
|
26
|
+
* @param {string} endpoint
|
|
27
|
+
* @param {{ headers: Record<string, string>, signal: AbortSignal, body: any }} options
|
|
28
|
+
* @param {import("@jskit-ai/connectors-core/server/types").ConnectorExchangeTransport} transport
|
|
29
|
+
*/
|
|
24
30
|
async function exchangeMcp(endpoint, { headers, signal, body }, { fetchImpl }) {
|
|
25
31
|
const client = new Client({ name: "jskit-connectors", version: "0.1.0" }, { capabilities: {} });
|
|
26
32
|
let cleanup = false;
|
|
@@ -37,9 +43,7 @@ async function exchangeMcp(endpoint, { headers, signal, body }, { fetchImpl }) {
|
|
|
37
43
|
const response = await fetchImpl(address, { ...init, headers: requestHeaders, credentials: "omit", redirect: "error", signal: requestSignal });
|
|
38
44
|
if (!response.ok && !(response.status === 405 && ["GET", "DELETE"].includes(init.method))) {
|
|
39
45
|
await response.body?.cancel();
|
|
40
|
-
|
|
41
|
-
error.statusCode = response.status;
|
|
42
|
-
throw error;
|
|
46
|
+
throw providerStatusError(response.status);
|
|
43
47
|
}
|
|
44
48
|
return response;
|
|
45
49
|
}
|
|
@@ -2,10 +2,10 @@ import { isDeepStrictEqual } from "node:util";
|
|
|
2
2
|
import { ConnectorError } from "@jskit-ai/connectors-core/server";
|
|
3
3
|
import { microsoftFabricDefinition, delegatedScope, serviceScope } from "../shared/microsoft-fabric.js";
|
|
4
4
|
import { jsonOperation } from "./jsonOperation.js";
|
|
5
|
+
import { isRecord } from "@jskit-ai/kernel/shared/support/normalize";
|
|
5
6
|
|
|
6
|
-
const object = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
7
7
|
const name = (value) => typeof value === "string" && /^[_A-Za-z][_0-9A-Za-z]*$/u.test(value);
|
|
8
|
-
const validResult = (value) =>
|
|
8
|
+
const validResult = (value) => isRecord(value?.data) &&
|
|
9
9
|
(value.errors === undefined || (Array.isArray(value.errors) && value.errors.length === 0));
|
|
10
10
|
const endpoint = (settings) => settings.graphqlEndpoint;
|
|
11
11
|
const permissions = [delegatedScope, serviceScope];
|
|
@@ -51,7 +51,7 @@ const microsoftFabricProvider = Object.freeze({
|
|
|
51
51
|
operations: {
|
|
52
52
|
"connection.check": fixedQuery("query ConnectorFabricCheck { __typename }", (data) => name(data.__typename)),
|
|
53
53
|
"schema.types": fixedQuery("query ConnectorFabricTypes { __schema { types { name kind } queryType { name } mutationType { name } } }",
|
|
54
|
-
(data) =>
|
|
54
|
+
(data) => isRecord(data.__schema) && Array.isArray(data.__schema.types) && data.__schema.types.every((type) => name(type?.name) &&
|
|
55
55
|
["SCALAR", "OBJECT", "INTERFACE", "UNION", "ENUM", "INPUT_OBJECT", "LIST", "NON_NULL"].includes(type.kind)) &&
|
|
56
56
|
name(data.__schema.queryType?.name) && (data.__schema.mutationType === null || name(data.__schema.mutationType?.name))),
|
|
57
57
|
"graphql.execute": {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ConnectorError } from "@jskit-ai/connectors-core/server";
|
|
1
|
+
import { ConnectorError, providerStatusError } from "@jskit-ai/connectors-core/server";
|
|
2
2
|
import { createSchema } from "json-rest-schema";
|
|
3
3
|
import { validateSchemaPayload } from "@jskit-ai/kernel/shared/validators";
|
|
4
4
|
import { microsoftOneDriveDefinition } from "../shared/microsoft.js";
|
|
@@ -50,7 +50,7 @@ const microsoftOneDriveProvider = Object.freeze({ ...base,
|
|
|
50
50
|
const response = await fetchImpl(address, { ...options, credentials: "omit", redirect: "error" });
|
|
51
51
|
if (!response.ok) {
|
|
52
52
|
await response.body?.cancel();
|
|
53
|
-
throw
|
|
53
|
+
throw providerStatusError(response.status);
|
|
54
54
|
}
|
|
55
55
|
return response.json();
|
|
56
56
|
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { createSchema } from "json-rest-schema";
|
|
2
2
|
import { validateSchemaPayload } from "@jskit-ai/kernel/shared/validators";
|
|
3
|
-
import { ConnectorError } from "@jskit-ai/connectors-core/server";
|
|
3
|
+
import { ConnectorError, providerStatusError } from "@jskit-ai/connectors-core/server";
|
|
4
4
|
import { microsoftOneNoteDefinition } from "../shared/microsoft.js";
|
|
5
5
|
import { graphRead, microsoftProvider, pageSize } from "./microsoft.js";
|
|
6
|
+
import { hasStringId } from "./connectorValues.js";
|
|
6
7
|
|
|
7
8
|
const id = { type: "string", required: true, minLength: 1, maxLength: 1024,
|
|
8
9
|
validator: value => /^[A-Za-z0-9!_{}:.-]+$/u.test(value) && value !== "." && value !== ".." || "Use the returned OneNote resource ID." };
|
|
@@ -20,7 +21,7 @@ const base = microsoftProvider(microsoftOneNoteDefinition, "common", "notebooks.
|
|
|
20
21
|
const { pageId } = validateSchemaPayload({ schema: contentSchema, mode: "replace" }, input, { statusCode: 422 });
|
|
21
22
|
return { method: "GET", url: `${origin}/pages/${encodeURIComponent(pageId)}/content?includeIDs=true` };
|
|
22
23
|
} },
|
|
23
|
-
"pages.create": { scopes: ["Notes.Create", "Notes.ReadWrite", "Notes.ReadWrite.All"], validateResult:
|
|
24
|
+
"pages.create": { scopes: ["Notes.Create", "Notes.ReadWrite", "Notes.ReadWrite.All"], validateResult: hasStringId, request(input) {
|
|
24
25
|
const { sectionId, title, text } = validateSchemaPayload({ schema: createSchemaPage, mode: "replace" }, input, { statusCode: 422 });
|
|
25
26
|
return { method: "POST", url: `${origin}/sections/${encodeURIComponent(sectionId)}/pages`, headers: { "Content-Type": "text/html" },
|
|
26
27
|
body: `<!DOCTYPE html><html><head><title>${escape(title)}</title></head><body><p>${escape(text).replaceAll("\n", "<br />")}</p></body></html>` };
|
|
@@ -37,7 +38,7 @@ const microsoftOneNoteProvider = Object.freeze({ ...base,
|
|
|
37
38
|
const create = new Headers(options.headers).get("content-type") === "text/html";
|
|
38
39
|
if (!content && !create) return request(address, options);
|
|
39
40
|
const response = await fetchImpl(address, { ...options, credentials: "omit", redirect: "error" });
|
|
40
|
-
if (!response.ok) { await response.body?.cancel(); throw
|
|
41
|
+
if (!response.ok) { await response.body?.cancel(); throw providerStatusError(response.status); }
|
|
41
42
|
if (create) return response.json();
|
|
42
43
|
const reader = response.body?.getReader(); const chunks = []; let size = 0;
|
|
43
44
|
if (reader) try { while (true) {
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { createSchema } from "json-rest-schema";
|
|
2
2
|
import { validateSchemaPayload } from "@jskit-ai/kernel/shared/validators";
|
|
3
|
-
import { ConnectorError } from "@jskit-ai/connectors-core/server";
|
|
3
|
+
import { ConnectorError, providerStatusError } from "@jskit-ai/connectors-core/server";
|
|
4
4
|
import { microsoftOutlookDefinition } from "../shared/microsoft.js";
|
|
5
|
+
import { hasStringId } from "./connectorValues.js";
|
|
5
6
|
import { graphRead, microsoftProvider, pageSize } from "./microsoft.js";
|
|
6
7
|
|
|
7
8
|
const id = { type: "string", required: true, minLength: 1, maxLength: 2048,
|
|
@@ -9,8 +10,7 @@ const id = { type: "string", required: true, minLength: 1, maxLength: 2048,
|
|
|
9
10
|
const text = { type: "string", required: true, minLength: 1, maxLength: 100000, noTrim: true };
|
|
10
11
|
const instant = { type: "string", required: true, maxLength: 30,
|
|
11
12
|
validator: value => /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?Z$/u.test(value) && Number.isFinite(Date.parse(value)) || "Use a UTC ISO timestamp ending in Z." };
|
|
12
|
-
|
|
13
|
-
function operation(scopes, fields, destination, validateResult = resource) {
|
|
13
|
+
function operation(scopes, fields, destination, validateResult = hasStringId) {
|
|
14
14
|
const schema = createSchema(fields);
|
|
15
15
|
return { scopes, validateResult, request(input) {
|
|
16
16
|
return destination(validateSchemaPayload({ schema, mode: "replace" }, input, { statusCode: 422 }));
|
|
@@ -19,9 +19,9 @@ function operation(scopes, fields, destination, validateResult = resource) {
|
|
|
19
19
|
const url = path => `https://graph.microsoft.com/v1.0/me${path}`;
|
|
20
20
|
|
|
21
21
|
const base = microsoftProvider(microsoftOutlookDefinition, "common", "folders.list", {
|
|
22
|
-
"messages.get": operation(["Mail.Read"], { messageId: id }, ({ messageId }) => ({ method: "GET", url: url(`/messages/${encodeURIComponent(messageId)}`), headers: { Prefer: 'outlook.body-content-type="text"' } }), value =>
|
|
22
|
+
"messages.get": operation(["Mail.Read"], { messageId: id }, ({ messageId }) => ({ method: "GET", url: url(`/messages/${encodeURIComponent(messageId)}`), headers: { Prefer: 'outlook.body-content-type="text"' } }), value => hasStringId(value) && typeof value.body?.content === "string"),
|
|
23
23
|
"attachments.list": graphRead("Mail.Read", { messageId: id }, ({ messageId }) => ({ pathname: `/me/messages/${encodeURIComponent(messageId)}/attachments` })),
|
|
24
|
-
"attachments.get": operation(["Mail.Read"], { messageId: id, attachmentId: id }, ({ messageId, attachmentId }) => ({ method: "GET", url: url(`/messages/${encodeURIComponent(messageId)}/attachments/${encodeURIComponent(attachmentId)}`) }), value =>
|
|
24
|
+
"attachments.get": operation(["Mail.Read"], { messageId: id, attachmentId: id }, ({ messageId, attachmentId }) => ({ method: "GET", url: url(`/messages/${encodeURIComponent(messageId)}/attachments/${encodeURIComponent(attachmentId)}`) }), value => hasStringId(value) && value["@odata.type"] === "#microsoft.graph.fileAttachment" && typeof value.contentBytes === "string" && value.contentBytes.length <= 6666668),
|
|
25
25
|
"messages.setRead": operation(["Mail.ReadWrite"], { messageId: id, isRead: { type: "boolean", required: true } }, ({ messageId, isRead }) => ({ method: "PATCH", url: url(`/messages/${encodeURIComponent(messageId)}`), body: { isRead } })),
|
|
26
26
|
"messages.move": operation(["Mail.ReadWrite"], { messageId: id, destinationId: id }, ({ messageId, destinationId }) => ({ method: "POST", url: url(`/messages/${encodeURIComponent(messageId)}/move`), body: { destinationId } })),
|
|
27
27
|
"messages.send": operation(["Mail.Send"], {
|
|
@@ -51,7 +51,7 @@ const microsoftOutlookProvider = Object.freeze({ ...base,
|
|
|
51
51
|
if (new URL(address).pathname !== "/v1.0/me/sendMail") return request(address, options);
|
|
52
52
|
const response = await fetchImpl(address, { ...options, body: JSON.stringify(options.body), headers: { ...options.headers, "Content-Type": "application/json" }, credentials: "omit", redirect: "error" });
|
|
53
53
|
await response.body?.cancel();
|
|
54
|
-
if (response.status !== 202) throw
|
|
54
|
+
if (response.status !== 202) throw providerStatusError(response.status);
|
|
55
55
|
return { accepted: true };
|
|
56
56
|
}
|
|
57
57
|
});
|
|
@@ -3,6 +3,8 @@ import { validateSchemaPayload } from "@jskit-ai/kernel/shared/validators";
|
|
|
3
3
|
import { microsoftSharePointDefinition } from "../shared/microsoft.js";
|
|
4
4
|
import { graphRead, microsoftProvider, driveItemId, pageSize } from "./microsoft.js";
|
|
5
5
|
import { microsoftOneDriveProvider } from "./microsoft-onedrive.js";
|
|
6
|
+
import { hasStringId } from "./connectorValues.js";
|
|
7
|
+
import { isRecord } from "@jskit-ai/kernel/shared/support/normalize";
|
|
6
8
|
|
|
7
9
|
const siteId = { type: "string", required: true, minLength: 1, maxLength: 512,
|
|
8
10
|
validator: value => /^[A-Za-z0-9.,_-]+$/u.test(value) && value !== "." && value !== ".." || "Use the site's returned ID, not its URL." };
|
|
@@ -33,11 +35,11 @@ const base = microsoftProvider(microsoftSharePointDefinition, "organizations", "
|
|
|
33
35
|
"files.upload": fileOperation("files.upload", ["Sites.ReadWrite.All"]),
|
|
34
36
|
"lists.list": graphRead("Sites.Read.All", { siteId }, ({ siteId }) => ({ pathname: `/sites/${encodeURIComponent(siteId)}/lists` })),
|
|
35
37
|
"listItems.list": graphRead("Sites.Read.All", { siteId, listId, pageSize }, ({ siteId, listId, pageSize }) => ({ pathname: `/sites/${encodeURIComponent(siteId)}/lists/${encodeURIComponent(listId)}/items`, query: { $expand: "fields", $top: pageSize } })),
|
|
36
|
-
"listItems.create": { scopes: ["Sites.ReadWrite.All"], validateResult:
|
|
38
|
+
"listItems.create": { scopes: ["Sites.ReadWrite.All"], validateResult: hasStringId, request(input) {
|
|
37
39
|
const { siteId, listId, fields } = validateSchemaPayload({ schema: createItemSchema, mode: "replace" }, input, { statusCode: 422 });
|
|
38
40
|
return { method: "POST", url: `https://graph.microsoft.com/v1.0/sites/${encodeURIComponent(siteId)}/lists/${encodeURIComponent(listId)}/items`, body: { fields } };
|
|
39
41
|
} },
|
|
40
|
-
"listItems.update": { scopes: ["Sites.ReadWrite.All"], validateResult:
|
|
42
|
+
"listItems.update": { scopes: ["Sites.ReadWrite.All"], validateResult: isRecord, request(input) {
|
|
41
43
|
const { siteId, listId, itemId, fields, eTag } = validateSchemaPayload({ schema: updateItemSchema, mode: "replace" }, input, { statusCode: 422 });
|
|
42
44
|
return { method: "PATCH", url: `https://graph.microsoft.com/v1.0/sites/${encodeURIComponent(siteId)}/lists/${encodeURIComponent(listId)}/items/${encodeURIComponent(itemId)}/fields`, headers: { "If-Match": eTag }, body: fields };
|
|
43
45
|
} }
|
|
@@ -2,6 +2,7 @@ import { createSchema } from "json-rest-schema";
|
|
|
2
2
|
import { validateSchemaPayload } from "@jskit-ai/kernel/shared/validators";
|
|
3
3
|
import { microsoftTeamsDefinition } from "../shared/microsoft.js";
|
|
4
4
|
import { graphRead, microsoftProvider } from "./microsoft.js";
|
|
5
|
+
import { hasStringId } from "./connectorValues.js";
|
|
5
6
|
|
|
6
7
|
const id = { type: "string", required: true, minLength: 1, maxLength: 1024,
|
|
7
8
|
validator: value => /^[A-Za-z0-9_:@.=-]+$/u.test(value) && value !== "." && value !== ".." || "Use the returned Teams resource ID." };
|
|
@@ -10,7 +11,7 @@ const pageSize = { type: "integer", min: 1, max: 50, defaultTo: 25 };
|
|
|
10
11
|
const channelPath = ({ teamId, channelId }) => `/teams/${encodeURIComponent(teamId)}/channels/${encodeURIComponent(channelId)}`;
|
|
11
12
|
function send(scope, fields, path) {
|
|
12
13
|
const schema = createSchema({ ...fields, text: { type: "string", required: true, minLength: 1, maxLength: 20000, noTrim: true } });
|
|
13
|
-
return { scopes: [scope], validateResult:
|
|
14
|
+
return { scopes: [scope], validateResult: hasStringId, request(input) {
|
|
14
15
|
const values = validateSchemaPayload({ schema, mode: "replace" }, input, { statusCode: 422 });
|
|
15
16
|
return { method: "POST", url: `https://graph.microsoft.com/v1.0${path(values)}`, body: { body: { contentType: "text", content: values.text } } };
|
|
16
17
|
} };
|
package/src/server/microsoft.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createSchema } from "json-rest-schema";
|
|
2
2
|
import { validateSchemaPayload } from "@jskit-ai/kernel/shared/validators";
|
|
3
3
|
import { ConnectorError } from "@jskit-ai/connectors-core/server";
|
|
4
|
+
import { isRecord } from "@jskit-ai/kernel/shared/support/normalize";
|
|
4
5
|
|
|
5
6
|
const graphOrigin = "https://graph.microsoft.com";
|
|
6
7
|
const pageSize = { type: "integer", min: 1, max: 100, defaultTo: 25 };
|
|
@@ -48,13 +49,16 @@ function microsoftProvider(definition, authority, checkOperation, operations) {
|
|
|
48
49
|
authorization_endpoint: `https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/authorize`,
|
|
49
50
|
token_endpoint: `https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/token`
|
|
50
51
|
}),
|
|
51
|
-
authorizationParameters: { response_mode: "query" },
|
|
52
|
+
authorizationParameters: { response_mode: "query" },
|
|
53
|
+
apiOrigins: [graphOrigin],
|
|
54
|
+
checkOperation,
|
|
55
|
+
operations
|
|
52
56
|
});
|
|
53
57
|
}
|
|
54
58
|
|
|
55
59
|
function microsoftDocumentProvider(definition, extensions) {
|
|
56
60
|
const fields = "id,name,size,webUrl,file,folder,parentReference,lastModifiedDateTime,eTag";
|
|
57
|
-
const facet = (value) => value === undefined || (value
|
|
61
|
+
const facet = (value) => value === undefined || isRecord(value);
|
|
58
62
|
const metadata = (value) => typeof value?.id === "string" && value.id.length > 0 &&
|
|
59
63
|
typeof value.name === "string" && value.name.length > 0 && facet(value.file) && facet(value.folder);
|
|
60
64
|
const matches = (value) => value?.file && extensions.includes(value.name.split(".").at(-1).toLowerCase());
|
|
@@ -72,18 +76,21 @@ function microsoftDocumentProvider(definition, extensions) {
|
|
|
72
76
|
return { method: "GET", url: url.href };
|
|
73
77
|
} }
|
|
74
78
|
});
|
|
75
|
-
return Object.freeze({
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
79
|
+
return Object.freeze({
|
|
80
|
+
...provider,
|
|
81
|
+
async exchange(address, options, { request }) {
|
|
82
|
+
const result = await request(address, options);
|
|
83
|
+
if (new URL(address).pathname.endsWith("/children")) {
|
|
84
|
+
if (!provider.operations["items.list"].validateResult(result)) {
|
|
85
|
+
throw new ConnectorError("connector_response_invalid", "Microsoft returned an invalid file list.", { statusCode: 502, messageCode: "connectors.microsoft.returnedInvalidFileList" });
|
|
86
|
+
}
|
|
87
|
+
return { ...result, value: result.value.filter((item) => item.folder || matches(item)) };
|
|
80
88
|
}
|
|
81
|
-
|
|
89
|
+
if (!metadata(result)) throw new ConnectorError("connector_response_invalid", "Microsoft returned invalid file metadata.", { statusCode: 502, messageCode: "connectors.microsoft.returnedInvalidFileMetadata" });
|
|
90
|
+
if (!matches(result)) throw new ConnectorError("connector_document_type_invalid", `Choose a ${definition.name} file.`,
|
|
91
|
+
{ messageCode: "connectors.microsoft.chooseDocumentFile", messageParams: { product: definition.name } });
|
|
92
|
+
return result;
|
|
82
93
|
}
|
|
83
|
-
|
|
84
|
-
if (!matches(result)) throw new ConnectorError("connector_document_type_invalid", `Choose a ${definition.name} file.`,
|
|
85
|
-
{ messageCode: "connectors.microsoft.chooseDocumentFile", messageParams: { product: definition.name } });
|
|
86
|
-
return result;
|
|
87
|
-
} });
|
|
94
|
+
});
|
|
88
95
|
}
|
|
89
96
|
export { graphRead, microsoftProvider, microsoftDocumentProvider, pageSize, driveItemId };
|
package/src/server/notion.js
CHANGED
|
@@ -4,8 +4,9 @@ import { mcpOperations, exchangeMcp } from "./mcpTransport.js";
|
|
|
4
4
|
import { mcpRegistrationFields, registerMcpClient } from "./mcpRegistration.js";
|
|
5
5
|
import { notionDefinition } from "../shared/tokens.js";
|
|
6
6
|
import { jsonOperation, validatedOperation } from "./jsonOperation.js";
|
|
7
|
+
import { UUID_SOURCE } from "../shared/uuid.js";
|
|
7
8
|
|
|
8
|
-
const identifier = { type: "string", required: true, pattern:
|
|
9
|
+
const identifier = { type: "string", required: true, pattern: `^(?:[a-fA-F0-9]{32}|${UUID_SOURCE})$` };
|
|
9
10
|
const document = { type: "object", additionalProperties: true,
|
|
10
11
|
validator: value => Buffer.byteLength(JSON.stringify(value)) <= 65536 || "Keep each document below 64 KiB." };
|
|
11
12
|
const pagination = { page_size: { type: "integer", min: 1, max: 100, defaultTo: 100 },
|
package/src/server/pipedrive.js
CHANGED
|
@@ -3,6 +3,7 @@ import { validateSchemaPayload } from "@jskit-ai/kernel/shared/validators";
|
|
|
3
3
|
import { pipedriveDefinition } from "../shared/tokens.js";
|
|
4
4
|
import { jsonOperation } from "./jsonOperation.js";
|
|
5
5
|
import { ConnectorError } from "@jskit-ai/connectors-core/server";
|
|
6
|
+
import { UUID_SOURCE } from "../shared/uuid.js";
|
|
6
7
|
|
|
7
8
|
function companyApiDomain(value) {
|
|
8
9
|
if (typeof value !== "string" || !/^https:\/\/[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.pipedrive\.com$/.test(value)) {
|
|
@@ -26,7 +27,7 @@ const recordFields = {
|
|
|
26
27
|
};
|
|
27
28
|
function crmOperation(collection, action) {
|
|
28
29
|
const lead = collection === "leads", write = ["create", "update"].includes(action);
|
|
29
|
-
const id = lead ? { type: "string", pattern:
|
|
30
|
+
const id = lead ? { type: "string", pattern: `^${UUID_SOURCE}$` } : numberId;
|
|
30
31
|
const primary = ["deals", "leads"].includes(collection) ? "title" : collection === "activities" ? "subject" : "name";
|
|
31
32
|
const fields = write ? { ...recordFields[collection], ...(action === "create" ? { [primary]: { ...text, required: true } } : {}) } : action === "list"
|
|
32
33
|
? { limit: { type: "integer", min: 1, max: 500, defaultTo: 100 }, ...(lead ? { start: { type: "integer", min: 0 } } : { cursor: { type: "string", minLength: 1, maxLength: 4096 } }) } : {};
|
package/src/server/salesforce.js
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
import { createSchema } from "json-rest-schema";
|
|
2
2
|
import { validateSchemaPayload } from "@jskit-ai/kernel/shared/validators";
|
|
3
|
-
import { ConnectorError } from "@jskit-ai/connectors-core/server";
|
|
3
|
+
import { ConnectorError, providerStatusError } from "@jskit-ai/connectors-core/server";
|
|
4
4
|
import { salesforceDefinition } from "../shared/salesforce.js";
|
|
5
5
|
import { jsonOperation } from "./jsonOperation.js";
|
|
6
|
+
import { isRecord } from "@jskit-ai/kernel/shared/support/normalize";
|
|
7
|
+
import { accountOrigin } from "./connectorValues.js";
|
|
6
8
|
|
|
7
9
|
const apiPath = "/services/data/v66.0";
|
|
8
|
-
const
|
|
9
|
-
const endpoint = (resource) => (settings) => `${origin(settings)}${apiPath}/${resource}`;
|
|
10
|
+
const endpoint = (resource) => (settings) => `${accountOrigin(settings)}${apiPath}/${resource}`;
|
|
10
11
|
const objectName = (value) => typeof value === "string" && /^[A-Za-z][A-Za-z0-9_]{0,254}$/u.test(value);
|
|
11
12
|
const locator = (value) => typeof value === "string" && /^\/services\/data\/v66\.0\/query\/[A-Za-z0-9]{15,18}-[0-9]{1,10}$/u.test(value);
|
|
12
|
-
const
|
|
13
|
-
const limits = (value) => record(value) && Number.isSafeInteger(value.DailyApiRequests?.Max) && value.DailyApiRequests.Max >= 0 &&
|
|
13
|
+
const limits = (value) => isRecord(value) && Number.isSafeInteger(value.DailyApiRequests?.Max) && value.DailyApiRequests.Max >= 0 &&
|
|
14
14
|
Number.isSafeInteger(value.DailyApiRequests.Remaining) && value.DailyApiRequests.Remaining >= 0;
|
|
15
15
|
const queryResult = (value) => Number.isSafeInteger(value?.totalSize) && value.totalSize >= 0 && typeof value.done === "boolean" &&
|
|
16
|
-
Array.isArray(value.records) && value.records.length <= 2000 && value.records.every(
|
|
16
|
+
Array.isArray(value.records) && value.records.length <= 2000 && value.records.every(isRecord) && value.records.length <= value.totalSize &&
|
|
17
17
|
(value.done ? value.nextRecordsUrl == null : locator(value.nextRecordsUrl));
|
|
18
18
|
const describeSchema = createSchema({ object: { type: "string", required: true, validator: (value) => objectName(value) || "Use a Salesforce object API name." } });
|
|
19
19
|
const nextSchema = createSchema({ nextRecordsUrl: { type: "string", required: true, noTrim: true,
|
|
@@ -21,13 +21,13 @@ const nextSchema = createSchema({ nextRecordsUrl: { type: "string", required: tr
|
|
|
21
21
|
|
|
22
22
|
const salesforceProvider = Object.freeze({
|
|
23
23
|
...salesforceDefinition,
|
|
24
|
-
oauth: (settings) => ({ issuer:
|
|
25
|
-
apiOrigins: (settings) => [
|
|
24
|
+
oauth: (settings) => ({ issuer: accountOrigin(settings), authorization_endpoint: `${accountOrigin(settings)}/services/oauth2/authorize`, token_endpoint: `${accountOrigin(settings)}/services/oauth2/token` }),
|
|
25
|
+
apiOrigins: (settings) => [accountOrigin(settings)], checkOperation: "limits.read",
|
|
26
26
|
async normalizeTokenResponse(response, { settings }) {
|
|
27
27
|
if (!response.ok) return response;
|
|
28
28
|
let value;
|
|
29
29
|
try { value = await response.clone().json(); } catch { /* reported below */ }
|
|
30
|
-
if (!
|
|
30
|
+
if (!isRecord(value) || ![accountOrigin(settings), accountOrigin(settings) + "/"].includes(value.instance_url) || typeof value.scope !== "string" || !value.scope.trim()) {
|
|
31
31
|
throw new ConnectorError("connector_response_invalid", "Salesforce returned an invalid grant or a different organisation.", { statusCode: 502, messageCode: "connectors.salesforce.returnedInvalidGrantDifferentOrganisation" });
|
|
32
32
|
}
|
|
33
33
|
// Salesforce can omit expiry. Renew after five minutes of use as a local
|
|
@@ -54,7 +54,7 @@ const salesforceProvider = Object.freeze({
|
|
|
54
54
|
scopes: ["api"],
|
|
55
55
|
request(input, settings) {
|
|
56
56
|
const values = validateSchemaPayload({ schema: nextSchema, mode: "replace" }, input, { statusCode: 422 });
|
|
57
|
-
return { method: "GET", url:
|
|
57
|
+
return { method: "GET", url: accountOrigin(settings) + values.nextRecordsUrl };
|
|
58
58
|
},
|
|
59
59
|
validateResult: queryResult
|
|
60
60
|
}
|
|
@@ -70,9 +70,9 @@ const salesforceProvider = Object.freeze({
|
|
|
70
70
|
if (code === "INVALID_QUERY_LOCATOR") throw new ConnectorError("connector_cursor_expired", "Start the Salesforce query again; its result cursor is no longer valid.", { statusCode: 410, messageCode: "connectors.salesforce.startQueryAgainResultCursor" });
|
|
71
71
|
if (response.status === 404) throw new ConnectorError("connector_resource_not_found", "This Salesforce resource is not available.", { statusCode: 404, messageCode: "connectors.shared.resourceUnavailable", messageParams: { provider: "Salesforce" } });
|
|
72
72
|
if (response.status === 400) throw new ConnectorError("connector_query_invalid", "Salesforce rejected the query or object. Check its API names and the user's permissions.", { statusCode: 422, messageCode: "connectors.salesforce.rejectedQueryObjectCheckApi" });
|
|
73
|
-
throw
|
|
73
|
+
throw providerStatusError(response.status);
|
|
74
74
|
}
|
|
75
|
-
if (response.status !== 200 || !
|
|
75
|
+
if (response.status !== 200 || !isRecord(value)) throw new ConnectorError("connector_response_invalid", "Salesforce returned an unexpected response.", { statusCode: 502, messageCode: "connectors.shared.unexpectedResponse", messageParams: { provider: "Salesforce" } });
|
|
76
76
|
const url = new URL(address);
|
|
77
77
|
if (url.pathname.endsWith("/describe") && value.name !== url.pathname.split("/").at(-2)) {
|
|
78
78
|
throw new ConnectorError("connector_response_invalid", "Salesforce described a different object.", { statusCode: 502, messageCode: "connectors.salesforce.describedDifferentObject" });
|
package/src/server/semrush.js
CHANGED
|
@@ -3,6 +3,8 @@ import { validateSchemaPayload } from "@jskit-ai/kernel/shared/validators";
|
|
|
3
3
|
import { ConnectorError } from "@jskit-ai/connectors-core/server";
|
|
4
4
|
import { semrushDefinition } from "../shared/semrush.js";
|
|
5
5
|
import { jsonOperation } from "./jsonOperation.js";
|
|
6
|
+
import { isCount } from "./connectorValues.js";
|
|
7
|
+
import { isRecord } from "@jskit-ai/kernel/shared/support/normalize";
|
|
6
8
|
|
|
7
9
|
const projectsUrl = "https://api.semrush.com/apis/v4/projects/v1/projects";
|
|
8
10
|
const projectSchema = createSchema({ projectId: { type: "integer", required: true, min: 1, max: Number.MAX_SAFE_INTEGER } });
|
|
@@ -60,7 +62,7 @@ function semrushKeywordReport(target, kind) {
|
|
|
60
62
|
};
|
|
61
63
|
}
|
|
62
64
|
|
|
63
|
-
const trackingObject = (value) => Boolean(
|
|
65
|
+
const trackingObject = (value) => Boolean(isRecord(value) && value.error === undefined);
|
|
64
66
|
|
|
65
67
|
function semrushTrackingReport(type, positions = false) {
|
|
66
68
|
const operation = jsonOperation("https://api.semrush.com/", {
|
|
@@ -133,22 +135,21 @@ function v4Result(check, status = 200) {
|
|
|
133
135
|
typeof value.meta.request_id === "string" && value.meta.request_id.length > 0 && value.error === undefined &&
|
|
134
136
|
Boolean(check(value, new URL(request.url), request));
|
|
135
137
|
}
|
|
136
|
-
const count = (value) => Number.isSafeInteger(value) && value >= 0;
|
|
137
138
|
// Backlink reports keep the requested scope and, when Semrush supplies them, the requested page bounds.
|
|
138
139
|
function backlinkResult(report) {
|
|
139
140
|
return v4Result((value, url) => {
|
|
140
141
|
const { meta, data } = value;
|
|
141
142
|
if (meta.scope !== undefined && meta.scope !== url.searchParams.get("scope")) return false;
|
|
142
|
-
if (report === "overview") return ["backlinks_count", "domains_count", "urls_count", "score"].every((field) =>
|
|
143
|
+
if (report === "overview") return ["backlinks_count", "domains_count", "urls_count", "score"].every((field) => isCount(data?.[field]));
|
|
143
144
|
// Some report envelopes omit pagination metadata; validate it when supplied.
|
|
144
145
|
return Array.isArray(data) && data.length <= Number(url.searchParams.get("limit")) && data.every((row) => {
|
|
145
146
|
if (!row || typeof row !== "object" || Array.isArray(row)) return false;
|
|
146
147
|
if (report === "links") return typeof row.source_url === "string" && typeof row.target_url === "string" &&
|
|
147
148
|
typeof row.anchor === "string" && typeof row.is_nofollow === "boolean";
|
|
148
|
-
if (report === "ref-domains") return typeof row.domain === "string" &&
|
|
149
|
-
return typeof row.anchor === "string" &&
|
|
149
|
+
if (report === "ref-domains") return typeof row.domain === "string" && isCount(row.backlinks_count) && isCount(row.domain_score);
|
|
150
|
+
return typeof row.anchor === "string" && isCount(row.backlinks_count) && isCount(row.domains_count);
|
|
150
151
|
}) && ["limit", "offset"].every((field) => meta[field] === undefined || String(meta[field]) === url.searchParams.get(field)) &&
|
|
151
|
-
(meta.total === undefined ||
|
|
152
|
+
(meta.total === undefined || isCount(meta.total));
|
|
152
153
|
});
|
|
153
154
|
}
|
|
154
155
|
const requestedProject = (url) => Number(url.pathname.split("/").at(-1));
|
|
@@ -222,7 +223,7 @@ const semrushProvider = Object.freeze({
|
|
|
222
223
|
validateResult: v4Result(({ meta, data }, url) => meta.keyword === url.searchParams.get("keyword") && meta.country === url.searchParams.get("country") &&
|
|
223
224
|
typeof meta.month === "string" && /^\d{4}-(0[1-9]|1[0-2])$/u.test(meta.month) &&
|
|
224
225
|
(!url.searchParams.has("month") || meta.month === url.searchParams.get("month")) &&
|
|
225
|
-
|
|
226
|
+
isRecord(data) &&
|
|
226
227
|
["cpc", "number_of_results", "search_volume"].every((key) => typeof data[key] === "string" && /^\d+$/u.test(data[key])) &&
|
|
227
228
|
["competitive_density", "keyword_difficulty"].every((key) => Number.isFinite(data[key])) &&
|
|
228
229
|
["intents", "serp_features"].every((key) => Array.isArray(data[key]) && data[key].every((value) => typeof value === "string")) &&
|
|
@@ -249,7 +250,7 @@ const semrushProvider = Object.freeze({
|
|
|
249
250
|
}, v4Result(({ meta, data }, url) => {
|
|
250
251
|
const limit = Number(url.searchParams.get("limit"));
|
|
251
252
|
return meta.scope === url.searchParams.get("scope") && meta.limit === limit && meta.offset === Number(url.searchParams.get("offset")) &&
|
|
252
|
-
|
|
253
|
+
isCount(meta.total_count) && Array.isArray(data) && data.length <= limit && data.length <= meta.total_count && data.every(validProject);
|
|
253
254
|
})),
|
|
254
255
|
"projects.delete": {
|
|
255
256
|
scopes: [],
|
package/src/server/snowflake.js
CHANGED
|
@@ -1,26 +1,26 @@
|
|
|
1
|
-
import { ConnectorError } from "@jskit-ai/connectors-core/server";
|
|
1
|
+
import { ConnectorError, providerStatusError } from "@jskit-ai/connectors-core/server";
|
|
2
2
|
import { snowflakeDefinition } from "../shared/snowflake.js";
|
|
3
3
|
import { jsonOperation } from "./jsonOperation.js";
|
|
4
|
+
import { isRecord } from "@jskit-ai/kernel/shared/support/normalize";
|
|
5
|
+
import { accountOrigin } from "./connectorValues.js";
|
|
6
|
+
import { isUuid } from "../shared/uuid.js";
|
|
4
7
|
|
|
5
|
-
const
|
|
6
|
-
const object = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
7
|
-
const database = (value) => object(value) && typeof value.name === "string" && value.name.length > 0 && value.name.length <= 255 &&
|
|
8
|
+
const database = (value) => isRecord(value) && typeof value.name === "string" && value.name.length > 0 && value.name.length <= 255 &&
|
|
8
9
|
["owner", "comment", "kind", "created_on"].every((key) => value[key] == null || typeof value[key] === "string");
|
|
9
10
|
const nameFilter = { type: "string", minLength: 1, maxLength: 255, noTrim: true };
|
|
10
|
-
const databasesList = jsonOperation((settings) => `${
|
|
11
|
+
const databasesList = jsonOperation((settings) => `${accountOrigin(settings)}/api/v2/databases`, {
|
|
11
12
|
showLimit: { type: "integer", min: 1, max: 1000, defaultTo: 20 },
|
|
12
13
|
like: nameFilter, startsWith: nameFilter, fromName: nameFilter, history: { type: "boolean", defaultTo: false }
|
|
13
14
|
}, (value) => Array.isArray(value) && value.every(database));
|
|
14
15
|
|
|
15
|
-
const
|
|
16
|
-
const uuidField = { type: "string", required: true, validator: (value) => uuid(value) || "Use a Snowflake UUID." };
|
|
16
|
+
const uuidField = { type: "string", required: true, validator: (value) => isUuid(value) || "Use a Snowflake UUID." };
|
|
17
17
|
const bindings = { type: "object", additionalProperties: true, validator: (value) =>
|
|
18
18
|
Object.keys(value).length <= 100 && Object.entries(value).every(([key, binding]) =>
|
|
19
|
-
/^[1-9][0-9]{0,2}$/u.test(key) &&
|
|
19
|
+
/^[1-9][0-9]{0,2}$/u.test(key) && isRecord(binding) && Object.keys(binding).every((name) => ["type", "value"].includes(name)) &&
|
|
20
20
|
["FIXED", "REAL", "TEXT", "BINARY", "BOOLEAN", "DATE", "TIME", "TIMESTAMP_LTZ", "TIMESTAMP_NTZ", "TIMESTAMP_TZ"].includes(binding.type) &&
|
|
21
21
|
(binding.value === null || (typeof binding.value === "string" && binding.value.length <= 65536))) || "Use at most 100 numbered typed bindings." };
|
|
22
|
-
const sqlResult = (value) =>
|
|
23
|
-
const submit = jsonOperation((settings) => `${
|
|
22
|
+
const sqlResult = (value) => isRecord(value) && ["pending", "completed", "cancelled"].includes(value.status);
|
|
23
|
+
const submit = jsonOperation((settings) => `${accountOrigin(settings)}/api/v2/statements`, {
|
|
24
24
|
statement: { type: "string", required: true, minLength: 1, maxLength: 65536, noTrim: true },
|
|
25
25
|
requestId: uuidField, bindings, timeout: { type: "integer", min: 1, max: 3600, defaultTo: 60 }
|
|
26
26
|
}, sqlResult, "POST");
|
|
@@ -34,7 +34,7 @@ const operations = { "databases.list": databasesList,
|
|
|
34
34
|
} }
|
|
35
35
|
};
|
|
36
36
|
for (const [operation, cancel] of [["statements.get", false], ["statements.cancel", true]]) {
|
|
37
|
-
const base = jsonOperation((settings) => `${
|
|
37
|
+
const base = jsonOperation((settings) => `${accountOrigin(settings)}/api/v2/statements`, {
|
|
38
38
|
handle: uuidField, ...(!cancel ? { partition: { type: "integer", min: 0, max: 100000 } } : {})
|
|
39
39
|
}, sqlResult, "POST");
|
|
40
40
|
operations[operation] = { ...base, request(input, settings) {
|
|
@@ -47,7 +47,7 @@ for (const action of ["create", "resize", "resume", "suspend", "delete"]) {
|
|
|
47
47
|
const fields = { name: { ...nameFilter, required: true }, requestId: uuidField };
|
|
48
48
|
if (["create", "resize"].includes(action)) fields.size = { type: "string", required: true,
|
|
49
49
|
enum: ["XSMALL", "SMALL", "MEDIUM", "LARGE", "XLARGE", "XXLARGE", "XXXLARGE", "X4LARGE", "X5LARGE", "X6LARGE"] };
|
|
50
|
-
const base = jsonOperation((settings) => `${
|
|
50
|
+
const base = jsonOperation((settings) => `${accountOrigin(settings)}/api/v2/statements`, fields, sqlResult, "POST");
|
|
51
51
|
operations[`warehouses.${action}`] = { ...base, request(input, settings) {
|
|
52
52
|
const { body } = base.request(input, settings);
|
|
53
53
|
const name = `"${body.name.replaceAll('"', '""')}"`;
|
|
@@ -68,9 +68,9 @@ for (const [name, operation] of Object.entries(operations)) {
|
|
|
68
68
|
|
|
69
69
|
const snowflakeProvider = Object.freeze({
|
|
70
70
|
...snowflakeDefinition,
|
|
71
|
-
oauth: (settings) => ({ issuer:
|
|
72
|
-
token_endpoint: `${
|
|
73
|
-
apiOrigins: (settings) => [
|
|
71
|
+
oauth: (settings) => ({ issuer: accountOrigin(settings), authorization_endpoint: `${accountOrigin(settings)}/oauth/authorize`,
|
|
72
|
+
token_endpoint: `${accountOrigin(settings)}/oauth/token-request` }),
|
|
73
|
+
apiOrigins: (settings) => [accountOrigin(settings)], checkOperation: "databases.list", scopesInAuthorizationResponse: true,
|
|
74
74
|
async normalizeTokenResponse(response) {
|
|
75
75
|
if (response.ok) {
|
|
76
76
|
let value;
|
|
@@ -90,7 +90,7 @@ const snowflakeProvider = Object.freeze({
|
|
|
90
90
|
const url = new URL(address);
|
|
91
91
|
const sql = url.pathname.startsWith("/api/v2/statements");
|
|
92
92
|
if (sql && response.status === 422) throw new ConnectorError("connector_operation_rejected", "Snowflake rejected the statement. Inspect query history with the authorized role.", { statusCode: 422, messageCode: "connectors.snowflake.rejectedStatementInspectQueryHistory" });
|
|
93
|
-
if (!response.ok) throw
|
|
93
|
+
if (!response.ok) throw providerStatusError(response.status);
|
|
94
94
|
const reader = response.body?.getReader();
|
|
95
95
|
const chunks = [];
|
|
96
96
|
let size = 0;
|
|
@@ -110,10 +110,10 @@ const snowflakeProvider = Object.freeze({
|
|
|
110
110
|
try { result = JSON.parse(Buffer.concat(chunks).toString("utf8")); } catch { /* rejected below */ }
|
|
111
111
|
if (sql) {
|
|
112
112
|
const reject = () => { throw new ConnectorError("connector_response_invalid", "Snowflake returned an invalid statement response.", { statusCode: 502, messageCode: "connectors.snowflake.returnedInvalidStatementResponse" }); };
|
|
113
|
-
if (!
|
|
113
|
+
if (!isRecord(result)) reject();
|
|
114
114
|
const requestedHandle = url.pathname.split("/")[4];
|
|
115
115
|
const handle = result.statementHandle ?? requestedHandle;
|
|
116
|
-
if (!
|
|
116
|
+
if (!isUuid(handle) || (requestedHandle && requestedHandle !== handle)) reject();
|
|
117
117
|
if (response.status === 202 && result.code === "333334") return { status: "pending", handle };
|
|
118
118
|
if (response.status !== 200) reject();
|
|
119
119
|
if (url.pathname.endsWith("/cancel")) {
|
|
@@ -123,8 +123,8 @@ const snowflakeProvider = Object.freeze({
|
|
|
123
123
|
if (result.code !== undefined && !["090001", "391908"].includes(result.code)) reject();
|
|
124
124
|
if (!Array.isArray(result.data) || !result.data.every((row) => Array.isArray(row) && row.every((cell) => cell === null || typeof cell === "string"))) reject();
|
|
125
125
|
const metadata = result.resultSetMetaData;
|
|
126
|
-
if (metadata !== undefined && (!
|
|
127
|
-
!metadata.rowType.every((column) =>
|
|
126
|
+
if (metadata !== undefined && (!isRecord(metadata) || !Array.isArray(metadata.rowType) ||
|
|
127
|
+
!metadata.rowType.every((column) => isRecord(column) && typeof column.name === "string" && typeof column.type === "string") ||
|
|
128
128
|
!result.data.every((row) => row.length === metadata.rowType.length))) reject();
|
|
129
129
|
if (metadata === undefined && !url.searchParams.has("partition")) reject();
|
|
130
130
|
return { status: "completed", handle, data: result.data, ...(metadata ? { metadata } : {}),
|
package/src/server/storyblok.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { storyblokDefinition } from "../shared/tokens.js";
|
|
2
2
|
import { jsonOperation } from "./jsonOperation.js";
|
|
3
|
+
import { isRecord } from "@jskit-ai/kernel/shared/support/normalize";
|
|
3
4
|
|
|
4
5
|
const origins = {
|
|
5
6
|
eu: "https://api.storyblok.com", us: "https://api-us.storyblok.com",
|
|
@@ -15,7 +16,7 @@ const contentFields = {
|
|
|
15
16
|
resolve_assets: { type: "integer", enum: [0, 1] }
|
|
16
17
|
};
|
|
17
18
|
const story = (value) => Number.isSafeInteger(value?.id) && value.id > 0 &&
|
|
18
|
-
|
|
19
|
+
isRecord(value.content);
|
|
19
20
|
const getStory = jsonOperation((settings) => `${origins[settings.region]}/v2/cdn/stories`, {
|
|
20
21
|
...contentFields,
|
|
21
22
|
id: { type: "string", required: true, minLength: 1, maxLength: 1024,
|
package/src/server/tally.js
CHANGED
|
@@ -3,11 +3,12 @@ import { validateSchemaPayload } from "@jskit-ai/kernel/shared/validators";
|
|
|
3
3
|
import { ConnectorError } from "@jskit-ai/connectors-core/server";
|
|
4
4
|
import { tallyDefinition } from "../shared/tokens.js";
|
|
5
5
|
import { jsonOperation } from "./jsonOperation.js";
|
|
6
|
+
import { isRecord } from "@jskit-ai/kernel/shared/support/normalize";
|
|
6
7
|
|
|
7
8
|
const formIdField = { type: "string", required: true, noTrim: true,
|
|
8
9
|
validator: (value) => /^[A-Za-z0-9_-]{1,128}$/u.test(value) || "Enter a Tally form ID, not a URL." };
|
|
9
10
|
const blocksField = { type: "array", validator: (value) => value.length <= 500 &&
|
|
10
|
-
value.every((block) =>
|
|
11
|
+
value.every((block) => isRecord(block) && typeof block.type === "string") || "Supply up to 500 native Tally block objects." };
|
|
11
12
|
const statusField = { type: "string", enum: ["BLANK", "DRAFT", "PUBLISHED"] };
|
|
12
13
|
const formResult = (result) => typeof result?.id === "string" && typeof result.name === "string" &&
|
|
13
14
|
["BLANK", "DRAFT", "PUBLISHED"].includes(result.status);
|
package/src/server/telegram.js
CHANGED
|
@@ -1,13 +1,9 @@
|
|
|
1
|
-
import { ConnectorError } from "@jskit-ai/connectors-core/server";
|
|
1
|
+
import { ConnectorError, providerStatusError } from "@jskit-ai/connectors-core/server";
|
|
2
2
|
import { telegramDefinition } from "../shared/tokens.js";
|
|
3
3
|
import { jsonOperation } from "./jsonOperation.js";
|
|
4
4
|
|
|
5
5
|
function telegramResult(response, validate) {
|
|
6
|
-
if (response?.ok === false)
|
|
7
|
-
const error = new Error("Telegram rejected the operation.");
|
|
8
|
-
error.status = response.error_code;
|
|
9
|
-
throw error;
|
|
10
|
-
}
|
|
6
|
+
if (response?.ok === false) throw providerStatusError(Number(response.error_code));
|
|
11
7
|
return response?.ok === true && validate(response.result);
|
|
12
8
|
}
|
|
13
9
|
|