@jskit-ai/connectors-catalog 0.2.3 → 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.
Files changed (192) hide show
  1. package/README.md +4 -4
  2. package/docs/ai.md +3 -3
  3. package/docs/airtable.md +1 -1
  4. package/docs/algolia.md +2 -2
  5. package/docs/amazon-redshift.md +2 -2
  6. package/docs/apollo-io.md +2 -2
  7. package/docs/asana.md +2 -2
  8. package/docs/ashby.md +1 -1
  9. package/docs/attention.md +2 -2
  10. package/docs/aws-athena.md +1 -1
  11. package/docs/aws-credentials.md +3 -3
  12. package/docs/aws-s3.md +1 -1
  13. package/docs/bigquery.md +2 -2
  14. package/docs/brevo.md +2 -2
  15. package/docs/calendly.md +3 -3
  16. package/docs/canva.md +2 -2
  17. package/docs/chargebee.md +2 -2
  18. package/docs/clay.md +2 -2
  19. package/docs/clickhouse.md +1 -1
  20. package/docs/confidence-flags.md +1 -1
  21. package/docs/contentful.md +1 -1
  22. package/docs/dbt-semantic-layer.md +2 -2
  23. package/docs/elevenlabs.md +2 -2
  24. package/docs/firebase-cloud-messaging.md +3 -3
  25. package/docs/firecrawl.md +4 -4
  26. package/docs/fireflies.md +1 -1
  27. package/docs/gatewayapi.md +1 -1
  28. package/docs/gemini-enterprise.md +1 -1
  29. package/docs/github-api.md +1 -1
  30. package/docs/gitlab-api.md +1 -1
  31. package/docs/gmail.md +1 -1
  32. package/docs/gong.md +1 -1
  33. package/docs/google-ads.md +3 -3
  34. package/docs/google-analytics.md +2 -2
  35. package/docs/google-docs.md +1 -1
  36. package/docs/google-drive.md +1 -1
  37. package/docs/google-maps-platform.md +2 -2
  38. package/docs/google-search-console.md +1 -1
  39. package/docs/google-sheets.md +1 -1
  40. package/docs/google-slides.md +2 -2
  41. package/docs/granola.md +2 -2
  42. package/docs/hex.md +2 -2
  43. package/docs/heygen.md +2 -2
  44. package/docs/inngest.md +1 -1
  45. package/docs/klipy.md +1 -1
  46. package/docs/lightspeed.md +2 -2
  47. package/docs/linear.md +1 -1
  48. package/docs/linkedin.md +2 -2
  49. package/docs/mailgun.md +1 -1
  50. package/docs/microsoft-excel.md +2 -2
  51. package/docs/microsoft-fabric.md +1 -1
  52. package/docs/microsoft-outlook.md +1 -1
  53. package/docs/microsoft-powerpoint.md +1 -1
  54. package/docs/microsoft-teams.md +1 -1
  55. package/docs/microsoft-word.md +1 -1
  56. package/docs/notion.md +1 -1
  57. package/docs/oura.md +3 -3
  58. package/docs/paddle.md +3 -3
  59. package/docs/pipedrive.md +2 -2
  60. package/docs/prestashop.md +1 -1
  61. package/docs/replicate.md +1 -1
  62. package/docs/resend.md +1 -1
  63. package/docs/salesforce.md +1 -1
  64. package/docs/sanity.md +3 -3
  65. package/docs/semrush.md +2 -2
  66. package/docs/sentry.md +1 -1
  67. package/docs/slack.md +1 -1
  68. package/docs/snowflake.md +5 -5
  69. package/docs/storyblok.md +1 -1
  70. package/docs/stripe.md +2 -2
  71. package/docs/tally.md +1 -1
  72. package/docs/telegram.md +2 -2
  73. package/docs/tiktok.md +3 -3
  74. package/docs/twilio.md +3 -3
  75. package/docs/twitch.md +2 -2
  76. package/docs/wave.md +3 -3
  77. package/docs/wix.md +13 -13
  78. package/docs/wordpress-com.md +1 -1
  79. package/docs/wordpress-self-hosted.md +1 -1
  80. package/docs/workday.md +2 -2
  81. package/docs/x-twitter.md +3 -3
  82. package/docs/xero.md +3 -3
  83. package/docs/zoho-books.md +2 -2
  84. package/docs/zoho-crm.md +2 -2
  85. package/package.json +4 -3
  86. package/patterns/ai-connections/PATTERN.md +16 -1
  87. package/patterns/ai-connections/example/ai-model.js +12 -3
  88. package/patterns/ai-connections/example/perplexity-answer.js +15 -4
  89. package/patterns/api-key-connection/PATTERN.md +9 -1
  90. package/patterns/api-key-connection/example/connections.js +5 -2
  91. package/patterns/aws-storage-queries/PATTERN.md +13 -3
  92. package/patterns/aws-storage-queries/example/s3-transfer.js +14 -3
  93. package/patterns/event-delivery/PATTERN.md +1 -1
  94. package/patterns/firebase-messaging/PATTERN.md +10 -0
  95. package/patterns/firebase-messaging/example/server/notifications.js +8 -0
  96. package/patterns/google-ads-search/PATTERN.md +18 -7
  97. package/patterns/google-ads-search/example/ads-setup.js +13 -5
  98. package/patterns/oauth-connection/PATTERN.md +1 -1
  99. package/patterns/paddle-catalogue/PATTERN.md +11 -2
  100. package/patterns/paddle-catalogue/example/create-products.js +5 -2
  101. package/patterns/public-image/PATTERN.md +10 -1
  102. package/patterns/public-image/example/logo-url.js +4 -1
  103. package/patterns/redshift-queries/PATTERN.md +1 -1
  104. package/patterns/source-scanning/PATTERN.md +13 -2
  105. package/patterns/source-scanning/example/source-scanner.js +9 -0
  106. package/src/server/ai.js +19 -5
  107. package/src/server/apify.js +2 -2
  108. package/src/server/asana.js +6 -6
  109. package/src/server/ashby.js +2 -1
  110. package/src/server/attention.js +12 -11
  111. package/src/server/awsTransport.js +6 -0
  112. package/src/server/bigquery.js +5 -4
  113. package/src/server/brevo.js +2 -1
  114. package/src/server/clay.js +2 -1
  115. package/src/server/clickhouse.js +2 -1
  116. package/src/server/confidence.js +6 -1
  117. package/src/server/connectorValues.js +33 -0
  118. package/src/server/databricks.js +7 -7
  119. package/src/server/dbt-semantic-layer.js +5 -5
  120. package/src/server/elevenlabs.js +2 -2
  121. package/src/server/firebase-cloud-messaging.js +2 -2
  122. package/src/server/gemini-enterprise.js +5 -5
  123. package/src/server/google-ads-search.js +68 -48
  124. package/src/server/google-ads.js +15 -17
  125. package/src/server/google-drive.js +7 -7
  126. package/src/server/google-maps-platform.js +4 -2
  127. package/src/server/google-search-console.js +4 -3
  128. package/src/server/google.js +8 -4
  129. package/src/server/googleAdsApi.js +14 -0
  130. package/src/server/granola.js +2 -2
  131. package/src/server/hubspot.js +2 -1
  132. package/src/server/incident-io.js +3 -2
  133. package/src/server/lexware.js +5 -4
  134. package/src/server/lightspeed.js +7 -5
  135. package/src/server/linkedin.js +2 -2
  136. package/src/server/mcpTransport.js +8 -4
  137. package/src/server/microsoft-fabric.js +3 -3
  138. package/src/server/microsoft-onedrive.js +2 -2
  139. package/src/server/microsoft-onenote.js +4 -3
  140. package/src/server/microsoft-outlook.js +6 -6
  141. package/src/server/microsoft-sharepoint.js +4 -2
  142. package/src/server/microsoft-teams.js +2 -1
  143. package/src/server/microsoft.js +20 -13
  144. package/src/server/notion.js +2 -1
  145. package/src/server/pipedrive.js +2 -1
  146. package/src/server/salesforce.js +12 -12
  147. package/src/server/semrush.js +9 -8
  148. package/src/server/snowflake.js +20 -20
  149. package/src/server/storyblok.js +2 -1
  150. package/src/server/tally.js +2 -1
  151. package/src/server/telegram.js +2 -6
  152. package/src/server/tiktok.js +3 -3
  153. package/src/server/twilio.js +3 -2
  154. package/src/server/twitch.js +5 -3
  155. package/src/server/types.js +13 -0
  156. package/src/server/wix.js +20 -878
  157. package/src/server/wixBookings.js +449 -0
  158. package/src/server/wixCms.js +132 -0
  159. package/src/server/wixStores.js +296 -0
  160. package/src/server/wixValues.js +35 -0
  161. package/src/server/wiz.js +18 -4
  162. package/src/server/woocommerce.js +1 -1
  163. package/src/server/wordpress-com.js +2 -1
  164. package/src/server/workday.js +9 -9
  165. package/src/server/x-twitter.js +2 -1
  166. package/src/server/xero.js +40 -40
  167. package/src/server/zoho-books.js +6 -6
  168. package/src/server/zoho-crm.js +5 -5
  169. package/src/shared/confidence.js +6 -1
  170. package/src/shared/dbt-semantic-layer.js +1 -1
  171. package/src/shared/figma.js +1 -1
  172. package/src/shared/google.js +8 -4
  173. package/src/shared/googleScope.js +4 -0
  174. package/src/shared/microsoft-fabric.js +4 -5
  175. package/src/shared/microsoft.js +8 -3
  176. package/src/shared/salesforce.js +1 -1
  177. package/src/shared/tokens.js +7 -3
  178. package/src/shared/uuid.js +13 -0
  179. package/src/shared/wix.js +4 -3
  180. package/src/shared/zoho-books.js +3 -4
  181. package/src/shared/zoho-crm.js +1 -1
  182. package/src/shared/{zoho-regions.js → zoho.js} +4 -1
  183. package/test/ai.test.js +1 -1
  184. package/test/brevo.test.js +1 -1
  185. package/test/chargebee.test.js +1 -1
  186. package/test/google-ads.test.js +8 -4
  187. package/test/linkedin.test.js +1 -1
  188. package/test/resend.test.js +2 -2
  189. package/test/salesforce.test.js +1 -1
  190. package/test/snowflake.test.js +7 -7
  191. package/test/tiktok.test.js +1 -1
  192. package/test/wiz.test.js +1 -1
