@jskit-ai/connectors-catalog 0.1.67 → 0.2.0

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 (137) hide show
  1. package/CHANGELOG.md +1722 -0
  2. package/README.md +11 -0
  3. package/locales/en.json +3139 -0
  4. package/package.json +7 -6
  5. package/src/server/ai.js +4 -4
  6. package/src/server/amazon-redshift.js +5 -5
  7. package/src/server/apify.js +1 -1
  8. package/src/server/apollo-io.js +1 -1
  9. package/src/server/asana.js +3 -3
  10. package/src/server/ashby.js +3 -3
  11. package/src/server/attention.js +2 -2
  12. package/src/server/aws-athena.js +3 -3
  13. package/src/server/aws-s3.js +1 -1
  14. package/src/server/awsTransport.js +9 -9
  15. package/src/server/brevo.js +3 -3
  16. package/src/server/calendly.js +2 -2
  17. package/src/server/chargebee.js +1 -1
  18. package/src/server/databricks.js +2 -2
  19. package/src/server/dbt-semantic-layer.js +8 -8
  20. package/src/server/elevenlabs.js +4 -4
  21. package/src/server/firebase-cloud-messaging.js +8 -8
  22. package/src/server/firecrawl.js +2 -2
  23. package/src/server/gatewayapi.js +4 -4
  24. package/src/server/github-api.js +3 -3
  25. package/src/server/gitlab-api.js +1 -1
  26. package/src/server/gmail.js +1 -1
  27. package/src/server/gong.js +4 -4
  28. package/src/server/google-ads-search.js +16 -16
  29. package/src/server/google-ads.js +7 -7
  30. package/src/server/google-drive.js +8 -6
  31. package/src/server/google-maps-platform.js +5 -5
  32. package/src/server/google-search-console.js +1 -1
  33. package/src/server/google-sheets.js +3 -2
  34. package/src/server/google.js +3 -2
  35. package/src/server/granola.js +5 -5
  36. package/src/server/hubspot.js +5 -3
  37. package/src/server/inngest.js +4 -4
  38. package/src/server/klipy.js +1 -1
  39. package/src/server/lightspeed.js +4 -4
  40. package/src/server/linear.js +2 -2
  41. package/src/server/linkedin.js +6 -6
  42. package/src/server/mapbox.js +1 -1
  43. package/src/server/mcpRegistration.js +3 -2
  44. package/src/server/mcpTransport.js +1 -1
  45. package/src/server/microsoft-fabric.js +3 -3
  46. package/src/server/microsoft-onedrive.js +1 -1
  47. package/src/server/microsoft-onenote.js +1 -1
  48. package/src/server/microsoft-outlook.js +1 -1
  49. package/src/server/microsoft.js +4 -3
  50. package/src/server/n8n.js +4 -4
  51. package/src/server/oura.js +1 -1
  52. package/src/server/paddle.js +1 -1
  53. package/src/server/pipedrive.js +4 -4
  54. package/src/server/posthog.js +3 -3
  55. package/src/server/salesforce.js +8 -8
  56. package/src/server/semrush.js +25 -25
  57. package/src/server/shopify.js +6 -6
  58. package/src/server/slack.js +10 -10
  59. package/src/server/snowflake.js +5 -5
  60. package/src/server/tally.js +1 -1
  61. package/src/server/telegram.js +1 -1
  62. package/src/server/tiktok.js +14 -14
  63. package/src/server/twilio.js +3 -3
  64. package/src/server/twitch.js +22 -21
  65. package/src/server/wave.js +34 -34
  66. package/src/server/wix.js +73 -73
  67. package/src/server/wiz.js +18 -18
  68. package/src/server/woocommerce.js +23 -23
  69. package/src/server/wordpress-com.js +3 -3
  70. package/src/server/wordpress-self-hosted.js +2 -2
  71. package/src/server/workday.js +3 -3
  72. package/src/server/x-twitter.js +6 -6
  73. package/src/server/xero.js +15 -15
  74. package/src/server/zoho-books.js +8 -8
  75. package/src/server/zoho-crm.js +5 -5
  76. package/src/server/zohoOAuth.js +1 -1
  77. package/src/shared/ai.js +24 -13
  78. package/src/shared/algolia.js +4 -4
  79. package/src/shared/amazon-redshift.js +16 -16
  80. package/src/shared/amplitude.js +6 -6
  81. package/src/shared/atlassian.js +19 -19
  82. package/src/shared/aws.js +18 -18
  83. package/src/shared/canva.js +18 -18
  84. package/src/shared/chargebee.js +3 -3
  85. package/src/shared/clickhouse.js +9 -9
  86. package/src/shared/confidence.js +6 -6
  87. package/src/shared/databricks.js +8 -8
  88. package/src/shared/dbt-semantic-layer.js +7 -7
  89. package/src/shared/english-ai.js +6 -0
  90. package/src/shared/english-definitions.js +34 -0
  91. package/src/shared/english-texts.js +7 -0
  92. package/src/shared/figma.js +2 -2
  93. package/src/shared/firebase-cloud-messaging.js +11 -11
  94. package/src/shared/firecrawl.js +1 -1
  95. package/src/shared/gemini-enterprise.js +7 -7
  96. package/src/shared/gong.js +5 -5
  97. package/src/shared/google-ads.js +5 -5
  98. package/src/shared/google-maps-platform.js +7 -7
  99. package/src/shared/google.js +40 -40
  100. package/src/shared/granola.js +5 -5
  101. package/src/shared/hex.js +7 -7
  102. package/src/shared/inngest.js +8 -8
  103. package/src/shared/lightspeed.js +52 -52
  104. package/src/shared/linkedin.js +6 -6
  105. package/src/shared/logo-dev.js +3 -3
  106. package/src/shared/mapbox.js +6 -6
  107. package/src/shared/mcp.js +9 -9
  108. package/src/shared/microsoft-fabric.js +9 -9
  109. package/src/shared/microsoft.js +52 -52
  110. package/src/shared/miro.js +5 -5
  111. package/src/shared/oura.js +5 -5
  112. package/src/shared/posthog.js +9 -9
  113. package/src/shared/prestashop.js +5 -5
  114. package/src/shared/resend.js +1 -1
  115. package/src/shared/salesforce.js +8 -8
  116. package/src/shared/semrush.js +6 -6
  117. package/src/shared/shopify.js +26 -26
  118. package/src/shared/slack.js +61 -61
  119. package/src/shared/snowflake.js +15 -15
  120. package/src/shared/tiktok.js +7 -7
  121. package/src/shared/tokens.js +126 -126
  122. package/src/shared/twilio.js +7 -7
  123. package/src/shared/twitch.js +24 -24
  124. package/src/shared/wave.js +33 -33
  125. package/src/shared/wix.js +5 -5
  126. package/src/shared/wiz.js +14 -14
  127. package/src/shared/wordpress-com.js +9 -9
  128. package/src/shared/wordpress.js +11 -11
  129. package/src/shared/workday.js +8 -8
  130. package/src/shared/x-twitter.js +5 -5
  131. package/src/shared/xero.js +25 -25
  132. package/src/shared/zoho-books.js +10 -10
  133. package/src/shared/zoho-crm.js +16 -16
  134. package/src/shared/zoho-regions.js +8 -8
  135. package/test/englishDefinitions.test.js +37 -0
  136. package/test/i18nCatalog.test.js +16 -0
  137. package/test/shopify.test.js +3 -1