@@ -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 Object.assign(new Error("LinkedIn publishing failed."), { status: response.status });
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
- const error = new Error("MCP request failed.");
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) => object(value?.data) &&
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) => object(data.__schema) && Array.isArray(data.__schema.types) && data.__schema.types.every((type) => name(type?.name) &&
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 Object.assign(new Error("OneDrive upload failed; inspect the folder before retrying."), { status: response.status });
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: value => typeof value?.id === "string", request(input) {
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 Object.assign(new Error("OneNote request failed."), { status: response.status }); }
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
- const resource = value => typeof value?.id === "string";
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 => resource(value) && typeof value.body?.content === "string"),
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 => resource(value) && value["@odata.type"] === "#microsoft.graph.fileAttachment" && typeof value.contentBytes === "string" && value.contentBytes.length <= 6666668),
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 Object.assign(new Error("Outlook did not accept the message."), { status: response.ok ? 502 : response.status });
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: value => typeof value?.id === "string", request(input) {
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: value => value !== null && typeof value === "object" && !Array.isArray(value), request(input) {
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: value => typeof value?.id === "string", request(input) {
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
  } };
@@ -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" }, apiOrigins: [graphOrigin], checkOperation, operations
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 !== null && typeof value === "object" && !Array.isArray(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({ ...provider, async exchange(address, options, { request }) {
76
- const result = await request(address, options);
77
- if (new URL(address).pathname.endsWith("/children")) {
78
- if (!provider.operations["items.list"].validateResult(result)) {
79
- throw new ConnectorError("connector_response_invalid", "Microsoft returned an invalid file list.", { statusCode: 502, messageCode: "connectors.microsoft.returnedInvalidFileList" });
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
- return { ...result, value: result.value.filter((item) => item.folder || matches(item)) };
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
- if (!metadata(result)) throw new ConnectorError("connector_response_invalid", "Microsoft returned invalid file metadata.", { statusCode: 502, messageCode: "connectors.microsoft.returnedInvalidFileMetadata" });
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 };
@@ -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: "^(?:[a-fA-F0-9]{32}|[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12})$" };
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 },
@@ -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: "^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$" } : numberId;
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 } }) } : {};
@@ -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 origin = (settings) => new URL(settings.accountUrl).origin;
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 record = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
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(record) && value.records.length <= value.totalSize &&
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: origin(settings), authorization_endpoint: `${origin(settings)}/services/oauth2/authorize`, token_endpoint: `${origin(settings)}/services/oauth2/token` }),
25
- apiOrigins: (settings) => [origin(settings)], checkOperation: "limits.read",
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 (!record(value) || ![origin(settings), origin(settings) + "/"].includes(value.instance_url) || typeof value.scope !== "string" || !value.scope.trim()) {
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: origin(settings) + values.nextRecordsUrl };
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 Object.assign(new Error("Salesforce request failed."), { status: response.status });
73
+ throw providerStatusError(response.status);
74
74
  }