@@ -55,7 +55,7 @@ function analyticsOperation(kind, identity) {
55
55
  const query = new URL(request.url).searchParams;
56
56
  const start = query.get("started_at"), end = query.get("ended_at");
57
57
  if (Boolean(start) !== Boolean(end) || (start && start > end)) {
58
- throw new ConnectorError("connector_input_invalid", "Supply both report dates, with the end on or after the start.", { statusCode: 422 });
58
+ throw new ConnectorError("connector_input_invalid", "Supply both report dates, with the end on or after the start.", { statusCode: 422, messageCode: "connectors.twitch.supplyBothReportDatesEnd" });
59
59
  }
60
60
  return request;
61
61
  } };
@@ -105,7 +105,7 @@ function scheduleWrite(fields, method) {
105
105
  const request = operation.request(input);
106
106
  const url = new URL(request.url);
107
107
  if (request.body.id) { url.searchParams.set("id", request.body.id); delete request.body.id; }
108
- if (method === "PATCH" && !Object.keys(request.body).length) throw new ConnectorError("connector_input_invalid", "Supply at least one schedule change.", { statusCode: 422 });
108
+ if (method === "PATCH" && !Object.keys(request.body).length) throw new ConnectorError("connector_input_invalid", "Supply at least one schedule change.", { statusCode: 422, messageCode: "connectors.twitch.supplyLeastScheduleChange" });
109
109
  if (request.body.duration !== undefined) request.body.duration = String(request.body.duration);
110
110
  return { ...request, url: url.href, body: method === "DELETE" ? undefined : request.body };
111
111
  } };
@@ -118,36 +118,37 @@ function rewardWrite(path, fields, method, queryNames, validateResult) {
118
118
  for (const [enabled, amount] of [["is_max_per_stream_enabled", "max_per_stream"],
119
119
  ["is_max_per_user_per_stream_enabled", "max_per_user_per_stream"], ["is_global_cooldown_enabled", "global_cooldown_seconds"]]) {
120
120
  if (request.body[enabled] === true && request.body[amount] === undefined) {
121
- throw new ConnectorError("connector_input_invalid", `Supply ${amount} when enabling this reward limit.`, { statusCode: 422 });
121
+ throw new ConnectorError("connector_input_invalid", `Supply ${amount} when enabling this reward limit.`,
122
+ { statusCode: 422, messageCode: "connectors.twitch.rewardLimitAmountRequired", messageParams: { field: amount } });
122
123
  }
123
124
  }
124
125
  for (const name of queryNames) { url.searchParams.set(name, request.body[name]); delete request.body[name]; }
125
- if (method === "PATCH" && Object.keys(request.body).length === 0) throw new ConnectorError("connector_input_invalid", "Supply at least one reward change.", { statusCode: 422 });
126
+ if (method === "PATCH" && Object.keys(request.body).length === 0) throw new ConnectorError("connector_input_invalid", "Supply at least one reward change.", { statusCode: 422, messageCode: "connectors.twitch.supplyLeastRewardChange" });
126
127
  return { ...request, url: url.href, body: method === "DELETE" ? undefined : request.body };
127
128
  } };
128
129
  }
129
130
 