75
- if (response.status !== 200 || !record(value)) throw new ConnectorError("connector_response_invalid", "Salesforce returned an unexpected response.", { statusCode: 502, messageCode: "connectors.shared.unexpectedResponse", messageParams: { provider: "Salesforce" } });
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" });
@@ -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(value && typeof value === "object" && !Array.isArray(value) && value.error === undefined);
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) => count(data?.[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" && count(row.backlinks_count) && count(row.domain_score);
149
- return typeof row.anchor === "string" && count(row.backlinks_count) && count(row.domains_count);
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 || count(meta.total));
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
- data !== null && typeof data === "object" && !Array.isArray(data) &&
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
- count(meta.total_count) && Array.isArray(data) && data.length <= limit && data.length <= meta.total_count && data.every(validProject);
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: [],
@@ -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 origin = (settings) => new URL(settings.accountUrl).origin;
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) => `${origin(settings)}/api/v2/databases`, {
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 uuid = (value) => typeof value === "string" && /^[a-f0-9]{8}(?:-[a-f0-9]{4}){3}-[a-f0-9]{12}$/iu.test(value);
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) && object(binding) && Object.keys(binding).every((name) => ["type", "value"].includes(name)) &&
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) => object(value) && ["pending", "completed", "cancelled"].includes(value.status);
23
- const submit = jsonOperation((settings) => `${origin(settings)}/api/v2/statements`, {
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) => `${origin(settings)}/api/v2/statements`, {
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) => `${origin(settings)}/api/v2/statements`, fields, sqlResult, "POST");
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: origin(settings), authorization_endpoint: `${origin(settings)}/oauth/authorize`,
72
- token_endpoint: `${origin(settings)}/oauth/token-request` }),
73
- apiOrigins: (settings) => [origin(settings)], checkOperation: "databases.list", scopesInAuthorizationResponse: true,
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 Object.assign(new Error("Snowflake request failed."), { status: response.status });
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 (!object(result)) reject();
113
+ if (!isRecord(result)) reject();
114
114
  const requestedHandle = url.pathname.split("/")[4];
115
115
  const handle = result.statementHandle ?? requestedHandle;
116
- if (!uuid(handle) || (requestedHandle && requestedHandle !== handle)) reject();
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 && (!object(metadata) || !Array.isArray(metadata.rowType) ||
127
- !metadata.rowType.every((column) => object(column) && typeof column.name === "string" && typeof column.type === "string") ||
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 } : {}),
@@ -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
- value.content !== null && typeof value.content === "object" && !Array.isArray(value.content);
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,
@@ -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) => block && typeof block === "object" && !Array.isArray(block) && typeof block.type === "string") || "Supply up to 500 native Tally block objects." };
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);
@@ -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