130
131
  async function normalizeTokenResponse(response) {
131
132
  const failure = {
132
- 401: ["connector_reconnect_required", "Connect this Twitch account again."],
133
- 403: ["connector_permission_denied", "Twitch denied the token request."],
134
- 429: ["connector_rate_limited", "Twitch's request limit was reached."]
133
+ 401: ["connector_reconnect_required", "Connect this Twitch account again.", { messageCode: "connectors.shared.reconnectAccount", messageParams: { provider: "Twitch" } }],
134
+ 403: ["connector_permission_denied", "Twitch denied the token request.", { messageCode: "connectors.twitch.tokenRequestDenied" }],
135
+ 429: ["connector_rate_limited", "Twitch's request limit was reached.", { messageCode: "connectors.shared.requestLimitReached", messageParams: { provider: "Twitch" } }]
135
136
  }[response.status];
136
- if (failure) throw new ConnectorError(...failure, { statusCode: response.status });
137
+ if (failure) throw new ConnectorError(failure[0], failure[1], { statusCode: response.status, ...failure[2] });
137
138
  let value;
138
139
  try { value = await response.clone().json(); } catch {
139
140
  if (!response.ok) return response;
140
- throw new ConnectorError("connector_response_invalid", "Twitch returned an invalid token response.", { statusCode: 502 });
141
+ throw new ConnectorError("connector_response_invalid", "Twitch returned an invalid token response.", { statusCode: 502, messageCode: "connectors.shared.invalidTokenResponse", messageParams: { provider: "Twitch" } });
141
142
  }
142
143
  if (!response.ok) {
143
144
  if (response.status === 400 && value?.message === "Invalid refresh token") {
144
- throw new ConnectorError("connector_reconnect_required", "Connect this Twitch account again.", { statusCode: 401 });
145
+ throw new ConnectorError("connector_reconnect_required", "Connect this Twitch account again.", { statusCode: 401, messageCode: "connectors.shared.reconnectAccount", messageParams: { provider: "Twitch" } });
145
146
  }
146
147
  return response;
147
148
  }
148
149
  if (typeof value?.scope === "string" && (value.scope === "" || /^[^\s]+(?: [^\s]+)*$/u.test(value.scope))) return response;
149
150
  if (!scopeList(value?.scope)) {
150
- throw new ConnectorError("connector_response_invalid", "Twitch returned an invalid permission grant.", { statusCode: 502 });
151
+ throw new ConnectorError("connector_response_invalid", "Twitch returned an invalid permission grant.", { statusCode: 502, messageCode: "connectors.shared.invalidPermissionGrant", messageParams: { provider: "Twitch" } });
151
152
  }
152
153
  return Response.json({ ...value, scope: value.scope.join(" ") }, { status: response.status });
153
154
  }
@@ -200,24 +201,24 @@ const twitchProvider = Object.freeze({
200
201
  const operation = `${options.method} ${url.pathname}`;
201
202
  if (!(url.href === validationUrl && options.method === "GET") &&
202
203
  (url.origin !== "https://api.twitch.tv" || !Object.hasOwn(permissions, operation))) {
203
- throw new ConnectorError("connector_destination_invalid", "This Twitch operation has an invalid destination.");
204
+ throw new ConnectorError("connector_destination_invalid", "This Twitch operation has an invalid destination.", { messageCode: "connectors.twitch.operationInvalidDestination" });
204
205
  }
205
206
  const validation = await request(validationUrl, { ...options, method: "GET", body: undefined });
206
207
  if (!validToken(validation)) {
207
- throw new ConnectorError("connector_response_invalid", "Twitch returned an invalid user-token validation.", { statusCode: 502 });
208
+ throw new ConnectorError("connector_response_invalid", "Twitch returned an invalid user-token validation.", { statusCode: 502, messageCode: "connectors.twitch.returnedInvalidUserTokenValidation" });
208
209
  }
209
210
  if (validation.client_id !== new Headers(options.headers).get("Client-Id")) {
210
- throw new ConnectorError("connector_reconnect_required", "The Twitch token belongs to a different application.", { statusCode: 401 });
211
+ throw new ConnectorError("connector_reconnect_required", "The Twitch token belongs to a different application.", { statusCode: 401, messageCode: "connectors.twitch.tokenBelongsDifferentApplication" });
211
212
  }
212
213
  if (url.href === validationUrl) return validation;
213
214
  const requiredScope = permissions[operation];
214
215
  if (requiredScope && ![requiredScope].flat().some(scope => validation.scopes.includes(scope))) {
215
- throw new ConnectorError("connector_reconnect_required", "Connect Twitch again with the permission needed for this operation.", { statusCode: 401 });
216
+ throw new ConnectorError("connector_reconnect_required", "Connect Twitch again with the permission needed for this operation.", { statusCode: 401, messageCode: "connectors.twitch.connectAgainPermissionNeededOperation" });
216
217
  }
217
218
  if (operation === "POST /helix/eventsub/subscriptions") {
218
219
  const [version, scopes] = eventTypes[options.body.type];
219
220
  if (scopes.length && !scopes.some(scope => validation.scopes.includes(scope))) {
220
- throw new ConnectorError("connector_reconnect_required", "Connect Twitch again with permission for this event.", { statusCode: 401 });
221
+ throw new ConnectorError("connector_reconnect_required", "Connect Twitch again with permission for this event.", { statusCode: 401, messageCode: "connectors.twitch.connectAgainPermissionEvent" });
221
222
  }
222
223
  options = { ...options, body: { type: options.body.type, version,
223
224
  condition: { broadcaster_user_id: validation.user_id },
@@ -238,7 +239,7 @@ const twitchProvider = Object.freeze({
238
239
  if (url.pathname === "/helix/channels/followers" && !url.searchParams.has("broadcaster_id")) url.searchParams.set("broadcaster_id", validation.user_id);
239
240
  const result = await request(url.href, options);
240
241
  if (url.pathname === "/helix/users" && result?.data?.[0]?.id !== validation.user_id) {
241
- throw new ConnectorError("connector_response_invalid", "Twitch returned a profile for a different user.", { statusCode: 502 });
242
+ throw new ConnectorError("connector_response_invalid", "Twitch returned a profile for a different user.", { statusCode: 502, messageCode: "connectors.twitch.returnedProfileDifferentUser" });
242
243
  }
243
244
  return result;
244
245
  },
@@ -258,7 +259,7 @@ const twitchProvider = Object.freeze({
258
259
  const query = new URL(request.url).searchParams;
259
260
  if (query.get("is_vacation_enabled") === "true" && (!query.has("timezone") || !query.has("vacation_start_time") || !query.has("vacation_end_time") ||
260
261
  Date.parse(query.get("vacation_end_time")) <= Date.parse(query.get("vacation_start_time")))) {
261
- throw new ConnectorError("connector_input_invalid", "Enabling vacation requires a timezone and an end after the start.", { statusCode: 422 });
262
+ throw new ConnectorError("connector_input_invalid", "Enabling vacation requires a timezone and an end after the start.", { statusCode: 422, messageCode: "connectors.twitch.enablingVacationRequiresTimezoneEnd" });
262
263
  }
263
264
  return { ...request, method: "PATCH" };
264
265
  } },
@@ -325,7 +326,7 @@ const twitchProvider = Object.freeze({
325
326
  "predictions.end": helixOperation("predictions", { id: opaqueId,
326
327
  status: { type: "string", required: true, enum: ["LOCKED", "CANCELED", "RESOLVED"] }, winning_outcome_id: { ...opaqueId, required: false }
327
328
  }, ["channel:manage:predictions"], interactionResult, "PATCH", values => {
328
- if ((values.status === "RESOLVED") !== Boolean(values.winning_outcome_id)) throw new ConnectorError("connector_input_invalid", "Supply a winning outcome only when resolving the prediction.", { statusCode: 422 });
329
+ if ((values.status === "RESOLVED") !== Boolean(values.winning_outcome_id)) throw new ConnectorError("connector_input_invalid", "Supply a winning outcome only when resolving the prediction.", { statusCode: 422, messageCode: "connectors.twitch.supplyWinningOutcomeResolvingPrediction" });
329
330
  }),
330
331
  "chat.send": helixOperation("chat/messages", { broadcaster_id: { ...idField, required: true },
331
332
  message: { type: "string", required: true, noTrim: true, minLength: 1, maxLength: 500 }, reply_parent_message_id: { ...opaqueId, required: false }
@@ -346,7 +347,7 @@ const twitchProvider = Object.freeze({
346
347
  scopes: ["channel:manage:broadcast"], validateResult: broadcastUpdate.validateResult,
347
348
  request(input) {
348
349
  const request = broadcastUpdate.request(input);
349
- if (!Object.keys(request.body).length) throw new ConnectorError("connector_input_invalid", "Supply at least one broadcast change.", { statusCode: 422 });
350
+ if (!Object.keys(request.body).length) throw new ConnectorError("connector_input_invalid", "Supply at least one broadcast change.", { statusCode: 422, messageCode: "connectors.twitch.supplyLeastBroadcastChange" });
350
351
  return { ...request, method: "PATCH" };
351
352
  }
352
353
  },
@@ -371,7 +372,7 @@ const twitchProvider = Object.freeze({
371
372
  });
372
373
 
373
374
  function parseTwitchEventSubMessage(raw, { broadcasterId, sessionId, subscriptionIds = [] } = {}) {
374
- const invalid = () => new ConnectorError("connector_event_invalid", "Twitch returned an invalid or unexpected EventSub message.", { statusCode: 422 });
375
+ const invalid = () => new ConnectorError("connector_event_invalid", "Twitch returned an invalid or unexpected EventSub message.", { statusCode: 422, messageCode: "connectors.twitch.returnedInvalidUnexpectedEventsubMessage" });
375
376
  if (typeof raw !== "string" || Buffer.byteLength(raw, "utf8") > 1048576 || !userId(broadcasterId)) throw invalid();
376
377
  let message;
377
378
  try { message = JSON.parse(raw); } catch { throw invalid(); }
@@ -164,13 +164,13 @@ const waveProvider = Object.freeze({
164
164
  apiOrigins: ["https://gql.waveapps.com"], checkOperation: "user.read",
165
165
  async normalizeTokenResponse(response) {
166
166
  if (response.status === 403) {
167
- throw new ConnectorError("connector_permission_denied", "Wave denied token access. Check the business subscription and permissions.", { statusCode: 403 });
167
+ throw new ConnectorError("connector_permission_denied", "Wave denied token access. Check the business subscription and permissions.", { statusCode: 403, messageCode: "connectors.wave.deniedTokenAccessCheckBusiness" });
168
168
  }
169
169
  if (response.ok) {
170
170
  let value;
171
171
  try { value = await response.clone().json(); } catch { /* reported below */ }
172
172
  if (typeof value?.scope !== "string" || !/^[^\s]+(?: [^\s]+)*$/u.test(value.scope)) {
173
- throw new ConnectorError("connector_response_invalid", "Wave returned an invalid permission grant.", { statusCode: 502 });
173
+ throw new ConnectorError("connector_response_invalid", "Wave returned an invalid permission grant.", { statusCode: 502, messageCode: "connectors.shared.invalidPermissionGrant", messageParams: { provider: "Wave" } });
174
174
  }
175
175
  }
176
176
  return response;
@@ -311,9 +311,9 @@ const waveProvider = Object.freeze({
311
311
  const result = await transport(url, init);
312
312
  if (Array.isArray(result?.errors) && result.errors.length) {
313
313
  const codes = result.errors.map((error) => error?.extensions?.code);
314
- if (codes.includes("UNAUTHENTICATED")) throw new ConnectorError("connector_reconnect_required", "Connect this Wave account again.", { statusCode: 401 });
315
- if (codes.includes("NOT_FOUND")) throw new ConnectorError("connector_resource_not_found", "This Wave resource is not available.", { statusCode: 404 });
316
- throw new ConnectorError("connector_provider_failed", "Wave could not complete this query.", { statusCode: 502 });
314
+ if (codes.includes("UNAUTHENTICATED")) throw new ConnectorError("connector_reconnect_required", "Connect this Wave account again.", { statusCode: 401, messageCode: "connectors.shared.reconnectAccount", messageParams: { provider: "Wave" } });
315
+ if (codes.includes("NOT_FOUND")) throw new ConnectorError("connector_resource_not_found", "This Wave resource is not available.", { statusCode: 404, messageCode: "connectors.shared.resourceUnavailable", messageParams: { provider: "Wave" } });
316
+ throw new ConnectorError("connector_provider_failed", "Wave could not complete this query.", { statusCode: 502, messageCode: "connectors.wave.couldNotCompleteQuery" });
317
317
  }
318
318
  return result;
319
319
  };
@@ -333,7 +333,7 @@ const waveProvider = Object.freeze({
333
333
  if (line.customerId) related.set(`customer:${line.customerId}`, { field: "customer", id: line.customerId });
334
334
  for (const tax of line.taxes || []) related.set(`salesTax:${tax.salesTaxId}`, { field: "salesTax", id: tax.salesTaxId });
335
335
  }
336
- if (related.size > 100) throw new ConnectorError("connector_input_invalid", "Use at most 100 distinct related records per transaction.", { statusCode: 422 });
336
+ if (related.size > 100) throw new ConnectorError("connector_input_invalid", "Use at most 100 distinct related records per transaction.", { statusCode: 422, messageCode: "connectors.wave.useMost100DistinctRelated" });
337
337
  for (const target of related.values()) {
338
338
  const owner = await request(address, { ...options, body: {
339
339
  query: `query ConnectorWaveTransactionOwner($businessId: ID!, $id: ID!) { business(id: $businessId) { id record: ${target.field}(id: $id) { id business { id } } } }`,
@@ -341,7 +341,7 @@ const waveProvider = Object.freeze({
341
341
  } });
342
342
  if (owner?.data?.business?.id !== variables.businessId || owner.data.business.record?.id !== target.id ||
343
343
  owner.data.business.record.business?.id !== variables.businessId) {
344
- throw new ConnectorError("connector_resource_not_found", "A transaction account, customer or tax is not available in the selected business. Check read access.", { statusCode: 404 });
344
+ throw new ConnectorError("connector_resource_not_found", "A transaction account, customer or tax is not available in the selected business. Check read access.", { statusCode: 404, messageCode: "connectors.wave.transactionAccountCustomerTaxNot" });
345
345
  }
346
346
  }
347
347
  options = { ...options, body: { ...options.body, variables: { input: { ...values, businessId: variables.businessId } } } };
@@ -352,17 +352,17 @@ const waveProvider = Object.freeze({
352
352
  const values = variables.input;
353
353
  if (accountingMatch[2] !== "Create") {
354
354
  const changedFields = Object.keys(values).filter(key => key !== "id" && key !== "sequence");
355
- if (accountingMatch[2] === "Patch" && !changedFields.length) throw new ConnectorError("connector_input_invalid", "Choose accounting fields to update.", { statusCode: 422 });
355
+ if (accountingMatch[2] === "Patch" && !changedFields.length) throw new ConnectorError("connector_input_invalid", "Choose accounting fields to update.", { statusCode: 422, messageCode: "connectors.wave.chooseAccountingFieldsUpdate" });
356
356
  const existing = await request(address, { ...options, body: {
357
357
  query: `query ConnectorWaveAccountingOwner($businessId: ID!, $id: ID!) { business(id: $businessId) { id record: ${accountingResource}(id: $id) { id business { id } ${accountingResource === "account" ? "sequence" : ""} } } }`,
358
358
  variables: { businessId: variables.businessId, id: values.id }
359
359
  } });
360
360
  if (existing?.data?.business?.id !== variables.businessId || existing.data.business.record?.id !== values.id ||
361
361
  existing.data.business.record.business?.id !== variables.businessId) {
362
- throw new ConnectorError("connector_resource_not_found", "This accounting record is not available in the selected business. Check read access.", { statusCode: 404 });
362
+ throw new ConnectorError("connector_resource_not_found", "This accounting record is not available in the selected business. Check read access.", { statusCode: 404, messageCode: "connectors.wave.accountingRecordNotAvailableSelected" });
363
363
  }
364
364
  if (accountingMatch[2] === "Patch" && accountingResource === "account" && existing.data.business.record.sequence !== values.sequence) {
365
- throw new ConnectorError("connector_conflict", "The account changed. Reload it before applying your edit.", { statusCode: 409 });
365
+ throw new ConnectorError("connector_conflict", "The account changed. Reload it before applying your edit.", { statusCode: 409, messageCode: "connectors.wave.accountChangedReloadApplyingEdit" });
366
366
  }
367
367
  }
368
368
  options = { ...options, body: { ...options.body, variables: { input: accountingMatch[2] === "Create" ? { ...values, businessId: variables.businessId } : values } } };
@@ -371,7 +371,7 @@ const waveProvider = Object.freeze({
371
371
  if (productAction) {
372
372
  const values = variables.input;
373
373
  if (productAction === "Patch" && Object.keys(values).length < 2) {
374
- throw new ConnectorError("connector_input_invalid", "Choose at least one product field to update.", { statusCode: 422 });
374
+ throw new ConnectorError("connector_input_invalid", "Choose at least one product field to update.", { statusCode: 422, messageCode: "connectors.wave.chooseLeastProductFieldUpdate" });
375
375
  }
376
376
  const related = [];
377
377
  if (productAction === "Patch") related.push({ field: "product", id: values.id });
@@ -386,7 +386,7 @@ const waveProvider = Object.freeze({
386
386
  } });
387
387
  if (owner?.data?.business?.id !== variables.businessId ||
388
388
  owner.data.business.record?.id !== target.id || owner.data.business.record.business?.id !== variables.businessId) {
389
- throw new ConnectorError("connector_resource_not_found", "This product, account or tax is not available in the selected business. Check read access.", { statusCode: 404 });
389
+ throw new ConnectorError("connector_resource_not_found", "This product, account or tax is not available in the selected business. Check read access.", { statusCode: 404, messageCode: "connectors.wave.productAccountTaxNotAvailable" });
390
390
  }
391
391
  }
392
392
  options = { ...options, body: { ...options.body, variables: { input: productAction === "Create" ? { ...values, businessId: variables.businessId } : values } } };
@@ -394,7 +394,7 @@ const waveProvider = Object.freeze({
394
394
  if (documentAction) {
395
395
  const values = variables.input;
396
396
  if (documentAction === "Patch" && Object.keys(values).length < 2) {
397
- throw new ConnectorError("connector_input_invalid", "Choose at least one document field to update.", { statusCode: 422 });
397
+ throw new ConnectorError("connector_input_invalid", "Choose at least one document field to update.", { statusCode: 422, messageCode: "connectors.wave.chooseLeastDocumentFieldUpdate" });
398
398
  }
399
399
  const targetId = values.id || values[`${resource}Id`];
400
400
  if (targetId) {
@@ -404,13 +404,13 @@ const waveProvider = Object.freeze({
404
404
  } });
405
405
  if (existing?.data?.business?.id !== variables.businessId ||
406
406
  existing.data.business[resource]?.id !== targetId || (resource === "invoice" ? existing.data.business[resource].business?.id : existing.data.business[resource].customer?.business?.id) !== variables.businessId) {
407
- throw new ConnectorError("connector_resource_not_found", "This document is not available in the selected business. Check read access.", { statusCode: 404 });
407
+ throw new ConnectorError("connector_resource_not_found", "This document is not available in the selected business. Check read access.", { statusCode: 404, messageCode: "connectors.wave.documentNotAvailableSelectedBusiness" });
408
408
  }
409
409
  const status = existing.data.business[resource].status;
410
410
  if (documentAction === "Send" && ((resource === "invoice" && existing.data.business.emailSendEnabled !== true) ||
411
411
  typeof status !== "string" || !status || status === "DRAFT" ||
412
412
  (resource === "estimate" && !["APPROVED", "SENT", "VIEWED", "ACCEPTED"].includes(status)))) {
413
- throw new ConnectorError("connector_input_invalid", "Approve the document and check Wave email eligibility before sending.", { statusCode: 422 });
413
+ throw new ConnectorError("connector_input_invalid", "Approve the document and check Wave email eligibility before sending.", { statusCode: 422, messageCode: "connectors.wave.approveDocumentCheckEmailEligibility" });
414
414
  }
415
415
  }
416
416
  if (values.customerId) {
@@ -420,14 +420,14 @@ const waveProvider = Object.freeze({
420
420
  } });
421
421
  if (owner?.data?.business?.id !== variables.businessId || owner.data.business.customer?.id !== values.customerId ||
422
422
  owner.data.business.customer.business?.id !== variables.businessId) {
423
- throw new ConnectorError("connector_resource_not_found", "This customer is not available in the selected business. Check read access.", { statusCode: 404 });
423
+ throw new ConnectorError("connector_resource_not_found", "This customer is not available in the selected business. Check read access.", { statusCode: 404, messageCode: "connectors.wave.customerNotAvailableCheckAccess" });
424
424
  }
425
425
  }
426
426
  options = { ...options, body: { ...options.body, variables: { input: documentAction === "Create" ? { ...values, businessId: variables.businessId } : values } } };
427
427
  }
428
428
  if (patchCustomer) {
429
429
  if (Object.keys(variables.input).length < 2) {
430
- throw new ConnectorError("connector_input_invalid", "Choose at least one customer field to update.", { statusCode: 422 });
430
+ throw new ConnectorError("connector_input_invalid", "Choose at least one customer field to update.", { statusCode: 422, messageCode: "connectors.wave.chooseLeastCustomerFieldUpdate" });
431
431
  }
432
432
  const existing = await request(address, { ...options, body: {
433
433
  query: "query ConnectorWaveCustomerOwner($businessId: ID!, $id: ID!) { business(id: $businessId) { id customer(id: $id) { id business { id } } } }",
@@ -435,7 +435,7 @@ const waveProvider = Object.freeze({
435
435
  } });
436
436
  if (existing?.data?.business?.id !== variables.businessId || existing.data.business.customer?.id !== variables.input.id ||
437
437
  existing.data.business.customer.business?.id !== variables.businessId) {
438
- throw new ConnectorError("connector_resource_not_found", "This customer is not available in the selected business.", { statusCode: 404 });
438
+ throw new ConnectorError("connector_resource_not_found", "This customer is not available in the selected business.", { statusCode: 404, messageCode: "connectors.wave.customerNotAvailable" });
439
439
  }
440
440
  // Business selection is a local authorization input; CustomerPatchInput only takes its customer ID.
441
441
  options = { ...options, body: { ...options.body, variables: { input: variables.input } } };
@@ -443,76 +443,76 @@ const waveProvider = Object.freeze({
443
443
  const result = await request(address, options);
444
444
  if (transactionCreate) {
445
445
  if (result?.data?.moneyTransactionCreate?.didSucceed === false) {
446
- throw new ConnectorError("connector_input_invalid", "Wave rejected the transaction. Review its balance, accounts and details before submitting again.", { statusCode: 422 });
446
+ throw new ConnectorError("connector_input_invalid", "Wave rejected the transaction. Review its balance, accounts and details before submitting again.", { statusCode: 422, messageCode: "connectors.wave.rejectedTransactionReviewBalanceAccounts" });
447
447
  }
448
448
  return result;
449
449
  }
450
450
  if (accountingMatch) {
451
451
  const outcome = result?.data?.[`${accountingResource}${accountingMatch[2]}`];
452
- if (outcome?.didSucceed === false) throw new ConnectorError("connector_input_invalid", "Wave rejected the accounting change. Check the values and current record.", { statusCode: 422 });
452
+ if (outcome?.didSucceed === false) throw new ConnectorError("connector_input_invalid", "Wave rejected the accounting change. Check the values and current record.", { statusCode: 422, messageCode: "connectors.wave.rejectedAccountingChangeCheckValues" });
453
453
  if (outcome?.didSucceed === true && accountingMatch[2] !== "Archive" && (outcome[accountingResource]?.business?.id !== variables.businessId ||
454
454
  (variables.input.id && outcome[accountingResource]?.id !== variables.input.id))) {
455
- throw new ConnectorError("connector_response_invalid", "Wave returned an unexpected accounting record. Check the outcome before retrying.", { statusCode: 502 });
455
+ throw new ConnectorError("connector_response_invalid", "Wave returned an unexpected accounting record. Check the outcome before retrying.", { statusCode: 502, messageCode: "connectors.wave.returnedUnexpectedAccountingRecordCheck" });
456
456
  }
457
457
  return result;
458
458
  }
459
459
  if (productAction) {
460
460
  const value = result?.data?.[`product${productAction}`];
461
461
  if (value?.didSucceed === false) {
462
- throw new ConnectorError("connector_input_invalid", "Wave rejected the product details. Check accounts, taxes and price.", { statusCode: 422 });
462
+ throw new ConnectorError("connector_input_invalid", "Wave rejected the product details. Check accounts, taxes and price.", { statusCode: 422, messageCode: "connectors.wave.rejectedProductDetailsCheckAccounts" });
463
463
  }
464
464
  if (value?.didSucceed === true && (value.product?.business?.id !== variables.businessId ||
465
465
  (productAction === "Patch" && value.product?.id !== variables.input.id))) {
466
- throw new ConnectorError("connector_response_invalid", "Wave returned an unexpected product. Check the outcome before retrying.", { statusCode: 502 });
466
+ throw new ConnectorError("connector_response_invalid", "Wave returned an unexpected product. Check the outcome before retrying.", { statusCode: 502, messageCode: "connectors.wave.returnedUnexpectedProductCheckOutcome" });
467
467
  }
468
468
  return result;
469
469
  }
470
470
  if (documentAction) {
471
471
  const outcome = result?.data?.[`${resource}${documentAction}`];
472
472
  if (outcome?.didSucceed === false) {
473
- throw new ConnectorError("connector_input_invalid", "Wave rejected the document operation. Check its details and current state.", { statusCode: 422 });
473
+ throw new ConnectorError("connector_input_invalid", "Wave rejected the document operation. Check its details and current state.", { statusCode: 422, messageCode: "connectors.wave.rejectedDocumentOperationCheckDetails" });
474
474
  }
475
475
  if (outcome?.didSucceed === true && documentAction !== "Send" &&
476
476
  ((resource === "invoice" ? outcome[resource]?.business?.id : outcome[resource]?.customer?.business?.id) !== variables.businessId ||
477
477
  ((variables.input.id || variables.input[`${resource}Id`]) && outcome[resource]?.id !== (variables.input.id || variables.input[`${resource}Id`])) ||
478
478
  (variables.input.customerId && outcome[resource]?.customer?.id !== variables.input.customerId))) {
479
- throw new ConnectorError("connector_response_invalid", "Wave returned an unexpected document. Check the outcome before retrying.", { statusCode: 502 });
479
+ throw new ConnectorError("connector_response_invalid", "Wave returned an unexpected document. Check the outcome before retrying.", { statusCode: 502, messageCode: "connectors.wave.returnedUnexpectedDocumentCheckOutcome" });
480
480
  }
481
481
  return result;
482
482
  }
483
483
  const mutation = patchCustomer ? result?.data?.customerPatch : result?.data?.customerCreate;
484
484
  if (mutation?.didSucceed === false) {
485
- throw new ConnectorError("connector_input_invalid", "Wave rejected the customer details. Check the selected business and customer fields.", { statusCode: 422 });
485
+ throw new ConnectorError("connector_input_invalid", "Wave rejected the customer details. Check the selected business and customer fields.", { statusCode: 422, messageCode: "connectors.wave.rejectedCustomerDetailsCheckSelected" });
486
486
  }
487
487
  if (mutation?.didSucceed === true && mutation.customer?.business?.id !== (patchCustomer ? variables.businessId : variables.input?.businessId)) {
488
- throw new ConnectorError("connector_response_invalid", "Wave returned a customer from an unexpected business. Check the outcome before retrying.", { statusCode: 502 });
488
+ throw new ConnectorError("connector_response_invalid", "Wave returned a customer from an unexpected business. Check the outcome before retrying.", { statusCode: 502, messageCode: "connectors.wave.returnedCustomerUnexpectedBusinessCheck" });
489
489
  }
490
490
  if (!patchCustomer && variables.businessId && result?.data?.business?.id !== variables.businessId) {
491
- throw new ConnectorError("connector_response_invalid", "Wave returned an unexpected business.", { statusCode: 502 });
491
+ throw new ConnectorError("connector_response_invalid", "Wave returned an unexpected business.", { statusCode: 502, messageCode: "connectors.wave.returnedUnexpectedBusiness" });
492
492
  }
493
493
  if (patchCustomer && mutation?.customer?.id !== variables.input.id) {
494
- throw new ConnectorError("connector_response_invalid", "Wave returned an unexpected customer. Check the outcome before retrying.", { statusCode: 502 });
494
+ throw new ConnectorError("connector_response_invalid", "Wave returned an unexpected customer. Check the outcome before retrying.", { statusCode: 502, messageCode: "connectors.wave.returnedUnexpectedCustomerCheckOutcome" });
495
495
  }
496
496
  if (variables.invoiceId && (result?.data?.business?.invoice?.id !== variables.invoiceId ||
497
497
  result?.data?.business?.invoice?.business?.id !== variables.businessId)) {
498
- throw new ConnectorError("connector_response_invalid", "Wave returned an unexpected invoice.", { statusCode: 502 });
498
+ throw new ConnectorError("connector_response_invalid", "Wave returned an unexpected invoice.", { statusCode: 502, messageCode: "connectors.wave.returnedUnexpectedInvoice" });
499
499
  }
500
500
  if (variables.estimateId && (result?.data?.business?.estimate?.id !== variables.estimateId ||
501
501
  result?.data?.business?.estimate?.customer?.business?.id !== variables.businessId)) {
502
- throw new ConnectorError("connector_response_invalid", "Wave returned an unexpected estimate.", { statusCode: 502 });
502
+ throw new ConnectorError("connector_response_invalid", "Wave returned an unexpected estimate.", { statusCode: 502, messageCode: "connectors.wave.returnedUnexpectedEstimate" });
503
503
  }
504
504
  if (result?.data?.business?.estimates?.edges?.some(edge => edge?.node?.customer?.business?.id !== variables.businessId)) {
505
- throw new ConnectorError("connector_response_invalid", "Wave returned an estimate from another business.", { statusCode: 502 });
505
+ throw new ConnectorError("connector_response_invalid", "Wave returned an estimate from another business.", { statusCode: 502, messageCode: "connectors.wave.returnedEstimateAnotherBusiness" });
506
506
  }
507
507
  for (const field of ["account", "salesTax"]) {
508
508
  if (variables[`${field}Id`] && (result?.data?.business?.[field]?.id !== variables[`${field}Id`] ||
509
509
  result?.data?.business?.[field]?.business?.id !== variables.businessId)) {
510
- throw new ConnectorError("connector_response_invalid", "Wave returned an unexpected accounting record.", { statusCode: 502 });
510
+ throw new ConnectorError("connector_response_invalid", "Wave returned an unexpected accounting record.", { statusCode: 502, messageCode: "connectors.wave.returnedUnexpectedAccountingRecord" });
511
511
  }
512
512
  }
513
513
  const page = result?.data?.businesses || Object.values(result?.data?.business || {}).find(value => value && typeof value === "object" && Object.hasOwn(value, "pageInfo"));
514
514
  if (page && (page.pageInfo?.currentPage !== variables.page || page.edges?.length > variables.pageSize)) {
515
- throw new ConnectorError("connector_response_invalid", "Wave returned an unexpected page.", { statusCode: 502 });
515
+ throw new ConnectorError("connector_response_invalid", "Wave returned an unexpected page.", { statusCode: 502, messageCode: "connectors.shared.unexpectedPage", messageParams: { provider: "Wave" } });
516
516
  }
517
517
  return result;
518
518
  }