From e0a74ed386b61fc56ab3ada62da7f4aaf0131262 Mon Sep 17 00:00:00 2001 From: Houssem Eddine Zerrad Date: Fri, 10 Mar 2023 22:45:45 +0100 Subject: [PATCH 01/30] Initialize rutter source --- catalog/sources/rutter/CHANGELOG.md | 12 ++ catalog/sources/rutter/config.json | 163 +++++++++++++++ catalog/sources/rutter/docs/connect.md | 10 + catalog/sources/rutter/docs/setup.md | 3 + catalog/sources/rutter/lib/connection.ts | 251 +++++++++++++++++++++++ catalog/sources/rutter/lib/types.ts | 15 ++ catalog/sources/rutter/rutter.ts | 97 +++++++++ package-lock.json | 11 + package.json | 1 + 9 files changed, 563 insertions(+) create mode 100644 catalog/sources/rutter/CHANGELOG.md create mode 100644 catalog/sources/rutter/config.json create mode 100644 catalog/sources/rutter/docs/connect.md create mode 100644 catalog/sources/rutter/docs/setup.md create mode 100644 catalog/sources/rutter/lib/connection.ts create mode 100644 catalog/sources/rutter/lib/types.ts create mode 100644 catalog/sources/rutter/rutter.ts diff --git a/catalog/sources/rutter/CHANGELOG.md b/catalog/sources/rutter/CHANGELOG.md new file mode 100644 index 00000000..942879b1 --- /dev/null +++ b/catalog/sources/rutter/CHANGELOG.md @@ -0,0 +1,12 @@ +# 📣 Change Log +All notable changes to the `Rutter` Connection will be documented in this file. + +The format followed is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). + +--- + +## [1.0.0] - 2023-03-09 + +⚡️ Initial Version + +--- diff --git a/catalog/sources/rutter/config.json b/catalog/sources/rutter/config.json new file mode 100644 index 00000000..90912738 --- /dev/null +++ b/catalog/sources/rutter/config.json @@ -0,0 +1,163 @@ +{ + "title": "Rutter", + "description": "A universal API for Commerce, Accounting, Payments & More. For devs, by devs. Simple auth flow", + "apiVersion": "NA", + "type": "rutter", + "category": "accounting", + "image": "https://assets.buildable.dev/catalog/node-templates/rutter.svg", + "tags": [ + "ecommerce", + "accounting", + "payments" + ], + "authentication": [ + { + "name": "GITLAB_ACCESS_TOKEN", + "label": "Enter your GitLab Personal API Key", + "placeholder": "glpat-MDsSCit_wit61jf2bJgv" + }, + { + "name": "GITLAB_PROJECT_ID", + "label": "Enter your GitLab Project ID", + "placeholder": "39811640" + }, + { + "name": "GITLAB_WEBHOOK_SECRET", + "label": "Enter your GitLab Webhook Secret", + "placeholder": "some-secret-value" + } + ], + "eventSchema": { + "object_kind": "push", + "event_name": "push", + "before": "04b313cae1b737f001e9706988f8abd2aeee4663", + "after": "04b313cae1b737f001e9706988f8abd2aeee4663", + "ref": "refs/heads/main", + "checkout_sha": "04b313cae1b737f001e9706988f8abd2aeee4663", + "message": null, + "user_id": 12258219, + "user_name": "Michael Schofield", + "user_username": "mike.onbike", + "user_email": null, + "user_avatar": "https://secure.gravatar.com/avatar/7fc156ca309234333a82c25b63574fb8?s=80&d=identicon", + "project_id": 38449140, + "project": { + "id": 38449140, + "name": "myproj", + "description": null, + "web_url": "https://gitlab.com/schoff1/myproj", + "avatar_url": null, + "git_ssh_url": "git@gitlab.com:schoff1/myproj.git", + "git_http_url": "https://gitlab.com/schoff1/myproj.git", + "namespace": "schoff", + "visibility_level": 0, + "path_with_namespace": "schoff1/myproj", + "default_branch": "main", + "ci_config_path": "", + "homepage": "https://gitlab.com/schoff1/myproj", + "url": "git@gitlab.com:schoff1/myproj.git", + "ssh_url": "git@gitlab.com:schoff1/myproj.git", + "http_url": "https://gitlab.com/schoff1/myproj.git" + }, + "commits": [ + { + "id": "04b313cae1b737f001e9706988f8abd2aeee4663", + "message": "Initial commit", + "title": "Initial commit", + "timestamp": "2022-08-09T10:25:08+00:00", + "url": "https://gitlab.com/schoff1/myproj/-/commit/04b313cae1b737f001e9706988f8abd2aeee4663", + "author": { "name": "Michael Schofield", "email": "mike.schoff@somemail.com" }, + "added": [ "README.md" ], + "modified": [], + "removed": [] + } + ], + "total_commits_count": 1, + "push_options": {}, + "repository": { + "name": "myproj", + "url": "git@gitlab.com:schoff1/myproj.git", + "description": null, + "homepage": "https://gitlab.com/schoff1/myproj", + "git_http_url": "https://gitlab.com/schoff1/myproj.git", + "git_ssh_url": "git@gitlab.com:schoff1/myproj.git", + "visibility_level": 0 + } + }, + "settings": { + "parseWebhookBody": false, + "hasSubscriptionDelay": false, + "subscriptionDelayMultiplier": 0.0, + "showEvents": true + }, + "paths": { + "id": "_.body.id", + "event": "_.body.event_name", + "payload": "_.body", + "secret": "_.headers.x-gitlab-token", + "signature": "_.headers.x-gitlab-token" + }, + "connectionTypes": ["source"], + "events": [ + { + "name": "confidential_issues_events", + "description": "A new confidential issue is created or an existing issue is updated, closed, or reopened.", + "group": "" + }, + { + "name": "confidential_note_events", + "description": "A confidential note/comment is made or an existing note is updated, closed, or reopened.", + "group": "" + }, + { + "name": "deployment_events", + "description": "A deployment starts, succeeds, fails, or is canceled.\n", + "group": "" + }, + { + "name": "issues_events", + "description": "A new issue is created or an existing issue is updated, closed, or reopened.", + "group": "" + }, + { + "name": "job_events", + "description": "A job status changes.", + "group": "" + }, + { + "name": "merge_requests_events", + "description": "A merge request is created, updated, merged, or closed, or a commit is added in the source branch.", + "group": "" + }, + { + "name": "note_events", + "description": "A note/comment is made or an existing note is updated, closed, or reopened.", + "group": "" + }, + { + "name": "pipeline_events", + "description": "A pipeline status changes.", + "group": "" + }, + { + "name": "push_events", + "description": "A push is made to the repository.", + "group": "" + }, + { + "name": "releases_events", + "description": "A release is created or updated", + "group": "" + }, + { + "name": "tag_push_events", + "description": "Tags are created or deleted in the repository.", + "group": "" + }, + { + "name": "wiki_page_events", + "description": "A wiki page is created, updated, or deleted.", + "group": "" + } + ] +} diff --git a/catalog/sources/rutter/docs/connect.md b/catalog/sources/rutter/docs/connect.md new file mode 100644 index 00000000..ee50ecf8 --- /dev/null +++ b/catalog/sources/rutter/docs/connect.md @@ -0,0 +1,10 @@ +## Connect + +A universal API for Commerce, Accounting, Payments & More. For devs, by devs. Simple auth flow +### Securely Encrypted + +Rest assured, your credentials are securely encrypted to keep your information safe. + +### Need Help? + +Start a conversation in our [Discord Server](https://discord.com/invite/47AJ42Wzys) or send an email to [support@buildable.dev](mailto:https://discord.com/invite/47AJ42Wzys). Our Data Engineers are available 24/7 to help if you ever get stuck. \ No newline at end of file diff --git a/catalog/sources/rutter/docs/setup.md b/catalog/sources/rutter/docs/setup.md new file mode 100644 index 00000000..2ef4b3dc --- /dev/null +++ b/catalog/sources/rutter/docs/setup.md @@ -0,0 +1,3 @@ +## Rutter Source Setup + +Check out our [quick start guide](https://docs.buildable.dev/) to learn how to get started. \ No newline at end of file diff --git a/catalog/sources/rutter/lib/connection.ts b/catalog/sources/rutter/lib/connection.ts new file mode 100644 index 00000000..dfe58341 --- /dev/null +++ b/catalog/sources/rutter/lib/connection.ts @@ -0,0 +1,251 @@ +import axios, { AxiosInstance } from "axios"; +import jwtDecode from "jwt-decode"; +import { RutterDecodedToken, RutterWebhook } from "./types"; +import { Events } from "../../../../types/sourceClassDefinition"; + +const MANDATORY_EVENTS = [ + "INITIAL_UPDATE", + "HISTORICAL_UPDATE", + "CONNECTION_UPDATED", + "CONNECTION_NEEDS_UPDATE", + "CONNECTION_DISABLED", + "CONNECTION_LINK_ERROR", + "CONNECTION_ERROR", +]; + +export default class RutterConnection { + static readonly BASE_URL = "https://production.rutterapi.com/graphql"; + + private readonly email: string; + + private readonly password: string; + + private ACCESS_TOKEN: string; + + private client: AxiosInstance; + + constructor(email: string, password: string) { + this.email = email; + this.password = password; + } + + /** + * Initialize the Rutter connection by getting an access token and initializing the client + */ + async init() { + this.ACCESS_TOKEN = await this.getAccessToken(); + await this.initializeClient(); + } + + /** + * Create a Rutter webhook with the passed events + * @param webhookUrl Event webhook URL + * @param events list of Webhook Types to subscribe to + */ + async createWebhook({ webhookUrl, events }: {webhookUrl: string, events: Events}): Promise { + await this.rotateAccessToken(); + + const allowedWebhookTypes = `[${events.map((event) => `"${event}"`).join(", ")}]`; + + // TODO: change isSandbox to false + const mutation = ` + mutation { + addWebhookConfigToOrg(input: { + url: "${webhookUrl}", + isSandbox: true, + allowedWebhookTypes: ${allowedWebhookTypes} + }) { + org { + id + webhookConfigs { + id + url + isSandbox + disabled + allowlist { + allowedTypes + } + } + } + } + } + `; + + await this.client.post("", { query: mutation }); + + return true; + } + + /** + * Find a Rutter webhook given its URL + * @param url Webhook URL + */ + async findWebhookByUrl(url: string): Promise { + const query = ` + query WebHooks { + me { + organization { + webhookConfigs { + id + url + isSandbox + disabled + allowlist { + allowedTypes + } + } + } + } + }`; + + const response = await this.client.post("", { query }); + const webhooks: Array = response.data.data.me.organization.webhookConfigs; + + for (const webhook of webhooks) { + if (webhook.url === url) { + return webhook; + } + } + + throw Error("Could not find webhook with the given URL"); + } + + /** + * Find a Rutter webhook given its ID + * @param webhookId Webhook ID + */ + async findWebhookById(webhookId: string) { + const query = ` + query WebHooks { + me { + organization { + webhookConfigs { + id + url + isSandbox + disabled + allowlist { + allowedTypes + } + } + } + } + }`; + + const response = await this.client.post("", { query }); + const webhooks: Array = response.data.data.me.organization.webhookConfigs; + + for (const webhook of webhooks) { + if (webhook.id === webhookId) { + return webhook; + } + } + + throw Error("Could not find webhook with the given ID"); + } + + /** + * Update the events for a Rutter webhook + * @param webhookId Rutter Webhook ID + * @param events list of Webhook Types to subscribe to + */ + async updateWebhookEvents(webhookId: string, events: Events): Promise { + await this.rotateAccessToken(); + + const allowedWebhookTypes = `[${events.map((event) => `"${event}"`).join(", ")}]`; + + const mutation = ` + mutation { + updateWebhookConfig(input: { + id: "${webhookId}", + allowedWebhookTypes: ${allowedWebhookTypes} + }) { + webhookConfig { + id + allowlist { + allowedTypes + } + } + } + } + `; + + const response = await this.client.post("", { query: mutation }); + + return response.data.data.updateWebhookConfig.webhookConfig as RutterWebhook; + } + + /** + * Drop a Rutter webhook + * @param webhookId + */ + async dropWebhook(webhookId: string): Promise { + const mutation = ` + mutation { + deleteWebhookUrl(input: {id: "${webhookId}"}){ + error { + code + message + } + } + }`; + + const response = await this.client.post("", { query: mutation }); + return response.data.data.deleteWebhookUrl.error === null; + } + + /** + * Get the subscribed events for a Rutter webhook + * @param webhookId + */ + async getSubscribedEvents(webhookId: string) { + const webhook = await this.findWebhookById(webhookId); + + return webhook.allowlist.allowedTypes.filter((event) => !MANDATORY_EVENTS.includes(event)); + } + + /** + * Rotate the access token if it has expired. To be used before every request. + * @private + */ + private async rotateAccessToken() { + const decodedToken = jwtDecode(this.ACCESS_TOKEN) as RutterDecodedToken; + + if (decodedToken.exp < Date.now() / 1000) { + await this.init(); + } + } + + /** + * Initialize the axios client + * @private + */ + private async initializeClient() { + this.client = axios.create({ + baseURL: RutterConnection.BASE_URL, + headers: { + Authorization: `Bearer ${this.ACCESS_TOKEN}`, + "Content-Type": "application/json", + "User-Agent": "event", + }, + }); + } + + /** + * Get the access token from Rutter's GraphQL API + * @private + */ + private async getAccessToken(): Promise { + const mutation = ` + mutation { + login(email: "${this.email}", password: "${this.password}") { + token + } + } + `; + + const response = await axios.post(RutterConnection.BASE_URL, { query: mutation }); + + return response.data.data.login.token; + } +} diff --git a/catalog/sources/rutter/lib/types.ts b/catalog/sources/rutter/lib/types.ts new file mode 100644 index 00000000..d722ab82 --- /dev/null +++ b/catalog/sources/rutter/lib/types.ts @@ -0,0 +1,15 @@ +export type RutterDecodedToken = { + userId: string; + iat: number; + exp: number; +} + +export type RutterWebhook = { + id: string; + url: string; + isSandbox: boolean; + disabled: boolean; + allowlist: { + allowedTypes: string[]; + } +} diff --git a/catalog/sources/rutter/rutter.ts b/catalog/sources/rutter/rutter.ts new file mode 100644 index 00000000..443a99cf --- /dev/null +++ b/catalog/sources/rutter/rutter.ts @@ -0,0 +1,97 @@ +import { + AnyObject, DeleteWebhookEndpointProps, + Events, + InitProps, + InitReturns, + IntegrationClassI, SubscribeReturns, SubscriptionProps, TestConnection, + Truthy, VerifyWebhookSignatureProps, WebhooksProps, +} from "../../../types/sourceClassDefinition"; +import RutterConnection from "./lib/connection"; + +export default class RutterIntegration implements IntegrationClassI { + id: string; + + name: string; + + private readonly RUTTER_EMAIL: string; + + private readonly RUTTER_PASSWORD: string; + + private RUTTER_CONNECTION: RutterConnection; + + constructor({ + RUTTER_EMAIL, + RUTTER_PASSWORD, + }: { + RUTTER_EMAIL: string; + RUTTER_PASSWORD: string; + }) { + this.RUTTER_EMAIL = RUTTER_EMAIL; + this.RUTTER_PASSWORD = RUTTER_PASSWORD; + + this.RUTTER_CONNECTION = new RutterConnection(RUTTER_EMAIL, RUTTER_PASSWORD); + } + + async init({ webhookUrl, events }: InitProps): Promise { + // initialize connection with Rutter + this.RUTTER_CONNECTION = new RutterConnection(this.RUTTER_EMAIL, this.RUTTER_PASSWORD); + await this.RUTTER_CONNECTION.init(); + + // issue create a webhook + await this.RUTTER_CONNECTION.createWebhook({ webhookUrl, events }); + + // issue list webhooks + const webhook = await this.RUTTER_CONNECTION.findWebhookByUrl(webhookUrl); + + // return webhook + return { + webhookData: webhook, + events, + }; + } + + async verifyWebhookSignature(props: VerifyWebhookSignatureProps): Promise { + return Promise.resolve(undefined); + } + + async subscribe({ webhookId, events }: SubscriptionProps): Promise { + const webhook = await this.RUTTER_CONNECTION.updateWebhookEvents(webhookId, events); + + return { + webhook, events, + }; + } + + async getWebhooks({ webhookId }: WebhooksProps | undefined): Promise { + return this.RUTTER_CONNECTION.findWebhookById(webhookId); + } + + async unsubscribe({ webhookId, events }: SubscriptionProps): Promise<{ events: Events; webhook?: any; webhooks?: any }> { + const webhook = await this.RUTTER_CONNECTION.findWebhookById(webhookId); + const newEvents = webhook.allowlist.allowedTypes.filter((event: string) => !events.includes(event)); + + await this.RUTTER_CONNECTION.updateWebhookEvents(webhookId, newEvents); + + return { + webhook, + events: newEvents, + }; + } + + async getSubscribedEvents({ webhookId }: WebhooksProps): Promise { + return this.RUTTER_CONNECTION.getSubscribedEvents(webhookId); + } + + async deleteWebhookEndpoint({ webhookId }: DeleteWebhookEndpointProps): Promise { + return this.RUTTER_CONNECTION.dropWebhook(webhookId); + } + + async testConnection(): Promise { + await this.RUTTER_CONNECTION.init(); + + return { + success: true, + message: "Connection to Rutter API established", + }; + } +} diff --git a/package-lock.json b/package-lock.json index f15802e9..144e4172 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,6 +25,7 @@ "firebase": "^9.15.0", "firebase-admin": "^11.3.0", "ftp": "^0.3.10", + "jwt-decode": "^3.1.2", "knex": "^2.4.0", "lodash": "^4.17.21", "paypal-rest-sdk": "^1.8.1", @@ -7711,6 +7712,11 @@ "safe-buffer": "^5.0.1" } }, + "node_modules/jwt-decode": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-3.1.2.tgz", + "integrity": "sha512-UfpWE/VZn0iP50d8cz9NrZLM9lSWhcJ+0Gt/nm4by88UL+J1SiKN8/5dkjMmbEzwL2CAe+67GsegCbIKtbp75A==" + }, "node_modules/keygrip": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/keygrip/-/keygrip-1.1.0.tgz", @@ -16904,6 +16910,11 @@ "safe-buffer": "^5.0.1" } }, + "jwt-decode": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-3.1.2.tgz", + "integrity": "sha512-UfpWE/VZn0iP50d8cz9NrZLM9lSWhcJ+0Gt/nm4by88UL+J1SiKN8/5dkjMmbEzwL2CAe+67GsegCbIKtbp75A==" + }, "keygrip": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/keygrip/-/keygrip-1.1.0.tgz", diff --git a/package.json b/package.json index 83a0463b..ec5922ae 100644 --- a/package.json +++ b/package.json @@ -64,6 +64,7 @@ "firebase": "^9.15.0", "firebase-admin": "^11.3.0", "ftp": "^0.3.10", + "jwt-decode": "^3.1.2", "knex": "^2.4.0", "lodash": "^4.17.21", "paypal-rest-sdk": "^1.8.1", From fffcf88c0547710ae1af06d9fbee70cdf3dca69d Mon Sep 17 00:00:00 2001 From: Houssem Eddine Zerrad Date: Sat, 11 Mar 2023 17:10:57 +0100 Subject: [PATCH 02/30] setup config.json --- catalog/sources/rutter/config.json | 265 +++++++++++++++++++---------- catalog/sources/rutter/rutter.ts | 20 ++- 2 files changed, 194 insertions(+), 91 deletions(-) diff --git a/catalog/sources/rutter/config.json b/catalog/sources/rutter/config.json index 90912738..9092e701 100644 --- a/catalog/sources/rutter/config.json +++ b/catalog/sources/rutter/config.json @@ -12,76 +12,78 @@ ], "authentication": [ { - "name": "GITLAB_ACCESS_TOKEN", - "label": "Enter your GitLab Personal API Key", - "placeholder": "glpat-MDsSCit_wit61jf2bJgv" + "name": "RUTTER_EMAIL", + "label": "Enter your Rutter Email", + "placeholder": "johndoe@mail.com" }, { - "name": "GITLAB_PROJECT_ID", - "label": "Enter your GitLab Project ID", - "placeholder": "39811640" + "name": "RUTTER_PASSWORD", + "label": "Enter your Rutter Password", + "placeholder": "********" }, { - "name": "GITLAB_WEBHOOK_SECRET", - "label": "Enter your GitLab Webhook Secret", - "placeholder": "some-secret-value" + "name": "RUTTER_CLIENT_SECRET", + "label": "Enter your Rutter Client Secret", + "placeholder": "sk_df556f38-4bbf-4278-b236-7818fba3c699" } ], "eventSchema": { - "object_kind": "push", - "event_name": "push", - "before": "04b313cae1b737f001e9706988f8abd2aeee4663", - "after": "04b313cae1b737f001e9706988f8abd2aeee4663", - "ref": "refs/heads/main", - "checkout_sha": "04b313cae1b737f001e9706988f8abd2aeee4663", - "message": null, - "user_id": 12258219, - "user_name": "Michael Schofield", - "user_username": "mike.onbike", - "user_email": null, - "user_avatar": "https://secure.gravatar.com/avatar/7fc156ca309234333a82c25b63574fb8?s=80&d=identicon", - "project_id": 38449140, - "project": { - "id": 38449140, - "name": "myproj", - "description": null, - "web_url": "https://gitlab.com/schoff1/myproj", - "avatar_url": null, - "git_ssh_url": "git@gitlab.com:schoff1/myproj.git", - "git_http_url": "https://gitlab.com/schoff1/myproj.git", - "namespace": "schoff", - "visibility_level": 0, - "path_with_namespace": "schoff1/myproj", - "default_branch": "main", - "ci_config_path": "", - "homepage": "https://gitlab.com/schoff1/myproj", - "url": "git@gitlab.com:schoff1/myproj.git", - "ssh_url": "git@gitlab.com:schoff1/myproj.git", - "http_url": "https://gitlab.com/schoff1/myproj.git" - }, - "commits": [ - { - "id": "04b313cae1b737f001e9706988f8abd2aeee4663", - "message": "Initial commit", - "title": "Initial commit", - "timestamp": "2022-08-09T10:25:08+00:00", - "url": "https://gitlab.com/schoff1/myproj/-/commit/04b313cae1b737f001e9706988f8abd2aeee4663", - "author": { "name": "Michael Schofield", "email": "mike.schoff@somemail.com" }, - "added": [ "README.md" ], - "modified": [], - "removed": [] + "type": "INVOICE", + "code": "INVOICE_CREATED", + "access_token": "313c3e00-2bbc-4b2e-a9dc-13abed546b65", + "invoice": { + "id": "00000000-0000-0000-0000-000000000000", + "account_id": "00000000-0000-0000-0000-000000000000", + "platform_id": "12345678", + "document_number": "CUSTINVC-1", + "customer_id": "00000000-0000-0000-0000-000000000000", + "issue_date": "2023-01-02T02:34:56.000Z", + "due_date": "2023-01-02T02:34:56.000Z", + "currency_code": "USD", + "line_items": [ + { + "id": "00000000-0000-0000-0000-000000000000", + "platform_id": "12345678", + "description": "A Rutter shirt.", + "unit_amount": "12.34", + "quantity": 1, + "discount_amount": "0", + "sub_total": "12.34", + "tax_amount": "2", + "amount": "14.34", + "discount_percentage": "0", + "tax_rate_id": "00000000-0000-0000-0000-000000000000", + "item_id": "00000000-0000-0000-0000-000000000000" + } + ], + "status": "partially_paid", + "total_discount": "0", + "sub_total": "12.34", + "tax_amount": "2", + "total_amount": "14.34", + "amount_due": "9.34", + "memo": "For a Rutter shirt.", + "linked_payments": [ + { + "id": "00000000-0000-0000-0000-000000000000", + "type": "INVOICE_PAYMENT", + "amount": "3", + "date": "2023-01-02T02:34:56.000Z" + }, + { + "id": "00000000-0000-0000-0000-000000000000", + "type": "INVOICE_CREDIT_MEMO", + "amount": "2", + "date": "2023-01-02T02:34:56.000Z" + } + ], + "customer": { + "id": "00000000-0000-0000-0000-000000000000", + "platform_id": "12345678", + "name": "Eric Yu", + "email": "eric@rutter.com", + "updated_at": "2023-01-02T02:34:56.000Z" } - ], - "total_commits_count": 1, - "push_options": {}, - "repository": { - "name": "myproj", - "url": "git@gitlab.com:schoff1/myproj.git", - "description": null, - "homepage": "https://gitlab.com/schoff1/myproj", - "git_http_url": "https://gitlab.com/schoff1/myproj.git", - "git_ssh_url": "git@gitlab.com:schoff1/myproj.git", - "visibility_level": 0 } }, "settings": { @@ -92,71 +94,156 @@ }, "paths": { "id": "_.body.id", - "event": "_.body.event_name", + "event": "_.body.code", "payload": "_.body", - "secret": "_.headers.x-gitlab-token", - "signature": "_.headers.x-gitlab-token" + "secret": "_.headers.x-rutter-signature", + "signature": "_.headers.x-rutter-signature" }, "connectionTypes": ["source"], "events": [ { - "name": "confidential_issues_events", - "description": "A new confidential issue is created or an existing issue is updated, closed, or reopened.", + "name": "INITIAL_UPDATE", + "description": "The initial synchronization of data", "group": "" }, { - "name": "confidential_note_events", - "description": "A confidential note/comment is made or an existing note is updated, closed, or reopened.", + "name": "HISTORICAL_UPDATE", + "description": "A historical update of data", "group": "" }, { - "name": "deployment_events", - "description": "A deployment starts, succeeds, fails, or is canceled.\n", + "name": "CONNECTION_UPDATED", + "description": "The connection to the API has been updated", "group": "" }, { - "name": "issues_events", - "description": "A new issue is created or an existing issue is updated, closed, or reopened.", + "name": "CONNECTION_NEEDS_UPDATE", + "description": "The connection to the API needs an update", "group": "" }, { - "name": "job_events", - "description": "A job status changes.", + "name": "CONNECTION_DISABLED", + "description": "The connection to the API has been disabled", "group": "" }, { - "name": "merge_requests_events", - "description": "A merge request is created, updated, merged, or closed, or a commit is added in the source branch.", + "name": "CONNECTION_LINK_ERROR", + "description": "There was an error with the connection to the API link", "group": "" }, { - "name": "note_events", - "description": "A note/comment is made or an existing note is updated, closed, or reopened.", + "name": "CONNECTION_ERROR", + "description": "There was an error with the connection to the API", "group": "" }, { - "name": "pipeline_events", - "description": "A pipeline status changes.", + "name": "ORDER_FULFILLED", + "description": "An order has been fulfilled", "group": "" }, { - "name": "push_events", - "description": "A push is made to the repository.", + "name": "ORDER_DELETED", + "description": "An order has been deleted", "group": "" }, { - "name": "releases_events", - "description": "A release is created or updated", + "name": "ORDER_UPDATED", + "description": "An order has been updated", "group": "" }, { - "name": "tag_push_events", - "description": "Tags are created or deleted in the repository.", + "name": "ORDER_CREATED", + "description": "A new order has been placed", "group": "" }, { - "name": "wiki_page_events", - "description": "A wiki page is created, updated, or deleted.", + "name": "PRODUCT_CREATED", + "description": "A new product has been created", + "group": "" + }, + { + "name": "PRODUCT_UPDATED", + "description": "A product has been updated", + "group": "" + }, + { + "name": "PRODUCT_DELETED", + "description": "A product has been deleted", + "group": "" + }, + { + "name": "PRODUCT_READY", + "description": "A product is ready to be published", + "group": "" + }, + { + "name": "PRODUCT_FINISH_FAILED", + "description": "An error occurred while finishing a product", + "group": "" + }, + { + "name": "CUSTOMER_CREATED", + "description": "A new customer has been created", + "group": "" + }, + { + "name": "CUSTOMER_UPDATED", + "description": "A customer has been updated", + "group": "" + }, + { + "name": "CUSTOMER_DELETED", + "description": "A customer has been deleted", + "group": "" + }, + { + "name": "STORE_UPDATED", + "description": "A store has been updated", + "group": "" + }, + { + "name": "JOB_COMPLETED", + "description": "A job has been completed", + "group": "" + }, + { + "name": "INVOICE_CREATED", + "description": "An invoice has been created", + "group": "" + }, + { + "name": "INVOICE_UPDATED", + "description": "An invoice has been updated", + "group": "" + }, + { + "name": "ACCOUNT_CREATED", + "description": "An account has been created", + "group": "" + }, + { + "name": "ACCOUNT_UPDATED", + "description": "An account has been updated", + "group": "" + }, + { + "name": "BILL_CREATED", + "description": "A bill has been created", + "group": "" + }, + { + "name": "BILL_UPDATED", + "description": "A bill has been updated", + "group": "" + }, + { + "name": "VENDOR_CREATED", + "description": "A vendor has been created", + "group": "" + }, + { + "name": "VENDOR_UPDATED", + "description": "A vendor has been updated", "group": "" } ] diff --git a/catalog/sources/rutter/rutter.ts b/catalog/sources/rutter/rutter.ts index 443a99cf..74df7cb3 100644 --- a/catalog/sources/rutter/rutter.ts +++ b/catalog/sources/rutter/rutter.ts @@ -1,3 +1,4 @@ +import crypto from "crypto"; import { AnyObject, DeleteWebhookEndpointProps, Events, @@ -17,17 +18,22 @@ export default class RutterIntegration implements IntegrationClassI { private readonly RUTTER_PASSWORD: string; + private readonly RUTTER_CLIENT_SECRET: string; + private RUTTER_CONNECTION: RutterConnection; constructor({ RUTTER_EMAIL, RUTTER_PASSWORD, + RUTTER_CLIENT_SECRET, }: { RUTTER_EMAIL: string; RUTTER_PASSWORD: string; + RUTTER_CLIENT_SECRET: string; }) { this.RUTTER_EMAIL = RUTTER_EMAIL; this.RUTTER_PASSWORD = RUTTER_PASSWORD; + this.RUTTER_CLIENT_SECRET = RUTTER_CLIENT_SECRET; this.RUTTER_CONNECTION = new RutterConnection(RUTTER_EMAIL, RUTTER_PASSWORD); } @@ -50,8 +56,18 @@ export default class RutterIntegration implements IntegrationClassI { }; } - async verifyWebhookSignature(props: VerifyWebhookSignatureProps): Promise { - return Promise.resolve(undefined); + async verifyWebhookSignature({ request, signature }: VerifyWebhookSignatureProps): Promise { + const secret = this.RUTTER_CLIENT_SECRET; + const hash = crypto + .createHmac("sha256", secret) + .update(request.body, "utf8") + .digest("base64"); + + if (`sha256=${hash}` !== signature) { + throw new Error("Invalid signature"); + } + + return true; } async subscribe({ webhookId, events }: SubscriptionProps): Promise { From e4f92b8175a2e27df2d15fe496cc326d85ede416 Mon Sep 17 00:00:00 2001 From: Houssem Eddine Zerrad Date: Sat, 11 Mar 2023 23:55:00 +0100 Subject: [PATCH 03/30] add mandatory events to all subscription events --- catalog/sources/rutter/lib/connection.ts | 22 +++++++++++++++++++--- catalog/sources/rutter/rutter.ts | 11 ++++++----- 2 files changed, 25 insertions(+), 8 deletions(-) diff --git a/catalog/sources/rutter/lib/connection.ts b/catalog/sources/rutter/lib/connection.ts index dfe58341..c47274fa 100644 --- a/catalog/sources/rutter/lib/connection.ts +++ b/catalog/sources/rutter/lib/connection.ts @@ -45,7 +45,7 @@ export default class RutterConnection { async createWebhook({ webhookUrl, events }: {webhookUrl: string, events: Events}): Promise { await this.rotateAccessToken(); - const allowedWebhookTypes = `[${events.map((event) => `"${event}"`).join(", ")}]`; + const allowedWebhookTypes = this.createWebhookTypes(events); // TODO: change isSandbox to false const mutation = ` @@ -71,7 +71,12 @@ export default class RutterConnection { } `; - await this.client.post("", { query: mutation }); + try { + await this.client.post("", { query: mutation }); + } catch (e) { + console.log("Error creating webhook: ", e.response.data); + return false; + } return true; } @@ -152,7 +157,7 @@ export default class RutterConnection { async updateWebhookEvents(webhookId: string, events: Events): Promise { await this.rotateAccessToken(); - const allowedWebhookTypes = `[${events.map((event) => `"${event}"`).join(", ")}]`; + const allowedWebhookTypes = this.createWebhookTypes(events); const mutation = ` mutation { @@ -248,4 +253,15 @@ export default class RutterConnection { return response.data.data.login.token; } + + /** + * Create the webhook types string for the GraphQL mutation + * @param events + * @private + */ + private createWebhookTypes(events: string[]) { + const eventList = Array.from(new Set([...MANDATORY_EVENTS, ...events])); + + return `[${eventList.map((event) => `"${event}"`).join(", ")}]`; + } } diff --git a/catalog/sources/rutter/rutter.ts b/catalog/sources/rutter/rutter.ts index 74df7cb3..ec4b251f 100644 --- a/catalog/sources/rutter/rutter.ts +++ b/catalog/sources/rutter/rutter.ts @@ -52,7 +52,7 @@ export default class RutterIntegration implements IntegrationClassI { // return webhook return { webhookData: webhook, - events, + events: webhook.allowlist.allowedTypes, }; } @@ -74,7 +74,8 @@ export default class RutterIntegration implements IntegrationClassI { const webhook = await this.RUTTER_CONNECTION.updateWebhookEvents(webhookId, events); return { - webhook, events, + webhook, + events: webhook.allowlist.allowedTypes, }; } @@ -86,11 +87,11 @@ export default class RutterIntegration implements IntegrationClassI { const webhook = await this.RUTTER_CONNECTION.findWebhookById(webhookId); const newEvents = webhook.allowlist.allowedTypes.filter((event: string) => !events.includes(event)); - await this.RUTTER_CONNECTION.updateWebhookEvents(webhookId, newEvents); + const newWebhook = await this.RUTTER_CONNECTION.updateWebhookEvents(webhookId, newEvents); return { - webhook, - events: newEvents, + webhook: newWebhook, + events: newWebhook.allowlist.allowedTypes, }; } From dd650937ae3569d290d08f63482519eee532514c Mon Sep 17 00:00:00 2001 From: Houssem Eddine Zerrad Date: Sun, 12 Mar 2023 00:06:17 +0100 Subject: [PATCH 04/30] add mandatory events to all subscription events --- catalog/sources/rutter/lib/connection.ts | 8 +- catalog/sources/rutter/rutter.ts | 133 ++++++++++++++++------- 2 files changed, 96 insertions(+), 45 deletions(-) diff --git a/catalog/sources/rutter/lib/connection.ts b/catalog/sources/rutter/lib/connection.ts index c47274fa..1624f76c 100644 --- a/catalog/sources/rutter/lib/connection.ts +++ b/catalog/sources/rutter/lib/connection.ts @@ -70,13 +70,7 @@ export default class RutterConnection { } } `; - - try { - await this.client.post("", { query: mutation }); - } catch (e) { - console.log("Error creating webhook: ", e.response.data); - return false; - } + await this.client.post("", { query: mutation }); return true; } diff --git a/catalog/sources/rutter/rutter.ts b/catalog/sources/rutter/rutter.ts index ec4b251f..994038c4 100644 --- a/catalog/sources/rutter/rutter.ts +++ b/catalog/sources/rutter/rutter.ts @@ -1,4 +1,5 @@ import crypto from "crypto"; +import axios from "axios"; import { AnyObject, DeleteWebhookEndpointProps, Events, @@ -40,20 +41,28 @@ export default class RutterIntegration implements IntegrationClassI { async init({ webhookUrl, events }: InitProps): Promise { // initialize connection with Rutter - this.RUTTER_CONNECTION = new RutterConnection(this.RUTTER_EMAIL, this.RUTTER_PASSWORD); - await this.RUTTER_CONNECTION.init(); - - // issue create a webhook - await this.RUTTER_CONNECTION.createWebhook({ webhookUrl, events }); - - // issue list webhooks - const webhook = await this.RUTTER_CONNECTION.findWebhookByUrl(webhookUrl); - - // return webhook - return { - webhookData: webhook, - events: webhook.allowlist.allowedTypes, - }; + try { + this.RUTTER_CONNECTION = new RutterConnection(this.RUTTER_EMAIL, this.RUTTER_PASSWORD); + await this.RUTTER_CONNECTION.init(); + + // issue create a webhook + await this.RUTTER_CONNECTION.createWebhook({ webhookUrl, events }); + + // issue list webhooks + const webhook = await this.RUTTER_CONNECTION.findWebhookByUrl(webhookUrl); + + // return webhook + return { + webhookData: webhook, + events: webhook.allowlist.allowedTypes, + }; + } catch (e) { + if (axios.isAxiosError(e)) { + throw new Error(`Rutter API Error: ${JSON.stringify((e.response.data as any).errors)}`); + } + + throw new Error(`An unexpected error occurred: ${e.message}`); + } } async verifyWebhookSignature({ request, signature }: VerifyWebhookSignatureProps): Promise { @@ -71,44 +80,92 @@ export default class RutterIntegration implements IntegrationClassI { } async subscribe({ webhookId, events }: SubscriptionProps): Promise { - const webhook = await this.RUTTER_CONNECTION.updateWebhookEvents(webhookId, events); - - return { - webhook, - events: webhook.allowlist.allowedTypes, - }; + try { + const webhook = await this.RUTTER_CONNECTION.updateWebhookEvents(webhookId, events); + + return { + webhook, + events: webhook.allowlist.allowedTypes, + }; + } catch (e) { + if (axios.isAxiosError(e)) { + throw new Error(`Rutter API Error: ${JSON.stringify((e.response.data as any).errors)}`); + } + + throw new Error(`An unexpected error occurred: ${e.message}`); + } } async getWebhooks({ webhookId }: WebhooksProps | undefined): Promise { - return this.RUTTER_CONNECTION.findWebhookById(webhookId); + try { + return this.RUTTER_CONNECTION.findWebhookById(webhookId); + } catch (e) { + if (axios.isAxiosError(e)) { + throw new Error(`Rutter API Error: ${JSON.stringify((e.response.data as any).errors)}`); + } + + throw new Error(`An unexpected error occurred: ${e.message}`); + } } async unsubscribe({ webhookId, events }: SubscriptionProps): Promise<{ events: Events; webhook?: any; webhooks?: any }> { - const webhook = await this.RUTTER_CONNECTION.findWebhookById(webhookId); - const newEvents = webhook.allowlist.allowedTypes.filter((event: string) => !events.includes(event)); - - const newWebhook = await this.RUTTER_CONNECTION.updateWebhookEvents(webhookId, newEvents); - - return { - webhook: newWebhook, - events: newWebhook.allowlist.allowedTypes, - }; + try { + const webhook = await this.RUTTER_CONNECTION.findWebhookById(webhookId); + const newEvents = webhook.allowlist.allowedTypes.filter((event: string) => !events.includes(event)); + + const newWebhook = await this.RUTTER_CONNECTION.updateWebhookEvents(webhookId, newEvents); + + return { + webhook: newWebhook, + events: newWebhook.allowlist.allowedTypes, + }; + } catch (e) { + if (axios.isAxiosError(e)) { + throw new Error(`Rutter API Error: ${JSON.stringify((e.response.data as any).errors)}`); + } + + throw new Error(`An unexpected error occurred: ${e.message}`); + } } async getSubscribedEvents({ webhookId }: WebhooksProps): Promise { - return this.RUTTER_CONNECTION.getSubscribedEvents(webhookId); + try { + return this.RUTTER_CONNECTION.getSubscribedEvents(webhookId); + } catch (e) { + if (axios.isAxiosError(e)) { + throw new Error(`Rutter API Error: ${JSON.stringify((e.response.data as any).errors)}`); + } + + throw new Error(`An unexpected error occurred: ${e.message}`); + } } async deleteWebhookEndpoint({ webhookId }: DeleteWebhookEndpointProps): Promise { - return this.RUTTER_CONNECTION.dropWebhook(webhookId); + try { + return this.RUTTER_CONNECTION.dropWebhook(webhookId); + } catch (e) { + if (axios.isAxiosError(e)) { + throw new Error(`Rutter API Error: ${JSON.stringify((e.response.data as any).errors)}`); + } + + throw new Error(`An unexpected error occurred: ${e.message}`); + } } async testConnection(): Promise { - await this.RUTTER_CONNECTION.init(); - - return { - success: true, - message: "Connection to Rutter API established", - }; + try { + await this.RUTTER_CONNECTION.init(); + + return { + success: true, + message: "Connection to Rutter API established", + }; + } catch (e) { + console.log(e.message); + return { + success: false, + message: `Connection to Rutter API failed: ${e.message}`, + }; + } } } From 997c25525396eb0a5caedf18e62acbf1e01783a9 Mon Sep 17 00:00:00 2001 From: Houssem Eddine Zerrad Date: Sun, 12 Mar 2023 00:21:19 +0100 Subject: [PATCH 05/30] remove mandatory events from config.json --- catalog/sources/rutter/config.json | 35 ------------------------------ 1 file changed, 35 deletions(-) diff --git a/catalog/sources/rutter/config.json b/catalog/sources/rutter/config.json index 9092e701..e9e3f98a 100644 --- a/catalog/sources/rutter/config.json +++ b/catalog/sources/rutter/config.json @@ -101,41 +101,6 @@ }, "connectionTypes": ["source"], "events": [ - { - "name": "INITIAL_UPDATE", - "description": "The initial synchronization of data", - "group": "" - }, - { - "name": "HISTORICAL_UPDATE", - "description": "A historical update of data", - "group": "" - }, - { - "name": "CONNECTION_UPDATED", - "description": "The connection to the API has been updated", - "group": "" - }, - { - "name": "CONNECTION_NEEDS_UPDATE", - "description": "The connection to the API needs an update", - "group": "" - }, - { - "name": "CONNECTION_DISABLED", - "description": "The connection to the API has been disabled", - "group": "" - }, - { - "name": "CONNECTION_LINK_ERROR", - "description": "There was an error with the connection to the API link", - "group": "" - }, - { - "name": "CONNECTION_ERROR", - "description": "There was an error with the connection to the API", - "group": "" - }, { "name": "ORDER_FULFILLED", "description": "An order has been fulfilled", From d0d19b5f2df4f690d7419a315bc014584cfa41da Mon Sep 17 00:00:00 2001 From: Houssem Eddine Zerrad Date: Sun, 12 Mar 2023 01:11:43 +0100 Subject: [PATCH 06/30] Check if no access token is present before issuing a new one --- catalog/sources/rutter/lib/connection.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/catalog/sources/rutter/lib/connection.ts b/catalog/sources/rutter/lib/connection.ts index 1624f76c..819eaab7 100644 --- a/catalog/sources/rutter/lib/connection.ts +++ b/catalog/sources/rutter/lib/connection.ts @@ -208,10 +208,14 @@ export default class RutterConnection { * @private */ private async rotateAccessToken() { - const decodedToken = jwtDecode(this.ACCESS_TOKEN) as RutterDecodedToken; - - if (decodedToken.exp < Date.now() / 1000) { + if (!this.ACCESS_TOKEN) { await this.init(); + } else { + const decodedToken = jwtDecode(this.ACCESS_TOKEN) as RutterDecodedToken; + + if (decodedToken.exp < Date.now() / 1000) { + await this.init(); + } } } From ee2aaf48d51fcf3f0d0cea4b8a30b9aca70ce446 Mon Sep 17 00:00:00 2001 From: Houssem Eddine Zerrad Date: Sun, 12 Mar 2023 15:01:17 +0100 Subject: [PATCH 07/30] Raise if Rutter's GraphQL APi returns an error object --- catalog/sources/rutter/lib/connection.ts | 27 ++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/catalog/sources/rutter/lib/connection.ts b/catalog/sources/rutter/lib/connection.ts index 819eaab7..1d4e7e10 100644 --- a/catalog/sources/rutter/lib/connection.ts +++ b/catalog/sources/rutter/lib/connection.ts @@ -70,9 +70,9 @@ export default class RutterConnection { } } `; - await this.client.post("", { query: mutation }); + const response = await this.client.post("", { query: mutation }); - return true; + return response.data.errors === undefined; } /** @@ -98,6 +98,11 @@ export default class RutterConnection { }`; const response = await this.client.post("", { query }); + + if (response.data.errors) { + throw Error(`${JSON.stringify(response.data.errors)}`); + } + const webhooks: Array = response.data.data.me.organization.webhookConfigs; for (const webhook of webhooks) { @@ -132,6 +137,11 @@ export default class RutterConnection { }`; const response = await this.client.post("", { query }); + + if (response.data.errors) { + throw Error(`${JSON.stringify(response.data.errors)}`); + } + const webhooks: Array = response.data.data.me.organization.webhookConfigs; for (const webhook of webhooks) { @@ -171,6 +181,10 @@ export default class RutterConnection { const response = await this.client.post("", { query: mutation }); + if (response.data.errors) { + throw Error(`${JSON.stringify(response.data.errors)}`); + } + return response.data.data.updateWebhookConfig.webhookConfig as RutterWebhook; } @@ -190,6 +204,11 @@ export default class RutterConnection { }`; const response = await this.client.post("", { query: mutation }); + + if (response.data.errors) { + throw Error(`${JSON.stringify(response.data.errors)}`); + } + return response.data.data.deleteWebhookUrl.error === null; } @@ -249,6 +268,10 @@ export default class RutterConnection { const response = await axios.post(RutterConnection.BASE_URL, { query: mutation }); + if (response.data.errors) { + throw Error(`${JSON.stringify(response.data.errors)}`); + } + return response.data.data.login.token; } From ba94d916f6e4469900e1d99fbef32629d087d090 Mon Sep 17 00:00:00 2001 From: Houssem Eddine Zerrad Date: Sun, 12 Mar 2023 21:55:19 +0100 Subject: [PATCH 08/30] Filter out mandatory Rutter event list from the subscription result --- catalog/sources/rutter/lib/connection.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/catalog/sources/rutter/lib/connection.ts b/catalog/sources/rutter/lib/connection.ts index 1d4e7e10..b032a707 100644 --- a/catalog/sources/rutter/lib/connection.ts +++ b/catalog/sources/rutter/lib/connection.ts @@ -107,6 +107,8 @@ export default class RutterConnection { for (const webhook of webhooks) { if (webhook.url === url) { + // filter out mandatory events from the list + webhook.allowlist.allowedTypes = webhook.allowlist.allowedTypes.filter((type) => !MANDATORY_EVENTS.includes(type)); return webhook; } } @@ -146,6 +148,8 @@ export default class RutterConnection { for (const webhook of webhooks) { if (webhook.id === webhookId) { + // filter out mandatory events from the list + webhook.allowlist.allowedTypes = webhook.allowlist.allowedTypes.filter((type) => !MANDATORY_EVENTS.includes(type)); return webhook; } } @@ -185,7 +189,12 @@ export default class RutterConnection { throw Error(`${JSON.stringify(response.data.errors)}`); } - return response.data.data.updateWebhookConfig.webhookConfig as RutterWebhook; + const webhook = response.data.data.updateWebhookConfig.webhookConfig as RutterWebhook; + + // filter out mandatory events from the list + webhook.allowlist.allowedTypes = webhook.allowlist.allowedTypes.filter((type) => !MANDATORY_EVENTS.includes(type)); + + return webhook; } /** From 54895430d1e2c4b992e4a0c33d30d02ff7840532 Mon Sep 17 00:00:00 2001 From: Paul Krishnamurthy Date: Fri, 31 Mar 2023 15:11:13 -0400 Subject: [PATCH 09/30] Remove Rutter Source --- catalog/sources/rutter/CHANGELOG.md | 12 - catalog/sources/rutter/config.json | 215 ---------------- catalog/sources/rutter/docs/connect.md | 10 - catalog/sources/rutter/docs/setup.md | 3 - catalog/sources/rutter/lib/connection.ts | 297 ----------------------- catalog/sources/rutter/lib/types.ts | 15 -- catalog/sources/rutter/rutter.ts | 171 ------------- package.json | 1 - 8 files changed, 724 deletions(-) delete mode 100644 catalog/sources/rutter/CHANGELOG.md delete mode 100644 catalog/sources/rutter/config.json delete mode 100644 catalog/sources/rutter/docs/connect.md delete mode 100644 catalog/sources/rutter/docs/setup.md delete mode 100644 catalog/sources/rutter/lib/connection.ts delete mode 100644 catalog/sources/rutter/lib/types.ts delete mode 100644 catalog/sources/rutter/rutter.ts diff --git a/catalog/sources/rutter/CHANGELOG.md b/catalog/sources/rutter/CHANGELOG.md deleted file mode 100644 index 942879b1..00000000 --- a/catalog/sources/rutter/CHANGELOG.md +++ /dev/null @@ -1,12 +0,0 @@ -# 📣 Change Log -All notable changes to the `Rutter` Connection will be documented in this file. - -The format followed is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). - ---- - -## [1.0.0] - 2023-03-09 - -⚡️ Initial Version - ---- diff --git a/catalog/sources/rutter/config.json b/catalog/sources/rutter/config.json deleted file mode 100644 index e9e3f98a..00000000 --- a/catalog/sources/rutter/config.json +++ /dev/null @@ -1,215 +0,0 @@ -{ - "title": "Rutter", - "description": "A universal API for Commerce, Accounting, Payments & More. For devs, by devs. Simple auth flow", - "apiVersion": "NA", - "type": "rutter", - "category": "accounting", - "image": "https://assets.buildable.dev/catalog/node-templates/rutter.svg", - "tags": [ - "ecommerce", - "accounting", - "payments" - ], - "authentication": [ - { - "name": "RUTTER_EMAIL", - "label": "Enter your Rutter Email", - "placeholder": "johndoe@mail.com" - }, - { - "name": "RUTTER_PASSWORD", - "label": "Enter your Rutter Password", - "placeholder": "********" - }, - { - "name": "RUTTER_CLIENT_SECRET", - "label": "Enter your Rutter Client Secret", - "placeholder": "sk_df556f38-4bbf-4278-b236-7818fba3c699" - } - ], - "eventSchema": { - "type": "INVOICE", - "code": "INVOICE_CREATED", - "access_token": "313c3e00-2bbc-4b2e-a9dc-13abed546b65", - "invoice": { - "id": "00000000-0000-0000-0000-000000000000", - "account_id": "00000000-0000-0000-0000-000000000000", - "platform_id": "12345678", - "document_number": "CUSTINVC-1", - "customer_id": "00000000-0000-0000-0000-000000000000", - "issue_date": "2023-01-02T02:34:56.000Z", - "due_date": "2023-01-02T02:34:56.000Z", - "currency_code": "USD", - "line_items": [ - { - "id": "00000000-0000-0000-0000-000000000000", - "platform_id": "12345678", - "description": "A Rutter shirt.", - "unit_amount": "12.34", - "quantity": 1, - "discount_amount": "0", - "sub_total": "12.34", - "tax_amount": "2", - "amount": "14.34", - "discount_percentage": "0", - "tax_rate_id": "00000000-0000-0000-0000-000000000000", - "item_id": "00000000-0000-0000-0000-000000000000" - } - ], - "status": "partially_paid", - "total_discount": "0", - "sub_total": "12.34", - "tax_amount": "2", - "total_amount": "14.34", - "amount_due": "9.34", - "memo": "For a Rutter shirt.", - "linked_payments": [ - { - "id": "00000000-0000-0000-0000-000000000000", - "type": "INVOICE_PAYMENT", - "amount": "3", - "date": "2023-01-02T02:34:56.000Z" - }, - { - "id": "00000000-0000-0000-0000-000000000000", - "type": "INVOICE_CREDIT_MEMO", - "amount": "2", - "date": "2023-01-02T02:34:56.000Z" - } - ], - "customer": { - "id": "00000000-0000-0000-0000-000000000000", - "platform_id": "12345678", - "name": "Eric Yu", - "email": "eric@rutter.com", - "updated_at": "2023-01-02T02:34:56.000Z" - } - } - }, - "settings": { - "parseWebhookBody": false, - "hasSubscriptionDelay": false, - "subscriptionDelayMultiplier": 0.0, - "showEvents": true - }, - "paths": { - "id": "_.body.id", - "event": "_.body.code", - "payload": "_.body", - "secret": "_.headers.x-rutter-signature", - "signature": "_.headers.x-rutter-signature" - }, - "connectionTypes": ["source"], - "events": [ - { - "name": "ORDER_FULFILLED", - "description": "An order has been fulfilled", - "group": "" - }, - { - "name": "ORDER_DELETED", - "description": "An order has been deleted", - "group": "" - }, - { - "name": "ORDER_UPDATED", - "description": "An order has been updated", - "group": "" - }, - { - "name": "ORDER_CREATED", - "description": "A new order has been placed", - "group": "" - }, - { - "name": "PRODUCT_CREATED", - "description": "A new product has been created", - "group": "" - }, - { - "name": "PRODUCT_UPDATED", - "description": "A product has been updated", - "group": "" - }, - { - "name": "PRODUCT_DELETED", - "description": "A product has been deleted", - "group": "" - }, - { - "name": "PRODUCT_READY", - "description": "A product is ready to be published", - "group": "" - }, - { - "name": "PRODUCT_FINISH_FAILED", - "description": "An error occurred while finishing a product", - "group": "" - }, - { - "name": "CUSTOMER_CREATED", - "description": "A new customer has been created", - "group": "" - }, - { - "name": "CUSTOMER_UPDATED", - "description": "A customer has been updated", - "group": "" - }, - { - "name": "CUSTOMER_DELETED", - "description": "A customer has been deleted", - "group": "" - }, - { - "name": "STORE_UPDATED", - "description": "A store has been updated", - "group": "" - }, - { - "name": "JOB_COMPLETED", - "description": "A job has been completed", - "group": "" - }, - { - "name": "INVOICE_CREATED", - "description": "An invoice has been created", - "group": "" - }, - { - "name": "INVOICE_UPDATED", - "description": "An invoice has been updated", - "group": "" - }, - { - "name": "ACCOUNT_CREATED", - "description": "An account has been created", - "group": "" - }, - { - "name": "ACCOUNT_UPDATED", - "description": "An account has been updated", - "group": "" - }, - { - "name": "BILL_CREATED", - "description": "A bill has been created", - "group": "" - }, - { - "name": "BILL_UPDATED", - "description": "A bill has been updated", - "group": "" - }, - { - "name": "VENDOR_CREATED", - "description": "A vendor has been created", - "group": "" - }, - { - "name": "VENDOR_UPDATED", - "description": "A vendor has been updated", - "group": "" - } - ] -} diff --git a/catalog/sources/rutter/docs/connect.md b/catalog/sources/rutter/docs/connect.md deleted file mode 100644 index ee50ecf8..00000000 --- a/catalog/sources/rutter/docs/connect.md +++ /dev/null @@ -1,10 +0,0 @@ -## Connect - -A universal API for Commerce, Accounting, Payments & More. For devs, by devs. Simple auth flow -### Securely Encrypted - -Rest assured, your credentials are securely encrypted to keep your information safe. - -### Need Help? - -Start a conversation in our [Discord Server](https://discord.com/invite/47AJ42Wzys) or send an email to [support@buildable.dev](mailto:https://discord.com/invite/47AJ42Wzys). Our Data Engineers are available 24/7 to help if you ever get stuck. \ No newline at end of file diff --git a/catalog/sources/rutter/docs/setup.md b/catalog/sources/rutter/docs/setup.md deleted file mode 100644 index 2ef4b3dc..00000000 --- a/catalog/sources/rutter/docs/setup.md +++ /dev/null @@ -1,3 +0,0 @@ -## Rutter Source Setup - -Check out our [quick start guide](https://docs.buildable.dev/) to learn how to get started. \ No newline at end of file diff --git a/catalog/sources/rutter/lib/connection.ts b/catalog/sources/rutter/lib/connection.ts deleted file mode 100644 index b032a707..00000000 --- a/catalog/sources/rutter/lib/connection.ts +++ /dev/null @@ -1,297 +0,0 @@ -import axios, { AxiosInstance } from "axios"; -import jwtDecode from "jwt-decode"; -import { RutterDecodedToken, RutterWebhook } from "./types"; -import { Events } from "../../../../types/sourceClassDefinition"; - -const MANDATORY_EVENTS = [ - "INITIAL_UPDATE", - "HISTORICAL_UPDATE", - "CONNECTION_UPDATED", - "CONNECTION_NEEDS_UPDATE", - "CONNECTION_DISABLED", - "CONNECTION_LINK_ERROR", - "CONNECTION_ERROR", -]; - -export default class RutterConnection { - static readonly BASE_URL = "https://production.rutterapi.com/graphql"; - - private readonly email: string; - - private readonly password: string; - - private ACCESS_TOKEN: string; - - private client: AxiosInstance; - - constructor(email: string, password: string) { - this.email = email; - this.password = password; - } - - /** - * Initialize the Rutter connection by getting an access token and initializing the client - */ - async init() { - this.ACCESS_TOKEN = await this.getAccessToken(); - await this.initializeClient(); - } - - /** - * Create a Rutter webhook with the passed events - * @param webhookUrl Event webhook URL - * @param events list of Webhook Types to subscribe to - */ - async createWebhook({ webhookUrl, events }: {webhookUrl: string, events: Events}): Promise { - await this.rotateAccessToken(); - - const allowedWebhookTypes = this.createWebhookTypes(events); - - // TODO: change isSandbox to false - const mutation = ` - mutation { - addWebhookConfigToOrg(input: { - url: "${webhookUrl}", - isSandbox: true, - allowedWebhookTypes: ${allowedWebhookTypes} - }) { - org { - id - webhookConfigs { - id - url - isSandbox - disabled - allowlist { - allowedTypes - } - } - } - } - } - `; - const response = await this.client.post("", { query: mutation }); - - return response.data.errors === undefined; - } - - /** - * Find a Rutter webhook given its URL - * @param url Webhook URL - */ - async findWebhookByUrl(url: string): Promise { - const query = ` - query WebHooks { - me { - organization { - webhookConfigs { - id - url - isSandbox - disabled - allowlist { - allowedTypes - } - } - } - } - }`; - - const response = await this.client.post("", { query }); - - if (response.data.errors) { - throw Error(`${JSON.stringify(response.data.errors)}`); - } - - const webhooks: Array = response.data.data.me.organization.webhookConfigs; - - for (const webhook of webhooks) { - if (webhook.url === url) { - // filter out mandatory events from the list - webhook.allowlist.allowedTypes = webhook.allowlist.allowedTypes.filter((type) => !MANDATORY_EVENTS.includes(type)); - return webhook; - } - } - - throw Error("Could not find webhook with the given URL"); - } - - /** - * Find a Rutter webhook given its ID - * @param webhookId Webhook ID - */ - async findWebhookById(webhookId: string) { - const query = ` - query WebHooks { - me { - organization { - webhookConfigs { - id - url - isSandbox - disabled - allowlist { - allowedTypes - } - } - } - } - }`; - - const response = await this.client.post("", { query }); - - if (response.data.errors) { - throw Error(`${JSON.stringify(response.data.errors)}`); - } - - const webhooks: Array = response.data.data.me.organization.webhookConfigs; - - for (const webhook of webhooks) { - if (webhook.id === webhookId) { - // filter out mandatory events from the list - webhook.allowlist.allowedTypes = webhook.allowlist.allowedTypes.filter((type) => !MANDATORY_EVENTS.includes(type)); - return webhook; - } - } - - throw Error("Could not find webhook with the given ID"); - } - - /** - * Update the events for a Rutter webhook - * @param webhookId Rutter Webhook ID - * @param events list of Webhook Types to subscribe to - */ - async updateWebhookEvents(webhookId: string, events: Events): Promise { - await this.rotateAccessToken(); - - const allowedWebhookTypes = this.createWebhookTypes(events); - - const mutation = ` - mutation { - updateWebhookConfig(input: { - id: "${webhookId}", - allowedWebhookTypes: ${allowedWebhookTypes} - }) { - webhookConfig { - id - allowlist { - allowedTypes - } - } - } - } - `; - - const response = await this.client.post("", { query: mutation }); - - if (response.data.errors) { - throw Error(`${JSON.stringify(response.data.errors)}`); - } - - const webhook = response.data.data.updateWebhookConfig.webhookConfig as RutterWebhook; - - // filter out mandatory events from the list - webhook.allowlist.allowedTypes = webhook.allowlist.allowedTypes.filter((type) => !MANDATORY_EVENTS.includes(type)); - - return webhook; - } - - /** - * Drop a Rutter webhook - * @param webhookId - */ - async dropWebhook(webhookId: string): Promise { - const mutation = ` - mutation { - deleteWebhookUrl(input: {id: "${webhookId}"}){ - error { - code - message - } - } - }`; - - const response = await this.client.post("", { query: mutation }); - - if (response.data.errors) { - throw Error(`${JSON.stringify(response.data.errors)}`); - } - - return response.data.data.deleteWebhookUrl.error === null; - } - - /** - * Get the subscribed events for a Rutter webhook - * @param webhookId - */ - async getSubscribedEvents(webhookId: string) { - const webhook = await this.findWebhookById(webhookId); - - return webhook.allowlist.allowedTypes.filter((event) => !MANDATORY_EVENTS.includes(event)); - } - - /** - * Rotate the access token if it has expired. To be used before every request. - * @private - */ - private async rotateAccessToken() { - if (!this.ACCESS_TOKEN) { - await this.init(); - } else { - const decodedToken = jwtDecode(this.ACCESS_TOKEN) as RutterDecodedToken; - - if (decodedToken.exp < Date.now() / 1000) { - await this.init(); - } - } - } - - /** - * Initialize the axios client - * @private - */ - private async initializeClient() { - this.client = axios.create({ - baseURL: RutterConnection.BASE_URL, - headers: { - Authorization: `Bearer ${this.ACCESS_TOKEN}`, - "Content-Type": "application/json", - "User-Agent": "event", - }, - }); - } - - /** - * Get the access token from Rutter's GraphQL API - * @private - */ - private async getAccessToken(): Promise { - const mutation = ` - mutation { - login(email: "${this.email}", password: "${this.password}") { - token - } - } - `; - - const response = await axios.post(RutterConnection.BASE_URL, { query: mutation }); - - if (response.data.errors) { - throw Error(`${JSON.stringify(response.data.errors)}`); - } - - return response.data.data.login.token; - } - - /** - * Create the webhook types string for the GraphQL mutation - * @param events - * @private - */ - private createWebhookTypes(events: string[]) { - const eventList = Array.from(new Set([...MANDATORY_EVENTS, ...events])); - - return `[${eventList.map((event) => `"${event}"`).join(", ")}]`; - } -} diff --git a/catalog/sources/rutter/lib/types.ts b/catalog/sources/rutter/lib/types.ts deleted file mode 100644 index d722ab82..00000000 --- a/catalog/sources/rutter/lib/types.ts +++ /dev/null @@ -1,15 +0,0 @@ -export type RutterDecodedToken = { - userId: string; - iat: number; - exp: number; -} - -export type RutterWebhook = { - id: string; - url: string; - isSandbox: boolean; - disabled: boolean; - allowlist: { - allowedTypes: string[]; - } -} diff --git a/catalog/sources/rutter/rutter.ts b/catalog/sources/rutter/rutter.ts deleted file mode 100644 index 994038c4..00000000 --- a/catalog/sources/rutter/rutter.ts +++ /dev/null @@ -1,171 +0,0 @@ -import crypto from "crypto"; -import axios from "axios"; -import { - AnyObject, DeleteWebhookEndpointProps, - Events, - InitProps, - InitReturns, - IntegrationClassI, SubscribeReturns, SubscriptionProps, TestConnection, - Truthy, VerifyWebhookSignatureProps, WebhooksProps, -} from "../../../types/sourceClassDefinition"; -import RutterConnection from "./lib/connection"; - -export default class RutterIntegration implements IntegrationClassI { - id: string; - - name: string; - - private readonly RUTTER_EMAIL: string; - - private readonly RUTTER_PASSWORD: string; - - private readonly RUTTER_CLIENT_SECRET: string; - - private RUTTER_CONNECTION: RutterConnection; - - constructor({ - RUTTER_EMAIL, - RUTTER_PASSWORD, - RUTTER_CLIENT_SECRET, - }: { - RUTTER_EMAIL: string; - RUTTER_PASSWORD: string; - RUTTER_CLIENT_SECRET: string; - }) { - this.RUTTER_EMAIL = RUTTER_EMAIL; - this.RUTTER_PASSWORD = RUTTER_PASSWORD; - this.RUTTER_CLIENT_SECRET = RUTTER_CLIENT_SECRET; - - this.RUTTER_CONNECTION = new RutterConnection(RUTTER_EMAIL, RUTTER_PASSWORD); - } - - async init({ webhookUrl, events }: InitProps): Promise { - // initialize connection with Rutter - try { - this.RUTTER_CONNECTION = new RutterConnection(this.RUTTER_EMAIL, this.RUTTER_PASSWORD); - await this.RUTTER_CONNECTION.init(); - - // issue create a webhook - await this.RUTTER_CONNECTION.createWebhook({ webhookUrl, events }); - - // issue list webhooks - const webhook = await this.RUTTER_CONNECTION.findWebhookByUrl(webhookUrl); - - // return webhook - return { - webhookData: webhook, - events: webhook.allowlist.allowedTypes, - }; - } catch (e) { - if (axios.isAxiosError(e)) { - throw new Error(`Rutter API Error: ${JSON.stringify((e.response.data as any).errors)}`); - } - - throw new Error(`An unexpected error occurred: ${e.message}`); - } - } - - async verifyWebhookSignature({ request, signature }: VerifyWebhookSignatureProps): Promise { - const secret = this.RUTTER_CLIENT_SECRET; - const hash = crypto - .createHmac("sha256", secret) - .update(request.body, "utf8") - .digest("base64"); - - if (`sha256=${hash}` !== signature) { - throw new Error("Invalid signature"); - } - - return true; - } - - async subscribe({ webhookId, events }: SubscriptionProps): Promise { - try { - const webhook = await this.RUTTER_CONNECTION.updateWebhookEvents(webhookId, events); - - return { - webhook, - events: webhook.allowlist.allowedTypes, - }; - } catch (e) { - if (axios.isAxiosError(e)) { - throw new Error(`Rutter API Error: ${JSON.stringify((e.response.data as any).errors)}`); - } - - throw new Error(`An unexpected error occurred: ${e.message}`); - } - } - - async getWebhooks({ webhookId }: WebhooksProps | undefined): Promise { - try { - return this.RUTTER_CONNECTION.findWebhookById(webhookId); - } catch (e) { - if (axios.isAxiosError(e)) { - throw new Error(`Rutter API Error: ${JSON.stringify((e.response.data as any).errors)}`); - } - - throw new Error(`An unexpected error occurred: ${e.message}`); - } - } - - async unsubscribe({ webhookId, events }: SubscriptionProps): Promise<{ events: Events; webhook?: any; webhooks?: any }> { - try { - const webhook = await this.RUTTER_CONNECTION.findWebhookById(webhookId); - const newEvents = webhook.allowlist.allowedTypes.filter((event: string) => !events.includes(event)); - - const newWebhook = await this.RUTTER_CONNECTION.updateWebhookEvents(webhookId, newEvents); - - return { - webhook: newWebhook, - events: newWebhook.allowlist.allowedTypes, - }; - } catch (e) { - if (axios.isAxiosError(e)) { - throw new Error(`Rutter API Error: ${JSON.stringify((e.response.data as any).errors)}`); - } - - throw new Error(`An unexpected error occurred: ${e.message}`); - } - } - - async getSubscribedEvents({ webhookId }: WebhooksProps): Promise { - try { - return this.RUTTER_CONNECTION.getSubscribedEvents(webhookId); - } catch (e) { - if (axios.isAxiosError(e)) { - throw new Error(`Rutter API Error: ${JSON.stringify((e.response.data as any).errors)}`); - } - - throw new Error(`An unexpected error occurred: ${e.message}`); - } - } - - async deleteWebhookEndpoint({ webhookId }: DeleteWebhookEndpointProps): Promise { - try { - return this.RUTTER_CONNECTION.dropWebhook(webhookId); - } catch (e) { - if (axios.isAxiosError(e)) { - throw new Error(`Rutter API Error: ${JSON.stringify((e.response.data as any).errors)}`); - } - - throw new Error(`An unexpected error occurred: ${e.message}`); - } - } - - async testConnection(): Promise { - try { - await this.RUTTER_CONNECTION.init(); - - return { - success: true, - message: "Connection to Rutter API established", - }; - } catch (e) { - console.log(e.message); - return { - success: false, - message: `Connection to Rutter API failed: ${e.message}`, - }; - } - } -} diff --git a/package.json b/package.json index ec5922ae..83a0463b 100644 --- a/package.json +++ b/package.json @@ -64,7 +64,6 @@ "firebase": "^9.15.0", "firebase-admin": "^11.3.0", "ftp": "^0.3.10", - "jwt-decode": "^3.1.2", "knex": "^2.4.0", "lodash": "^4.17.21", "paypal-rest-sdk": "^1.8.1", From 14a995edf066c225311bc4c3e4939ba365b5d2c2 Mon Sep 17 00:00:00 2001 From: Paul Krishnamurthy Date: Fri, 31 Mar 2023 16:45:17 -0400 Subject: [PATCH 10/30] Add queues to ftp config --- catalog/sources/ftp/config.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/catalog/sources/ftp/config.json b/catalog/sources/ftp/config.json index 4a1e1bcf..9d2c1490 100644 --- a/catalog/sources/ftp/config.json +++ b/catalog/sources/ftp/config.json @@ -217,6 +217,10 @@ ], "connectionTypes": ["source", "extractor"], "testConnection": true, + "queues": [ + "FTP_DOWNLOAD_QUEUE", + "FTP_EXTRACTION_QUEUE" + ], "classifications": { "dataIngestion": { "streamTypes": ["buildable::extractor::ftp-file"], From 1b06d2b6f9f4a021e356fe861d4ca635637db124 Mon Sep 17 00:00:00 2001 From: Paul Krishnamurthy Date: Fri, 31 Mar 2023 17:29:51 -0400 Subject: [PATCH 11/30] update ftp config queues --- catalog/sources/ftp/config.json | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/catalog/sources/ftp/config.json b/catalog/sources/ftp/config.json index 9d2c1490..25094c1f 100644 --- a/catalog/sources/ftp/config.json +++ b/catalog/sources/ftp/config.json @@ -218,8 +218,14 @@ "connectionTypes": ["source", "extractor"], "testConnection": true, "queues": [ - "FTP_DOWNLOAD_QUEUE", - "FTP_EXTRACTION_QUEUE" + { + "label": "Select Download Queue", + "name": "FTP_DOWNLOAD_QUEUE" + }, + { + "label": "Select Extraction Queue", + "name": "FTP_EXTRACTION_QUEUE" + } ], "classifications": { "dataIngestion": { From 521b59211005155afa05c6b9a3c6daf3468f4cad Mon Sep 17 00:00:00 2001 From: Paul Krishnamurthy Date: Mon, 10 Apr 2023 11:04:05 -0400 Subject: [PATCH 12/30] Update FTP config.json to support conditional rendering --- catalog/sources/ftp/config.json | 67 ++++++++++++++++++++++++--------- 1 file changed, 50 insertions(+), 17 deletions(-) diff --git a/catalog/sources/ftp/config.json b/catalog/sources/ftp/config.json index 25094c1f..5712d899 100644 --- a/catalog/sources/ftp/config.json +++ b/catalog/sources/ftp/config.json @@ -73,10 +73,10 @@ ] }, { - "name": "FTP_EXTRACTOR_ENABLED", - "label": "Enable FTP File Content Extraction", + "name": "FTP_FILE_ARCHIVING_ENABLED", + "label": "Enable File Archiving", "type": "select", - "value": "Y", + "value": "N", "options": [ { "value": "Y", @@ -86,6 +86,31 @@ "value": "N", "name": "No" } + ], + "conditionallyRender": [ + "FTP_FILE_EXTRACTION_ENABLED" + ] + }, + { + "name": "FTP_FILE_EXTRACTION_ENABLED", + "label": "Enable File Content Extraction", + "type": "select", + "value": "N", + "options": [ + { + "value": "Y", + "name": "Yes" + }, + { + "value": "N", + "name": "No" + } + ], + "conditionallyRender": [ + "FTP_EXTRACTOR_FILE_TYPE", + "FTP_EXTRACTOR_FILE_PARSER", + "FTP_EXTRACTOR_RECORD_COUNT_LIMIT", + "FTP_EXTRACTOR_FILE_SIZE_LIMIT" ] }, { @@ -97,9 +122,28 @@ { "name": "CSV", "value": "csv" + }, + { + "name": "JSON", + "value": "json" } - ], - "required": false + ] + }, + { + "name": "FTP_EXTRACTOR_FILE_PARSER", + "label": "Enter the Extractor File Parser", + "type": "select", + "value": "csv", + "options": [ + { + "name": "CSV", + "value": "csv" + }, + { + "name": "JSON", + "value": "json" + } + ] }, { "name": "FTP_EXTRACTOR_RECORD_COUNT_LIMIT", @@ -119,8 +163,7 @@ "name": "1000", "value": "1000" } - ], - "required": false + ] }, { "name": "FTP_EXTRACTOR_FILE_SIZE_LIMIT", @@ -217,16 +260,6 @@ ], "connectionTypes": ["source", "extractor"], "testConnection": true, - "queues": [ - { - "label": "Select Download Queue", - "name": "FTP_DOWNLOAD_QUEUE" - }, - { - "label": "Select Extraction Queue", - "name": "FTP_EXTRACTION_QUEUE" - } - ], "classifications": { "dataIngestion": { "streamTypes": ["buildable::extractor::ftp-file"], From d67612f18d971794bde5cf6544e9b141ecba7292 Mon Sep 17 00:00:00 2001 From: Paul Krishnamurthy Date: Mon, 10 Apr 2023 12:25:08 -0400 Subject: [PATCH 13/30] Update BigQuery destination tags --- catalog/destinations/bigquery/config.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/catalog/destinations/bigquery/config.json b/catalog/destinations/bigquery/config.json index deed102b..a3c58e91 100644 --- a/catalog/destinations/bigquery/config.json +++ b/catalog/destinations/bigquery/config.json @@ -5,7 +5,7 @@ "type": "bigquery", "category": "database", "image": "https://assets.buildable.dev/catalog/node-templates/bigquery.svg", - "tags": ["database", "db", "warehouse", "analytics", "olap"], + "tags": ["database", "warehouse"], "authentication": [ { "name": "GOOGLE_SERVICE_ACCOUNT_KEY", From 32c83aa93442dbce90dbfc1307c8fbf4d4dc59f5 Mon Sep 17 00:00:00 2001 From: Paul Krishnamurthy Date: Tue, 11 Apr 2023 16:44:01 -0400 Subject: [PATCH 14/30] hide ftp conditional form items --- catalog/sources/ftp/config.json | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/catalog/sources/ftp/config.json b/catalog/sources/ftp/config.json index 5712d899..4812bdb5 100644 --- a/catalog/sources/ftp/config.json +++ b/catalog/sources/ftp/config.json @@ -76,7 +76,7 @@ "name": "FTP_FILE_ARCHIVING_ENABLED", "label": "Enable File Archiving", "type": "select", - "value": "N", + "value": "Y", "options": [ { "value": "Y", @@ -89,13 +89,14 @@ ], "conditionallyRender": [ "FTP_FILE_EXTRACTION_ENABLED" - ] + ], + "hidden": true }, { "name": "FTP_FILE_EXTRACTION_ENABLED", "label": "Enable File Content Extraction", "type": "select", - "value": "N", + "value": "Y", "options": [ { "value": "Y", @@ -111,7 +112,8 @@ "FTP_EXTRACTOR_FILE_PARSER", "FTP_EXTRACTOR_RECORD_COUNT_LIMIT", "FTP_EXTRACTOR_FILE_SIZE_LIMIT" - ] + ], + "hidden": true }, { "name": "FTP_EXTRACTOR_FILE_TYPE", From c8d67c7e35fe9fce678ca169c0d18d035e5a4180 Mon Sep 17 00:00:00 2001 From: Paul Krishnamurthy Date: Tue, 11 Apr 2023 17:08:23 -0400 Subject: [PATCH 15/30] update FTP form "Enter" to "Select" --- catalog/sources/ftp/config.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/catalog/sources/ftp/config.json b/catalog/sources/ftp/config.json index 4812bdb5..b84644d3 100644 --- a/catalog/sources/ftp/config.json +++ b/catalog/sources/ftp/config.json @@ -117,7 +117,7 @@ }, { "name": "FTP_EXTRACTOR_FILE_TYPE", - "label": "Enter the Extractor File Type", + "label": "Select the Extractor File Type", "type": "select", "value": "csv", "options": [ @@ -133,7 +133,7 @@ }, { "name": "FTP_EXTRACTOR_FILE_PARSER", - "label": "Enter the Extractor File Parser", + "label": "Select the Extractor File Parser", "type": "select", "value": "csv", "options": [ From 1d43c536212cd6d23ccf25e75ab4e6da69d33cd1 Mon Sep 17 00:00:00 2001 From: Paul Krishnamurthy Date: Fri, 14 Apr 2023 12:16:59 -0400 Subject: [PATCH 16/30] add no-param-reassign rule to tsconfig --- .eslintrc.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.eslintrc.json b/.eslintrc.json index cd51b75e..ecfa0644 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -47,7 +47,7 @@ "class-methods-use-this": ["off"], "consistent-return": ["off"], "newIsCap": ["off"], - + "no-param-reassign": ["off"], "import/prefer-default-export": ["off"], "import/extensions": ["off"] } From 6beda5759c196ac9e822e05e886d7b6c46d011b6 Mon Sep 17 00:00:00 2001 From: Paul Krishnamurthy Date: Fri, 14 Apr 2023 12:18:47 -0400 Subject: [PATCH 17/30] add JSON and CSV parsers to FTP Source --- catalog/sources/ftp/parsers/csv/config.json | 4 ++++ catalog/sources/ftp/parsers/csv/parser.ts | 20 ++++++++++++++++++++ catalog/sources/ftp/parsers/json/config.json | 4 ++++ catalog/sources/ftp/parsers/json/parser.ts | 9 +++++++++ 4 files changed, 37 insertions(+) create mode 100644 catalog/sources/ftp/parsers/csv/config.json create mode 100644 catalog/sources/ftp/parsers/csv/parser.ts create mode 100644 catalog/sources/ftp/parsers/json/config.json create mode 100644 catalog/sources/ftp/parsers/json/parser.ts diff --git a/catalog/sources/ftp/parsers/csv/config.json b/catalog/sources/ftp/parsers/csv/config.json new file mode 100644 index 00000000..8f6a760a --- /dev/null +++ b/catalog/sources/ftp/parsers/csv/config.json @@ -0,0 +1,4 @@ +{ + "type": "csv", + "language": "typescript" +} \ No newline at end of file diff --git a/catalog/sources/ftp/parsers/csv/parser.ts b/catalog/sources/ftp/parsers/csv/parser.ts new file mode 100644 index 00000000..2d11d20d --- /dev/null +++ b/catalog/sources/ftp/parsers/csv/parser.ts @@ -0,0 +1,20 @@ +const parseCSV = (payload: string): { + [key: string]: string +}[] => { + const [headerLine, ...dataLines] = payload.split("\n"); + + const delimiter = ","; + const headers = headerLine.split(delimiter); + + return dataLines.map((line) => { + const cells = line.split(delimiter); + + return headers.reduce((obj: { + [key: string]: string + }, header, index) => { + obj[header] = cells[index]; + + return obj; + }, {}); + }); +}; diff --git a/catalog/sources/ftp/parsers/json/config.json b/catalog/sources/ftp/parsers/json/config.json new file mode 100644 index 00000000..8f645b52 --- /dev/null +++ b/catalog/sources/ftp/parsers/json/config.json @@ -0,0 +1,4 @@ +{ + "type": "json", + "language": "typescript" +} \ No newline at end of file diff --git a/catalog/sources/ftp/parsers/json/parser.ts b/catalog/sources/ftp/parsers/json/parser.ts new file mode 100644 index 00000000..7cb76e8a --- /dev/null +++ b/catalog/sources/ftp/parsers/json/parser.ts @@ -0,0 +1,9 @@ +const parseJSON = (payload: string): { + [key: string]: string +}[] => { + try { + return JSON.parse(payload); + } catch (e) { + return []; + } +}; From 568ca74514b03cf4a0c3ff732f9f01592ccfed1e Mon Sep 17 00:00:00 2001 From: Paul Krishnamurthy Date: Sun, 16 Apr 2023 21:38:10 -0400 Subject: [PATCH 18/30] Add FTP Parsers --- catalog/sources/ftp/config.json | 9 +++++---- catalog/sources/ftp/docs/connect.md | 6 ++++++ catalog/sources/ftp/parsers/csv/parser.ts | 6 +++--- catalog/sources/ftp/parsers/json/parser.ts | 15 ++++++++++----- 4 files changed, 24 insertions(+), 12 deletions(-) diff --git a/catalog/sources/ftp/config.json b/catalog/sources/ftp/config.json index b84644d3..b903bb85 100644 --- a/catalog/sources/ftp/config.json +++ b/catalog/sources/ftp/config.json @@ -135,15 +135,15 @@ "name": "FTP_EXTRACTOR_FILE_PARSER", "label": "Select the Extractor File Parser", "type": "select", - "value": "csv", + "value": "function parseCSV (payload) { const [headerLine, ...dataLines] = payload.split('\\n'); const delimiter = ','; const headers = headerLine.split(delimiter); return dataLines.map((line) => { const cells = line.split(delimiter); return headers.reduce((obj, header, index) => { obj[header] = cells[index]; return obj; }, {}); }); };", "options": [ { "name": "CSV", - "value": "csv" + "value": "function parseCSV (payload) { const [headerLine, ...dataLines] = payload.split('\\n'); const delimiter = ','; const headers = headerLine.split(delimiter); return dataLines.map((line) => { const cells = line.split(delimiter); return headers.reduce((obj, header, index) => { obj[header] = cells[index]; return obj; }, {}); }); };" }, { "name": "JSON", - "value": "json" + "value": "function parseJSON (payload) { try { const parsedPayload = JSON.parse(payload); return Object.entries(parsedPayload).map(([key, value]) => ({ key, value })); } catch (e) { return []; } };" } ] }, @@ -182,7 +182,8 @@ "createBuildableEnvVars": false, "autoSubscribeAllEvents": true, "showEvents": true, - "secureAuthFields": true + "secureAuthFields": true, + "parser": "FTP_EXTRACTOR_FILE_PARSER" }, "paths": { "id": null, diff --git a/catalog/sources/ftp/docs/connect.md b/catalog/sources/ftp/docs/connect.md index def26c60..100eb136 100644 --- a/catalog/sources/ftp/docs/connect.md +++ b/catalog/sources/ftp/docs/connect.md @@ -6,6 +6,12 @@ Receive file manipulation events on an FTP server and extract relevant informati Rest assured, your credentials are securely encrypted to keep your information safe. +### File Parsers + +FTP parsers are [open-source functions](https://github.com/buildable/connections/tree/main/catalog/sources/ftp/parsers) that facilitate the extraction of archived file contents, tailored to their respective file types. + +To create and use custom parsers, you'll need to use the `@event-inc/connections` [NPM package](https://www.npmjs.com/package/@event-inc/connections). + ### Need Help? Start a conversation in our [Discord Server](https://discord.com/invite/47AJ42Wzys) or send an email to [support@buildable.dev](mailto:https://discord.com/invite/47AJ42Wzys). Our Data Engineers are available 24/7 to help if you ever get stuck. \ No newline at end of file diff --git a/catalog/sources/ftp/parsers/csv/parser.ts b/catalog/sources/ftp/parsers/csv/parser.ts index 2d11d20d..408bd610 100644 --- a/catalog/sources/ftp/parsers/csv/parser.ts +++ b/catalog/sources/ftp/parsers/csv/parser.ts @@ -1,6 +1,6 @@ -const parseCSV = (payload: string): { +function parseCSV(payload: string): { [key: string]: string -}[] => { +}[] { const [headerLine, ...dataLines] = payload.split("\n"); const delimiter = ","; @@ -17,4 +17,4 @@ const parseCSV = (payload: string): { return obj; }, {}); }); -}; +} diff --git a/catalog/sources/ftp/parsers/json/parser.ts b/catalog/sources/ftp/parsers/json/parser.ts index 7cb76e8a..3b79737b 100644 --- a/catalog/sources/ftp/parsers/json/parser.ts +++ b/catalog/sources/ftp/parsers/json/parser.ts @@ -1,9 +1,14 @@ -const parseJSON = (payload: string): { - [key: string]: string -}[] => { +function parseJSON(payload: string): { + [key: string]: unknown +}[] { try { - return JSON.parse(payload); + const parsedPayload = JSON.parse(payload); + + return Object.entries(parsedPayload).map(([key, value]) => ({ + key, + value, + })); } catch (e) { return []; } -}; +} From 1c41b46b0aa1f17543e448a40af7ca871f723ff4 Mon Sep 17 00:00:00 2001 From: Houssem Eddine Zerrad Date: Wed, 19 Apr 2023 22:09:55 +0100 Subject: [PATCH 19/30] initial Kafka Destination commit --- catalog/destinations/kafka/CHANGELOG.md | 12 + catalog/destinations/kafka/config.json | 37 +++ catalog/destinations/kafka/docs/connect.md | 23 ++ catalog/destinations/kafka/docs/setup.md | 15 + .../docs/transformations/actions/pushData.md | 36 +++ .../kafka/docs/transformations/footer.md | 3 + .../kafka/docs/transformations/header.md | 16 + catalog/destinations/kafka/index.ts | 13 + catalog/destinations/kafka/kafka.ts | 155 ++++++++++ catalog/destinations/kafka/lib/types.ts | 6 + catalog/destinations/kafka/test/index.spec.ts | 283 ++++++++++++++++++ catalog/destinations/package-lock.json | 14 + catalog/destinations/package.json | 1 + 13 files changed, 614 insertions(+) create mode 100644 catalog/destinations/kafka/CHANGELOG.md create mode 100644 catalog/destinations/kafka/config.json create mode 100644 catalog/destinations/kafka/docs/connect.md create mode 100644 catalog/destinations/kafka/docs/setup.md create mode 100644 catalog/destinations/kafka/docs/transformations/actions/pushData.md create mode 100644 catalog/destinations/kafka/docs/transformations/footer.md create mode 100644 catalog/destinations/kafka/docs/transformations/header.md create mode 100644 catalog/destinations/kafka/index.ts create mode 100644 catalog/destinations/kafka/kafka.ts create mode 100644 catalog/destinations/kafka/lib/types.ts create mode 100644 catalog/destinations/kafka/test/index.spec.ts diff --git a/catalog/destinations/kafka/CHANGELOG.md b/catalog/destinations/kafka/CHANGELOG.md new file mode 100644 index 00000000..a9d687a5 --- /dev/null +++ b/catalog/destinations/kafka/CHANGELOG.md @@ -0,0 +1,12 @@ +# 📣 Change Log +All notable changes to the `Kafka` Destination Connection will be documented in this file. + +The format followed is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). + +--- + +## [1.0.0] - 2023-04-18 + +⚡️ Initial Version + +--- diff --git a/catalog/destinations/kafka/config.json b/catalog/destinations/kafka/config.json new file mode 100644 index 00000000..17296857 --- /dev/null +++ b/catalog/destinations/kafka/config.json @@ -0,0 +1,37 @@ +{ + "title": "Kafka", + "description": "Kafka is an open-source streaming platform used for building real-time data pipelines and streaming applications.", + "apiVersion": "NA", + "type": "kafka", + "category": "streaming", + "image": "https://assets.buildable.dev/catalog/node-templates/kafka.svg", + "tags": ["streaming"], + "authentication": [ + { + "name": "KAFKA_BROKER_URLS", + "label": "Enter your Kafka Broker Nodes URLs, separated by comma (,)", + "placeholder": "kafka-broker1.dev:9092,kafka-broker2.dev:9092,kafka-broker3.dev:9092" + }, + { + "name": "KAFKA_USERNAME", + "label": "Enter your Kafka Username", + "placeholder": "myusername" + }, + { + "name": "KAFKA_PASSWORD", + "label": "Enter your Kafka Password", + "placeholder": "mypassword" + } + ], + "eventSchema": {}, + "settings": { + "createBuildableEnvVars": true, + "hasEvents": false, + "showEvents": false + }, + "paths": null, + "events": [], + "connectionTypes": ["target"], + "actions": ["pushData"], + "destinationType": "http" +} diff --git a/catalog/destinations/kafka/docs/connect.md b/catalog/destinations/kafka/docs/connect.md new file mode 100644 index 00000000..869f904c --- /dev/null +++ b/catalog/destinations/kafka/docs/connect.md @@ -0,0 +1,23 @@ +## Connect + +BigQuery is a fully-managed, serverless data warehouse provided by Google Cloud. It allows you to store, manage, and analyze large amounts of data with ease and at a low cost. BigQuery supports both structured and semi-structured data and can handle terabyte-scale datasets with fast query performance. + +### IP Whitelisting + +To enable Buildable to connect to your database, please ensure that your firewall accepts incoming requests from the following IP addresses: + +`35.245.232.82` `35.245.100.81` + +`34.120.49.202` `34.160.218.232` + +`3.12.101.201` `3.129.238.32` + +`3.13.190.25` `3.133.23.83` + +### Securely Encrypted + +Rest assured, your credentials are securely encrypted to keep your information safe. + +### Need Help? + +Start a conversation in our [Discord Server](https://discord.com/invite/47AJ42Wzys) or send an email to [support@buildable.dev](mailto:https://discord.com/invite/47AJ42Wzys). Our Data Engineers are available 24/7 to help if you ever get stuck. diff --git a/catalog/destinations/kafka/docs/setup.md b/catalog/destinations/kafka/docs/setup.md new file mode 100644 index 00000000..417ba0b1 --- /dev/null +++ b/catalog/destinations/kafka/docs/setup.md @@ -0,0 +1,15 @@ +## BigQuery Destination Setup + +Check out our [quick start guide](https://docs.buildable.dev/) to learn how to get started. + +### IP Whitelisting + +To enable Buildable to connect to your database, please ensure that your firewall accepts incoming requests from the following IP addresses: + +`35.245.232.82` `35.245.100.81` + +`34.120.49.202` `34.160.218.232` + +`3.12.101.201` `3.129.238.32` + +`3.13.190.25` `3.133.23.83` \ No newline at end of file diff --git a/catalog/destinations/kafka/docs/transformations/actions/pushData.md b/catalog/destinations/kafka/docs/transformations/actions/pushData.md new file mode 100644 index 00000000..4dbc4e77 --- /dev/null +++ b/catalog/destinations/kafka/docs/transformations/actions/pushData.md @@ -0,0 +1,36 @@ +### Insert Data + +Allows to insert row(s) into a BigQuery table + +[Documentation](https://cloud.google.com/bigquery/docs/reference/standard-sql/dml-syntax) + +**Types** + +```typescript +interface IKafkaPushData { + topic: string; + data: string | string[] | Buffer | Buffer[] | AnyObject | AnyObject[]; +} +``` + +**Sample Payload** +```json +{ + "topic": "topic_0", + "data": [{ "id": 1, "name": "John Doe" }] +} +``` + +**Sample Response** +```json +[ + { + "topicName": "topic_0", + "partition": 3, + "errorCode": 0, + "baseOffset": "2", + "logAppendTime": "-1", + "logStartOffset": "0" + } +] +``` diff --git a/catalog/destinations/kafka/docs/transformations/footer.md b/catalog/destinations/kafka/docs/transformations/footer.md new file mode 100644 index 00000000..203ae528 --- /dev/null +++ b/catalog/destinations/kafka/docs/transformations/footer.md @@ -0,0 +1,3 @@ +### Need Help? + +Start a conversation in our [Discord Server](https://discord.com/invite/47AJ42Wzys) or send an email to [support@buildable.dev](mailto:https://discord.com/invite/47AJ42Wzys). Our Data Engineers are available 24/7 to help if you ever get stuck. \ No newline at end of file diff --git a/catalog/destinations/kafka/docs/transformations/header.md b/catalog/destinations/kafka/docs/transformations/header.md new file mode 100644 index 00000000..dae23af0 --- /dev/null +++ b/catalog/destinations/kafka/docs/transformations/header.md @@ -0,0 +1,16 @@ +## Documentation +---- + +BigQuery is a fully-managed, serverless data warehouse provided by Google Cloud. It allows you to store, manage, and analyze large amounts of data with ease and at a low cost. BigQuery supports both structured and semi-structured data and can handle terabyte-scale datasets with fast query performance. + +### IP Whitelisting + +To enable Buildable to connect to your database, please ensure that your firewall accepts incoming requests from the following IP addresses: + +`35.245.232.82` `35.245.100.81` + +`34.120.49.202` `34.160.218.232` + +`3.12.101.201` `3.129.238.32` + +`3.13.190.25` `3.133.23.83` diff --git a/catalog/destinations/kafka/index.ts b/catalog/destinations/kafka/index.ts new file mode 100644 index 00000000..34dfc0c7 --- /dev/null +++ b/catalog/destinations/kafka/index.ts @@ -0,0 +1,13 @@ +import getProxyDriver from "./kafka"; + +export async function main({ payload, config, action }) { + try { + const driver = getProxyDriver(config); + + const data = await driver[action](payload); + + return { data, status: 200 }; + } catch (error) { + throw error; + } +} diff --git a/catalog/destinations/kafka/kafka.ts b/catalog/destinations/kafka/kafka.ts new file mode 100644 index 00000000..366e4a02 --- /dev/null +++ b/catalog/destinations/kafka/kafka.ts @@ -0,0 +1,155 @@ +import { Kafka, KafkaConfig, Producer } from "kafkajs"; + +import { AnyObject, DestinationClassI, TestConnection, Truthy } from "../../../types/destinationClassDefinition"; +import { IKafkaPushData } from "./lib/types"; + +export class KafkaDriver implements DestinationClassI { + public client: Kafka = null; + + public producer: Producer = null; + + public readonly KAFKA_BROKER_URLS: string; + + public readonly KAFKA_USERNAME: string; + + public readonly KAFKA_PASSWORD: string; + + constructor({ KAFKA_BROKER_URLS, KAFKA_USERNAME, KAFKA_PASSWORD }: AnyObject) { + this.KAFKA_BROKER_URLS = KAFKA_BROKER_URLS; + this.KAFKA_USERNAME = KAFKA_USERNAME; + this.KAFKA_PASSWORD = KAFKA_PASSWORD; + } + + async connect(config?: AnyObject): Promise { + const { KAFKA_BROKER_URLS, KAFKA_USERNAME, KAFKA_PASSWORD } = config || this; + + this.client = new Kafka({ + clientId: "buildable", + brokers: KAFKA_BROKER_URLS.split(",").map((url) => url.trim()), + ssl: true, + sasl: { + mechanism: "plain", + username: KAFKA_USERNAME, + password: KAFKA_PASSWORD, + }, + logLevel: 1, + connectionTimeout: 5000, + }); + + this.producer = this.client.producer(); + await this.producer.connect(); + } + + async disconnect(): Promise { + if (this.producer) { + await this.producer.disconnect(); + this.producer = null; + } + + if (this.client) { + this.client = null; + } + } + + async testConnection(): Promise { + // If the client is not yet initialized, initialize it + if (!this.client) { + this.client = new Kafka({ + clientId: "buildable", + brokers: this.KAFKA_BROKER_URLS.split(",").map((url) => url.trim()), + ssl: true, + sasl: { + mechanism: "plain", + username: this.KAFKA_USERNAME, + password: this.KAFKA_PASSWORD, + }, + logLevel: 1, + connectionTimeout: 5000, + }); + } + + // Test the connection + try { + const admin = this.client.admin(); + await admin.connect(); + await admin.disconnect(); + + return { + success: true, + message: "Connection established successfully", + }; + } catch (e) { + return { + success: false, + message: `Kafka connection failed: ${e.message}`, + }; + } + } + + /** + * Push data to Kafka + * @param topic - Kafka topic + * @param data - Data to push to Kafka + */ + async pushData({ topic, data }: IKafkaPushData) { + return this.producer.send({ + topic, + messages: [{ value: JSON.stringify(data) }], + }); + } +} + +export default function getProxyDriver(config: AnyObject) { + const driver = new KafkaDriver(config); + + return new Proxy(driver, { + get: (target, prop) => { + // return the client + if (prop === "client") { + return driver.client; + } + + // return the producer + if (prop === "producer") { + return driver.producer; + } + + if (typeof driver[prop] === "function") { + if (prop === "testConnection") { + return async () => driver.testConnection(); + } + + // Force the proxy to return a Promise that only resolves once the connection has been established + if (prop === "connect") { + return async () => { + await driver.connect(config); + }; + } + + // Force the proxy to return a Promise that only resolves once the connection has been dropped + if (prop === "disconnect") { + return async () => { + await driver.disconnect(); + }; + } + + return async (payload) => { + try { + await driver.connect(config); + + const result = await target[prop](payload); + + await driver.disconnect(); + + return result; + } catch (err) { + console.log("Error occurred ===> ", err); + throw err; + } + }; + } + + throw new Error(`Method ${prop as string}() not found`); + }, + }); +} diff --git a/catalog/destinations/kafka/lib/types.ts b/catalog/destinations/kafka/lib/types.ts new file mode 100644 index 00000000..d17681b5 --- /dev/null +++ b/catalog/destinations/kafka/lib/types.ts @@ -0,0 +1,6 @@ +import { AnyObject } from "../../../../types/destinationClassDefinition"; + +export interface IKafkaPushData { + topic: string; + data: string | string[] | Buffer | Buffer[] | AnyObject | AnyObject[]; +} diff --git a/catalog/destinations/kafka/test/index.spec.ts b/catalog/destinations/kafka/test/index.spec.ts new file mode 100644 index 00000000..e1bc8ab5 --- /dev/null +++ b/catalog/destinations/kafka/test/index.spec.ts @@ -0,0 +1,283 @@ +import crypto from "crypto"; +import { Admin } from "kafkajs"; +import getProxyDriver, { KafkaDriver } from "../kafka"; + +describe("Test: Kafka Destination", () => { + let driver: KafkaDriver | null = null; + + it("should reject unknown methods from the proxy object", async () => { + driver = getProxyDriver( + { + KAFKA_BROKER_URLS: process.env.KAFKA_BROKER_URLS, + KAFKA_USERNAME: process.env.KAFKA_USERNAME, + KAFKA_PASSWORD: process.env.KAFKA_PASSWORD, + }, + ); + + try { + (driver as any).unknownMethod(); + expect(false).toBe(true); + } catch (err) { + expect(err.message).toEqual("Method unknownMethod() not found"); + } + }); + + it("should log and raise an error from within the proxy object", async () => { + driver = getProxyDriver( + { + KAFKA_BROKER_URLS: process.env.KAFKA_BROKER_URLS, + KAFKA_USERNAME: process.env.KAFKA_USERNAME, + KAFKA_PASSWORD: process.env.KAFKA_PASSWORD, + }, + ); + + driver.pushData = jest.fn().mockImplementation(() => Promise.reject(new Error("Mocked error"))); + + expect(driver.producer).toBeNull(); + + await expect(driver.pushData({ topic: "topic_0", data: "my data" })).rejects.toThrow("Mocked error"); + + jest.resetAllMocks(); + }); + + describe("connect", () => { + beforeEach(() => { + driver = getProxyDriver( + { + KAFKA_BROKER_URLS: process.env.KAFKA_BROKER_URLS, + KAFKA_USERNAME: process.env.KAFKA_USERNAME, + KAFKA_PASSWORD: process.env.KAFKA_PASSWORD, + }, + ); + }); + + afterEach(() => { + driver = null; + }); + + it("should connect to Kafka", async () => { + return driver.connect().then(async () => { + const result = await driver.testConnection(); + + expect(result.success).toBeTruthy(); + }); + }); + it("should accept config passed as arguments", async () => { + return driver.connect({ KAFKA_BROKER_URLS: process.env.KAFKA_BROKER_URLS, + KAFKA_USERNAME: process.env.KAFKA_USERNAME, + KAFKA_PASSWORD: process.env.KAFKA_PASSWORD }).then(async () => { + const result = await driver.testConnection(); + + expect(result.success).toBeTruthy(); + }); + }); + }); + + describe("disconnect", () => { + beforeEach(() => { + driver = getProxyDriver( + { + KAFKA_BROKER_URLS: process.env.KAFKA_BROKER_URLS, + KAFKA_USERNAME: process.env.KAFKA_USERNAME, + KAFKA_PASSWORD: process.env.KAFKA_PASSWORD, + }, + ); + + driver.connect(); + }); + + afterEach(() => { + driver = null; + }); + + it("should disconnect from Kafka", async () => { + await driver.disconnect(); + + await expect(driver.client).toBeNull(); + }); + }); + + describe("testConnection", () => { + beforeEach(() => { + driver = getProxyDriver( + { + KAFKA_BROKER_URLS: process.env.KAFKA_BROKER_URLS, + KAFKA_USERNAME: process.env.KAFKA_USERNAME, + KAFKA_PASSWORD: process.env.KAFKA_PASSWORD, + }, + ); + }); + + afterEach(() => { + driver = null; + }); + + it("should return success=true and a message when successful", async () => { + await driver.connect(); + const result = await driver.testConnection(); + + expect(result).toEqual({ + success: true, + message: "Connection established successfully", + }); + }); + + it("should return success=false and a message when failed", async () => { + await driver.connect(); + + const adminSpy = jest.spyOn(driver.client, "admin"); + adminSpy.mockImplementation((_config?) => { + return { + connect: () => Promise.reject(new Error("Mocked error")), + } as Admin; + }); + + const result = await driver.testConnection(); + + expect(result).toEqual({ + success: false, + message: "Kafka connection failed: Mocked error", + }); + + adminSpy.mockRestore(); + }); + + it("should reconstruct the client when it is not initialized", async () => { + const result = await driver.testConnection(); // this should issue connect() internally + + expect(result).toEqual({ + success: true, + message: "Connection established successfully", + }); + }); + }); + + describe("pushData", () => { + const topic = "topic_0"; + + beforeEach(() => { + driver = getProxyDriver( + { + KAFKA_BROKER_URLS: process.env.KAFKA_BROKER_URLS, + KAFKA_USERNAME: process.env.KAFKA_USERNAME, + KAFKA_PASSWORD: process.env.KAFKA_PASSWORD, + }, + ); + + driver.connect(); + }); + + afterEach(() => { + driver.disconnect(); + driver = null; + }); + + it("should push string data to a Kafka topic", async () => { + await driver.connect(); + + const data = crypto.randomBytes(20).toString("hex"); + + const result = await driver.pushData({ topic, data }); + + await driver.disconnect(); + + for (const r of result) { + expect(r.errorCode).toEqual(0); + } + }); + + it("should push string array data to a Kafka topic", async () => { + await driver.connect(); + + const data = [ + crypto.randomBytes(20).toString("hex"), + crypto.randomBytes(20).toString("hex"), + crypto.randomBytes(20).toString("hex"), + ]; + + const result = await driver.pushData({ topic, data }); + + await driver.disconnect(); + + for (const r of result) { + expect(r.errorCode).toEqual(0); + } + }); + + it("should push buffer data to a Kafka topic", async () => { + await driver.connect(); + + const data = crypto.randomBytes(20); + + const result = await driver.pushData({ topic, data }); + + await driver.disconnect(); + + for (const r of result) { + expect(r.errorCode).toEqual(0); + } + }); + + it("should push buffer array data to a Kafka topic", async () => { + await driver.connect(); + + const data = [ + crypto.randomBytes(20), + crypto.randomBytes(20), + crypto.randomBytes(20), + ]; + + const result = await driver.pushData({ topic, data }); + + await driver.disconnect(); + + for (const r of result) { + expect(r.errorCode).toEqual(0); + } + }); + + it("should push object data to a Kafka topic", async () => { + await driver.connect(); + + const data = { + foo: "bar", + bar: "baz", + }; + + const result = await driver.pushData({ topic, data }); + + await driver.disconnect(); + + for (const r of result) { + expect(r.errorCode).toEqual(0); + } + }); + + it("should push object array data to a Kafka topic", async () => { + await driver.connect(); + + const data = [ + { + foo: "bar", + bar: "baz", + }, + { + foo: "baz", + bar: "foo", + }, + { + foo: "foo", + bar: "bar", + }, + ]; + + const result = await driver.pushData({ topic, data }); + + await driver.disconnect(); + + for (const r of result) { + expect(r.errorCode).toEqual(0); + } + }); + }); +}); diff --git a/catalog/destinations/package-lock.json b/catalog/destinations/package-lock.json index 0c8b473d..a642aca7 100644 --- a/catalog/destinations/package-lock.json +++ b/catalog/destinations/package-lock.json @@ -15,6 +15,7 @@ "dayjs": "^1.11.7", "firebase": "^9.15.0", "firebase-admin": "^11.3.0", + "kafkajs": "^2.2.4", "knex": "^2.3.0", "mongodb": "^4.11.0", "pg": "^8.8.0", @@ -3389,6 +3390,14 @@ "safe-buffer": "^5.0.1" } }, + "node_modules/kafkajs": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/kafkajs/-/kafkajs-2.2.4.tgz", + "integrity": "sha512-j/YeapB1vfPT2iOIUn/vxdyKEuhuY2PxMBvf5JWux6iSaukAccrMtXEY/Lb7OvavDhOWME589bpLrEdnVHjfjA==", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/klaw": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/klaw/-/klaw-3.0.0.tgz", @@ -7411,6 +7420,11 @@ "safe-buffer": "^5.0.1" } }, + "kafkajs": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/kafkajs/-/kafkajs-2.2.4.tgz", + "integrity": "sha512-j/YeapB1vfPT2iOIUn/vxdyKEuhuY2PxMBvf5JWux6iSaukAccrMtXEY/Lb7OvavDhOWME589bpLrEdnVHjfjA==" + }, "klaw": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/klaw/-/klaw-3.0.0.tgz", diff --git a/catalog/destinations/package.json b/catalog/destinations/package.json index 7b56303a..9816c951 100644 --- a/catalog/destinations/package.json +++ b/catalog/destinations/package.json @@ -9,6 +9,7 @@ "dayjs": "^1.11.7", "firebase": "^9.15.0", "firebase-admin": "^11.3.0", + "kafkajs": "^2.2.4", "knex": "^2.3.0", "mongodb": "^4.11.0", "pg": "^8.8.0", From efb59f4447c75471ef44a182d26b664a128a71e3 Mon Sep 17 00:00:00 2001 From: Paul Krishnamurthy Date: Fri, 21 Apr 2023 16:19:31 -0400 Subject: [PATCH 20/30] Rutter webhook source --- catalog/sources/rutter/CHANGELOG.md | 12 ++ catalog/sources/rutter/config.json | 195 +++++++++++++++++++++++++ catalog/sources/rutter/docs/connect.md | 11 ++ catalog/sources/rutter/docs/keys.json | 7 + catalog/sources/rutter/docs/setup.md | 5 + catalog/sources/rutter/rutter.ts | 48 ++++++ 6 files changed, 278 insertions(+) create mode 100644 catalog/sources/rutter/CHANGELOG.md create mode 100644 catalog/sources/rutter/config.json create mode 100644 catalog/sources/rutter/docs/connect.md create mode 100644 catalog/sources/rutter/docs/keys.json create mode 100644 catalog/sources/rutter/docs/setup.md create mode 100644 catalog/sources/rutter/rutter.ts diff --git a/catalog/sources/rutter/CHANGELOG.md b/catalog/sources/rutter/CHANGELOG.md new file mode 100644 index 00000000..3f116a7b --- /dev/null +++ b/catalog/sources/rutter/CHANGELOG.md @@ -0,0 +1,12 @@ +# 📣 Change Log +All notable changes to the `Rutter` Connection will be documented in this file. + +The format followed is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). + +--- + +## [1.0.0] - 2022-08-02 + +⚡️ Initial Version + +--- diff --git a/catalog/sources/rutter/config.json b/catalog/sources/rutter/config.json new file mode 100644 index 00000000..79070635 --- /dev/null +++ b/catalog/sources/rutter/config.json @@ -0,0 +1,195 @@ +{ + "title": "Rutter", + "description": "The Common Language for Business Data", + "apiVersion": "2023-02-07", + "type": "rutter", + "category": "data", + "image": "https://assets.buildable.dev/catalog/node-templates/rutter.svg", + "tags": ["commerce", "accounting", "payment"], + "authentication": [], + "eventSchema": {}, + "settings": { + "createBuildableEnvVars": false, + "showUrl": true, + "autoSubscribeAllEvents": true, + "showEvents": true, + "secureAuthFields": false + }, + "paths": { + "id": null, + "event": "_.body.code", + "payload": "_.body", + "secret": null, + "signature": null + }, + "events": [ + { + "name": "INITIAL_UPDATE", + "description": "This webhook fires after an initial data download for a Connection has been completed. You can now send requests to Rutter to fetch merchant data.", + "group": "connection" + }, + { + "name": "HISTORICAL_UPDATE", + "description": "HISTORICAL_UPDATE will only fire if you have data batching enabled.", + "group": "connection" + }, + { + "name": "CONNECTION_UPDATED", + "description": "This webhook fires after a merchant re-authenticates an existing Connection, or after completing authentication for a created connection.", + "group": "connection" + }, + { + "name": "CONNECTION_NEEDS_UPDATE", + "description": "This webhook fires after a merchant unauthorizes/uninstalls an existing Connection, or if the authentication fails for any other reason.", + "group": "connection" + }, + { + "name": "CONNECTION_DISABLED", + "description": "This webhook fires after Rutter has encountered a problem with a connection and has temporarily disabled access to it.", + "group": "connection" + }, + { + "name": "CONNECTION_LINK_ERROR", + "description": "This webhook fires after an error occurs during the authentication process.", + "group": "connection" + }, + { + "name": "CONNECTION_ERROR", + "description": "This webhook after an error occurs for a connection.", + "group": "connection" + }, + { + "name": "ORDER_FULFILLED", + "description": "", + "group": "" + }, + { + "name": "ORDER_DELETED", + "description": "Fired after an order has been deleted.", + "group": "order" + }, + { + "name": "ORDER_UPDATED", + "description": "Fired after an order has been updated for a connection.", + "group": "order" + }, + { + "name": "ORDER_CREATED", + "description": "Fired after a new order has been created for a connection.", + "group": "order" + }, + { + "name": "PRODUCT_CREATED", + "description": "Fired after a product has been created.", + "group": "product" + }, + { + "name": "PRODUCT_UPDATED", + "description": "Fired after a product has been updated.", + "group": "product" + }, + { + "name": "PRODUCT_DELETED", + "description": "Fired after a product has been deleted. This is available only to Shopify, WooCommerce, and Wix.", + "group": "product" + }, + { + "name": "PRODUCT_READY", + "description": "A PRODUCT_READY webhook will be fired when the job (POST /product) has completed.", + "group": "product" + }, + { + "name": "PRODUCT_FINISH_FAILED", + "description": "In the platforms where image upload and/or variant create run asynchronously after the initial POST /products request, we will send an error message in case variant create or image upload fails. In the case of success, we will send a PRODUCT_UPDATE webhook.", + "group": "product" + }, + { + "name": "CUSTOMER_CREATED", + "description": "", + "group": "" + }, + { + "name": "CUSTOMER_UPDATED", + "description": "", + "group": "" + }, + { + "name": "CUSTOMER_DELETED", + "description": "", + "group": "" + }, + { + "name": "STORE_UPDATED", + "description": "This webhook fires a merchant's store configuration changes (the most common changes are default currency change and default language change).", + "group": "store" + }, + { + "name": "JOB_COMPLETED", + "description": "", + "group": "" + }, + { + "name": "INVOICE_CREATED", + "description": "Fires when an invoice is created.", + "group": "invoice" + }, + { + "name": "INVOICE_UPDATED", + "description": "Fires when an invoice is updated.", + "group": "invoice" + }, + { + "name": "ACCOUNT_CREATED", + "description": "", + "group": "" + }, + { + "name": "ACCOUNT_UPDATED", + "description": "", + "group": "" + }, + { + "name": "BILL_CREATED", + "description": "", + "group": "" + }, + { + "name": "BILL_UPDATED", + "description": "", + "group": "" + }, + { + "name": "VENDOR_CREATED", + "description": "", + "group": "" + }, + { + "name": "VENDOR_UPDATED", + "description": "", + "group": "" + }, + { + "name": "JOURNAL_ENTRY_CREATED", + "description": "", + "group": "" + }, + { + "name": "JOURNAL_ENTRY_UPDATED", + "description": "", + "group": "" + }, + { + "name": "VERSIONED_ENTITY", + "description": "", + "group": "" + } + ], + "connectionTypes": ["source"], + "testConnection": false, + "classifications": { + "dataIngestion": { + "streamTypes": ["manual"], + "extractionTypes": [] + } + } +} diff --git a/catalog/sources/rutter/docs/connect.md b/catalog/sources/rutter/docs/connect.md new file mode 100644 index 00000000..e30b54df --- /dev/null +++ b/catalog/sources/rutter/docs/connect.md @@ -0,0 +1,11 @@ +## Connect + +Receive events emitted from [Rutter](https://rutter.com). + +### Securely Encrypted + +Rest assured, your credentials are securely encrypted to keep your information safe. + +### Need Help? + +Start a conversation in our [Discord Server](https://discord.com/invite/47AJ42Wzys) or send an email to [support@buildable.dev](mailto:https://discord.com/invite/47AJ42Wzys). Our Data Engineers are available 24/7 to help if you ever get stuck. \ No newline at end of file diff --git a/catalog/sources/rutter/docs/keys.json b/catalog/sources/rutter/docs/keys.json new file mode 100644 index 00000000..49599b08 --- /dev/null +++ b/catalog/sources/rutter/docs/keys.json @@ -0,0 +1,7 @@ +[ + { + "key": "WEBHOOK_URL", + "heading": "Webhook URL", + "subHeading": "Set this URL as the webhook URL in your Rutter dashboard" + } +] \ No newline at end of file diff --git a/catalog/sources/rutter/docs/setup.md b/catalog/sources/rutter/docs/setup.md new file mode 100644 index 00000000..2363fcab --- /dev/null +++ b/catalog/sources/rutter/docs/setup.md @@ -0,0 +1,5 @@ +## Rutter Source Setup + +Check out our [quick start guide](https://docs.buildable.dev/) to learn how to get started. + +Manage your subscribed events from the [Rutter Webhook Dashboard](https://dashboard.rutterapi.com/webhooks). \ No newline at end of file diff --git a/catalog/sources/rutter/rutter.ts b/catalog/sources/rutter/rutter.ts new file mode 100644 index 00000000..fabca0e5 --- /dev/null +++ b/catalog/sources/rutter/rutter.ts @@ -0,0 +1,48 @@ +import { TestConnection } from "../../../types/sourceClassDefinition"; + +export default class Rutter { + async init({ webhookUrl, events }) { + return { + webhookData: {}, + events, + }; + } + + async verifyWebhookSignature({ request, signature, secret }) { + // Validation falls on the user to implement + return true; + } + + async subscribe({ webhookId, events }) { + return { + webhook: {}, + events: [], + }; + } + + async unsubscribe({ webhookId, events }) { + return { + events: [], + webhook: {}, + }; + } + + async getWebhooks() { + return []; + } + + async getSubscribedEvents({ webhookId }) { + return []; + } + + async deleteWebhookEndpoint({ webhookId }) { + return true; + } + + async testConnection(): Promise { + return { + success: true, + message: "Connection tested successfully!", + }; + } +} From d0abd610b67a00a78731006fde98176cdada9385 Mon Sep 17 00:00:00 2001 From: Houssem Eddine Zerrad Date: Sat, 22 Apr 2023 09:34:44 +0100 Subject: [PATCH 21/30] add key and partition to Kafka push data --- .../docs/transformations/actions/pushData.md | 19 +++++++++++++++---- catalog/destinations/kafka/kafka.ts | 8 ++++++-- catalog/destinations/kafka/lib/types.ts | 4 ++++ 3 files changed, 25 insertions(+), 6 deletions(-) diff --git a/catalog/destinations/kafka/docs/transformations/actions/pushData.md b/catalog/destinations/kafka/docs/transformations/actions/pushData.md index 4dbc4e77..75eefbd1 100644 --- a/catalog/destinations/kafka/docs/transformations/actions/pushData.md +++ b/catalog/destinations/kafka/docs/transformations/actions/pushData.md @@ -1,8 +1,8 @@ -### Insert Data +### Push Data -Allows to insert row(s) into a BigQuery table +Allows to push data into a Kafka topic -[Documentation](https://cloud.google.com/bigquery/docs/reference/standard-sql/dml-syntax) +[Documentation](https://kafka.apache.org/quickstart) **Types** @@ -10,6 +10,10 @@ Allows to insert row(s) into a BigQuery table interface IKafkaPushData { topic: string; data: string | string[] | Buffer | Buffer[] | AnyObject | AnyObject[]; + headers?: AnyObject + partition?: number + key?: string + timestamp?: string } ``` @@ -17,7 +21,14 @@ interface IKafkaPushData { ```json { "topic": "topic_0", - "data": [{ "id": 1, "name": "John Doe" }] + "data": [{ "id": 1, "name": "John Doe" }], + "headers": { + "header1": "value1", + "header2": "value2" + }, + "partition": 3, + "key": "1", + "timestamp": "2023-01-01T00:00:00.000Z" } ``` diff --git a/catalog/destinations/kafka/kafka.ts b/catalog/destinations/kafka/kafka.ts index 366e4a02..23c63a6e 100644 --- a/catalog/destinations/kafka/kafka.ts +++ b/catalog/destinations/kafka/kafka.ts @@ -90,11 +90,15 @@ export class KafkaDriver implements DestinationClassI { * Push data to Kafka * @param topic - Kafka topic * @param data - Data to push to Kafka + * @param headers - Headers to send with the message + * @param partition - Partition to send the message to + * @param key - Key to send with the message + * @param timestamp - Timestamp in UTC format */ - async pushData({ topic, data }: IKafkaPushData) { + async pushData({ topic, data, headers, partition, key, timestamp }: IKafkaPushData) { return this.producer.send({ topic, - messages: [{ value: JSON.stringify(data) }], + messages: [{ value: JSON.stringify(data), headers, partition, key, timestamp }], }); } } diff --git a/catalog/destinations/kafka/lib/types.ts b/catalog/destinations/kafka/lib/types.ts index d17681b5..44d19336 100644 --- a/catalog/destinations/kafka/lib/types.ts +++ b/catalog/destinations/kafka/lib/types.ts @@ -3,4 +3,8 @@ import { AnyObject } from "../../../../types/destinationClassDefinition"; export interface IKafkaPushData { topic: string; data: string | string[] | Buffer | Buffer[] | AnyObject | AnyObject[]; + headers?: AnyObject + partition?: number + key?: string + timestamp?: string } From 7809423117005155abc8df9cacfb022347ecd500 Mon Sep 17 00:00:00 2001 From: Paul Krishnamurthy Date: Thu, 27 Apr 2023 11:32:29 -0400 Subject: [PATCH 22/30] MSSQL Destination --- catalog/destinations/mssql/CHANGELOG.md | 12 + catalog/destinations/mssql/config.json | 48 ++++ catalog/destinations/mssql/docs/connect.md | 24 ++ catalog/destinations/mssql/docs/setup.md | 15 ++ .../docs/transformations/actions/delete.md | 36 +++ .../actions/executeRawQuery.md | 34 +++ .../docs/transformations/actions/insert.md | 41 ++++ .../docs/transformations/actions/update.md | 42 ++++ .../mssql/docs/transformations/footer.md | 3 + .../mssql/docs/transformations/header.md | 16 ++ catalog/destinations/mssql/index.ts | 13 ++ catalog/destinations/mssql/mssql.ts | 208 ++++++++++++++++++ 12 files changed, 492 insertions(+) create mode 100644 catalog/destinations/mssql/CHANGELOG.md create mode 100644 catalog/destinations/mssql/config.json create mode 100644 catalog/destinations/mssql/docs/connect.md create mode 100644 catalog/destinations/mssql/docs/setup.md create mode 100644 catalog/destinations/mssql/docs/transformations/actions/delete.md create mode 100644 catalog/destinations/mssql/docs/transformations/actions/executeRawQuery.md create mode 100644 catalog/destinations/mssql/docs/transformations/actions/insert.md create mode 100644 catalog/destinations/mssql/docs/transformations/actions/update.md create mode 100644 catalog/destinations/mssql/docs/transformations/footer.md create mode 100644 catalog/destinations/mssql/docs/transformations/header.md create mode 100644 catalog/destinations/mssql/index.ts create mode 100644 catalog/destinations/mssql/mssql.ts diff --git a/catalog/destinations/mssql/CHANGELOG.md b/catalog/destinations/mssql/CHANGELOG.md new file mode 100644 index 00000000..805f00b1 --- /dev/null +++ b/catalog/destinations/mssql/CHANGELOG.md @@ -0,0 +1,12 @@ +# 📣 Change Log +All notable changes to the `Microsoft SQL Server` Destination Connection will be documented in this file. + +The format followed is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). + +--- + +## [1.0.0] - 2022-09-02 + +⚡️ Initial Version + +--- diff --git a/catalog/destinations/mssql/config.json b/catalog/destinations/mssql/config.json new file mode 100644 index 00000000..fa2c1610 --- /dev/null +++ b/catalog/destinations/mssql/config.json @@ -0,0 +1,48 @@ +{ + "title": "Microsoft SQL Server", + "description": "Microsoft SQL Server is a relational database management system developed by Microsoft", + "apiVersion": "NA", + "type": "mssql", + "category": "database", + "image": "https://assets.buildable.dev/catalog/node-templates/sql-server.svg", + "tags": ["Microsoft", "db", "sql"], + "authentication": [ + { + "name": "MSSQL_SERVER", + "label": "Enter your MSSQL Server", + "placeholder": "my-server" + }, + { + "name": "MSSQL_USERNAME", + "label": "Enter your MSSQL Username", + "placeholder": "my-username" + }, + { + "name": "MSSQL_PASSWORD", + "type": "password", + "label": "Enter your MSSQL Password", + "placeholder": "supersecretpassword" + }, + { + "name": "MSSQL_PORT", + "label": "Enter your MSSQL Port", + "placeholder": "1433" + }, + { + "name": "MSSQL_DATABASE", + "label": "Enter your MSSQL Database Name", + "placeholder": "my-db" + } + ], + "eventSchema": {}, + "settings": { + "createBuildableEnvVars": true, + "hasEvents": false, + "showEvents": false + }, + "paths": null, + "events": [], + "connectionTypes": ["target"], + "actions": ["insert", "update", "delete", "executeRawQuery"], + "destinationType": "http" +} diff --git a/catalog/destinations/mssql/docs/connect.md b/catalog/destinations/mssql/docs/connect.md new file mode 100644 index 00000000..1a16ad39 --- /dev/null +++ b/catalog/destinations/mssql/docs/connect.md @@ -0,0 +1,24 @@ +## Microsoft SQL Server + +MSSQL is a relational database management system developed by Microsoft. + +### Getting Started + +To enable Buildable to connect to your database, please ensure that your firewall accepts incoming requests from the following IP addresses: + +`35.245.232.82` `35.245.100.81` + +`34.120.49.202` `34.160.218.232` + +`3.12.101.201` `3.129.238.32` + +`3.13.190.25` `3.133.23.83` + + +### Securely Encrypted + +Rest assured, your credentials are securely encrypted to keep your information safe. + +### Need Help? + +Start a conversation in our [Discord Server](https://discord.com/invite/47AJ42Wzys) or send an email to [support@buildable.dev](mailto:https://discord.com/invite/47AJ42Wzys). Our Data Engineers are available 24/7 to help if you ever get stuck. \ No newline at end of file diff --git a/catalog/destinations/mssql/docs/setup.md b/catalog/destinations/mssql/docs/setup.md new file mode 100644 index 00000000..4f68afb4 --- /dev/null +++ b/catalog/destinations/mssql/docs/setup.md @@ -0,0 +1,15 @@ +## Microsoft SQL Server Destination Setup + +Check out our [quick start guide](https://docs.buildable.dev/) to learn how to get started. + +### IP Whitelisting + +To enable Buildable to connect to your database, please ensure that your firewall accepts incoming requests from the following IP addresses: + +`35.245.232.82` `35.245.100.81` + +`34.120.49.202` `34.160.218.232` + +`3.12.101.201` `3.129.238.32` + +`3.13.190.25` `3.133.23.83` \ No newline at end of file diff --git a/catalog/destinations/mssql/docs/transformations/actions/delete.md b/catalog/destinations/mssql/docs/transformations/actions/delete.md new file mode 100644 index 00000000..2f5a97b2 --- /dev/null +++ b/catalog/destinations/mssql/docs/transformations/actions/delete.md @@ -0,0 +1,36 @@ +### Delete + +Delete queried rows in an MSSQL table. + +[Documentation](https://knexjs.org/guide/query-builder.html#del-delete) + +**Types** + +```ts +interface DeletePayload { + tableName: string; + query: object; +} +``` + +**Sample Payload** + +```js +{ + tableName: "posts", + query: { + name: "John Doe" + } +} +``` + +**Sample Response** + +```json +{ + "data": { + + }, + "status": 200 +} +``` diff --git a/catalog/destinations/mssql/docs/transformations/actions/executeRawQuery.md b/catalog/destinations/mssql/docs/transformations/actions/executeRawQuery.md new file mode 100644 index 00000000..6a2e038d --- /dev/null +++ b/catalog/destinations/mssql/docs/transformations/actions/executeRawQuery.md @@ -0,0 +1,34 @@ +### Execute Raw Query + +Execute a raw SQL query on an MSSQL table. + +[Documentation](https://knexjs.org/guide/raw.html#raw-queries) + +**Types** + +```ts +interface ExecuteRawQueryPayload { + statement: string, + maxLimit: number = 100 // Max 250 +} +``` + +**Sample Payload** + +```js +{ + statement: "INSERT INTO names (name) VALUES ('John Doe');", + maxLimit: 10 +} +``` + +**Sample Response** + +```json +{ + "data": { + + }, + "status": 200 +} +``` diff --git a/catalog/destinations/mssql/docs/transformations/actions/insert.md b/catalog/destinations/mssql/docs/transformations/actions/insert.md new file mode 100644 index 00000000..04f3620b --- /dev/null +++ b/catalog/destinations/mssql/docs/transformations/actions/insert.md @@ -0,0 +1,41 @@ +### Insert + +Insert one or more rows into an MSSQL table. + +[Documentation](https://knexjs.org/guide/query-builder.html#insert) + +**Types** + +```ts +interface InsertPayload { + tableName: string; + data: object | object[]; + returning: string[]; + options: object ; +} +``` + +**Sample Payload** + +```js +{ + tableName: "posts", + data: [ + { + name: "My blog post", + description: "Lorem ipsum dolor sit amet.", + } + ] +} +``` + +**Sample Response** + +```json +{ + "data": { + + }, + "status": 200 +} +``` diff --git a/catalog/destinations/mssql/docs/transformations/actions/update.md b/catalog/destinations/mssql/docs/transformations/actions/update.md new file mode 100644 index 00000000..470ea3b2 --- /dev/null +++ b/catalog/destinations/mssql/docs/transformations/actions/update.md @@ -0,0 +1,42 @@ +### Update + +Update queried rows in an MSSQL table. + +[Documentation](https://knexjs.org/guide/query-builder.html#update) + +**Types** + +```ts +interface UpdatePayload { + tableName: string; + query: object; + data: object; + returning: string[]; + options: object ; +} +``` + +**Sample Payload** + +```js +{ + tableName: "posts", + query: { + _id: 1 + }, + data: { + name: "John Doe" + } +} +``` + +**Sample Response** + +```json +{ + "data": { + + }, + "status": 200 +} +``` diff --git a/catalog/destinations/mssql/docs/transformations/footer.md b/catalog/destinations/mssql/docs/transformations/footer.md new file mode 100644 index 00000000..203ae528 --- /dev/null +++ b/catalog/destinations/mssql/docs/transformations/footer.md @@ -0,0 +1,3 @@ +### Need Help? + +Start a conversation in our [Discord Server](https://discord.com/invite/47AJ42Wzys) or send an email to [support@buildable.dev](mailto:https://discord.com/invite/47AJ42Wzys). Our Data Engineers are available 24/7 to help if you ever get stuck. \ No newline at end of file diff --git a/catalog/destinations/mssql/docs/transformations/header.md b/catalog/destinations/mssql/docs/transformations/header.md new file mode 100644 index 00000000..f4484dc7 --- /dev/null +++ b/catalog/destinations/mssql/docs/transformations/header.md @@ -0,0 +1,16 @@ +## Documentation +---- + +Microsoft SQL Server is an open-source relational database management system. + +### Getting Started + +To enable Buildable to connect to your database, please ensure that your firewall accepts incoming requests from the following IP address: + +`35.245.232.82` `35.245.100.81` + +`34.120.49.202` `34.160.218.232` + +`3.12.101.201` `3.129.238.32` + +`3.13.190.25` `3.133.23.83` diff --git a/catalog/destinations/mssql/index.ts b/catalog/destinations/mssql/index.ts new file mode 100644 index 00000000..5e0ec9c3 --- /dev/null +++ b/catalog/destinations/mssql/index.ts @@ -0,0 +1,13 @@ +import getProxyDriver from "./mssql"; + +export async function main({ payload, config, action }) { + try { + const driver = getProxyDriver(config); + + const data = await driver[action](payload); + + return { data, status: 200 }; + } catch (error) { + throw error; + } +} diff --git a/catalog/destinations/mssql/mssql.ts b/catalog/destinations/mssql/mssql.ts new file mode 100644 index 00000000..a6a16571 --- /dev/null +++ b/catalog/destinations/mssql/mssql.ts @@ -0,0 +1,208 @@ +import { Knex, knex } from "knex"; +import { + DestinationClassI, + AnyObject, + TestConnection, +} from "../../../types/destinationClassDefinition"; + +class MSSQLDriver implements DestinationClassI { + MSSQL_HOST: string; + + MSSQL_USERNAME: string; + + MSSQL_PASSWORD: string; + + MSSQL_PORT: number; + + MSSQL_DATABASE: string; + + client: Knex; + + constructor({ + MSSQL_HOST, + MSSQL_USERNAME, + MSSQL_PASSWORD, + MSSQL_PORT, + MSSQL_DATABASE, + }: AnyObject) { + this.MSSQL_HOST = MSSQL_HOST; + this.MSSQL_USERNAME = MSSQL_USERNAME; + this.MSSQL_PASSWORD = MSSQL_PASSWORD; + this.MSSQL_PORT = parseFloat(MSSQL_PORT); + this.MSSQL_DATABASE = MSSQL_DATABASE; + } + + async connect(config?: AnyObject) { + const { + MSSQL_HOST, + MSSQL_USERNAME, + MSSQL_PASSWORD, + MSSQL_PORT, + MSSQL_DATABASE, + } = config || this; + + this.client = knex({ + client: "mssql", + connection: { + host: MSSQL_HOST, + user: MSSQL_USERNAME, + password: MSSQL_PASSWORD, + port: MSSQL_PORT, + database: MSSQL_DATABASE, + }, + }); + + try { + await this.client.raw("SELECT 1"); + } catch (error) { + throw new Error(`Error connecting to Microsoft SQL Server: ${error.message}`); + } + } + + async disconnect() { + return this.client.destroy(); + } + + async testConnection(): Promise { + await this.connect(); + + return { + success: true, + message: `Successfully connected to the '${this.MSSQL_DATABASE}' database!`, + }; + } + + async insert({ + tableName, + data, + returning, + options, + }: { + tableName: string; + data: AnyObject; + returning?: string[]; + options?: AnyObject; + }) { + return this.client(tableName).insert(data, returning, options); + } + + async update({ + tableName, + query, + data, + returning, + options, + }: { + tableName: string; + query: AnyObject; + data: AnyObject; + returning: string[]; + options: AnyObject; + }) { + return this.client(tableName).where(query).update(data, returning, options); + } + + async delete({ tableName, query }: { tableName: string; query: AnyObject }) { + return this.client(tableName).where(query).del(); + } + + async executeRawQuery({ + statement, + maxLimit = 100, + }: { + statement: string; + maxLimit: number; + }) { + const limitedStatement = handleLimit(statement, maxLimit); + + return this.client.raw(limitedStatement); + } +} + +const handleLimit = (query: string, maxLimit: number) => { + if (maxLimit > 200) { + throw new Error( + "The optimized value for maxLimit is less than or equal to 200", + ); + } + + const splitQuery = query.split(";"); + + splitQuery.forEach((q, i) => { + if (q.trim().toLowerCase().substring(0, "select".length) === "select") { + splitQuery[i] = setHardLimit(q.trim(), maxLimit); + } + }); + + return splitQuery.join("; "); +}; + +const setHardLimit = (query: string, maxLimit: number) => { + // if (query.charAt(query.length - 1) === ";") { + // query = query.substring(0, query.length - 1); + // } + const limitedQuery = query.charAt(query.length - 1) === ";" ? query.substring(query.length - 1) : query; + + const tokenizedQuery = limitedQuery.split(" "); + const foundIndex = Math.max( + tokenizedQuery.lastIndexOf("limit"), + tokenizedQuery.lastIndexOf("LIMIT"), + ); + if (foundIndex > 0) { + const limit = tokenizedQuery[foundIndex + 1]; + if (Number.parseInt(limit, 10) > maxLimit) { + // Handle parsing with parenthesis or semicolons + tokenizedQuery[foundIndex + 1] = + maxLimit + + limit.toString().substring(Number.parseInt(limit, 10).toString().length); + } + } else { + tokenizedQuery.push("LIMIT"); + tokenizedQuery.push(maxLimit.toString()); + } + + return tokenizedQuery.join(" "); +}; + +const getProxyDriver = (config: AnyObject) => { + const driver = new MSSQLDriver(config); + + return new Proxy(driver, { + get: (target, prop) => { + const whitelistedMethods = [ + "insert", + "update", + "delete", + "executeRawQuery", + "testConnection", + ]; + + if (whitelistedMethods.includes(prop as string)) { + // Establish and close connection for each method call + return async (payload) => { + try { + // Do not connect and disconnect for testConnection + if (prop === "testConnection") { + return await driver.testConnection(); + } + + await driver.connect(config); + + const result = await driver[prop](payload); + + await driver.disconnect(); + + return result; + } catch (error) { + console.log("Error occured ===> ", error); + throw error; + } + }; + } + + throw new Error(`Method ${prop as string} not found`); + }, + }); +}; + +export default getProxyDriver; From ddc7c8ecbdad7820579599d5c4eb0d10179bc6d4 Mon Sep 17 00:00:00 2001 From: Paul Krishnamurthy Date: Thu, 27 Apr 2023 12:44:14 -0400 Subject: [PATCH 23/30] add mssql package to destinations deployment --- catalog/destinations/package-lock.json | 1879 +++++++++++++++++++++++- catalog/destinations/package.json | 1 + 2 files changed, 1818 insertions(+), 62 deletions(-) diff --git a/catalog/destinations/package-lock.json b/catalog/destinations/package-lock.json index a642aca7..772d6438 100644 --- a/catalog/destinations/package-lock.json +++ b/catalog/destinations/package-lock.json @@ -18,6 +18,7 @@ "kafkajs": "^2.2.4", "knex": "^2.3.0", "mongodb": "^4.11.0", + "mssql": "^9.1.1", "pg": "^8.8.0", "stripe": "^8.219.0", "uuid": "^9.0.0" @@ -1096,6 +1097,248 @@ "tslib": "^2.3.1" } }, + "node_modules/@azure/abort-controller": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-1.1.0.tgz", + "integrity": "sha512-TrRLIoSQVzfAJX9H1JeFjzAoDGcoK1IYX1UImfceTZpsyYfWr09Ss1aHW1y5TrrR3iq6RZLBwJ3E24uwPhwahw==", + "dependencies": { + "tslib": "^2.2.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@azure/core-auth": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@azure/core-auth/-/core-auth-1.4.0.tgz", + "integrity": "sha512-HFrcTgmuSuukRf/EdPmqBrc5l6Q5Uu+2TbuhaKbgaCpP2TfAeiNaQPAadxO+CYBRHGUzIDteMAjFspFLDLnKVQ==", + "dependencies": { + "@azure/abort-controller": "^1.0.0", + "tslib": "^2.2.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@azure/core-client": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/@azure/core-client/-/core-client-1.7.2.tgz", + "integrity": "sha512-ye5554gnVnXdfZ64hptUtETgacXoRWxYv1JF5MctoAzTSH5dXhDPZd9gOjDPyWMcLIk58pnP5+p5vGX6PYn1ag==", + "dependencies": { + "@azure/abort-controller": "^1.0.0", + "@azure/core-auth": "^1.4.0", + "@azure/core-rest-pipeline": "^1.9.1", + "@azure/core-tracing": "^1.0.0", + "@azure/core-util": "^1.0.0", + "@azure/logger": "^1.0.0", + "tslib": "^2.2.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@azure/core-http-compat": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@azure/core-http-compat/-/core-http-compat-1.3.0.tgz", + "integrity": "sha512-ZN9avruqbQ5TxopzG3ih3KRy52n8OAbitX3fnZT5go4hzu0J+KVPSzkL+Wt3hpJpdG8WIfg1sBD1tWkgUdEpBA==", + "dependencies": { + "@azure/abort-controller": "^1.0.4", + "@azure/core-client": "^1.3.0", + "@azure/core-rest-pipeline": "^1.3.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@azure/core-lro": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/@azure/core-lro/-/core-lro-2.5.2.tgz", + "integrity": "sha512-tucUutPhBwCPu6v16KEFYML81npEL6gnT+iwewXvK5ZD55sr0/Vw2jfQETMiKVeARRrXHB2QQ3SpxxGi1zAUWg==", + "dependencies": { + "@azure/abort-controller": "^1.0.0", + "@azure/core-util": "^1.2.0", + "@azure/logger": "^1.0.0", + "tslib": "^2.2.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@azure/core-paging": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@azure/core-paging/-/core-paging-1.5.0.tgz", + "integrity": "sha512-zqWdVIt+2Z+3wqxEOGzR5hXFZ8MGKK52x4vFLw8n58pR6ZfKRx3EXYTxTaYxYHc/PexPUTyimcTWFJbji9Z6Iw==", + "dependencies": { + "tslib": "^2.2.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@azure/core-rest-pipeline": { + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/@azure/core-rest-pipeline/-/core-rest-pipeline-1.10.3.tgz", + "integrity": "sha512-AMQb0ttiGJ0MIV/r+4TVra6U4+90mPeOveehFnrqKlo7dknPJYdJ61wOzYJXJjDxF8LcCtSogfRelkq+fCGFTw==", + "dependencies": { + "@azure/abort-controller": "^1.0.0", + "@azure/core-auth": "^1.4.0", + "@azure/core-tracing": "^1.0.1", + "@azure/core-util": "^1.3.0", + "@azure/logger": "^1.0.0", + "form-data": "^4.0.0", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.0", + "tslib": "^2.2.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@azure/core-tracing": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@azure/core-tracing/-/core-tracing-1.0.1.tgz", + "integrity": "sha512-I5CGMoLtX+pI17ZdiFJZgxMJApsK6jjfm85hpgp3oazCdq5Wxgh4wMr7ge/TTWW1B5WBuvIOI1fMU/FrOAMKrw==", + "dependencies": { + "tslib": "^2.2.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@azure/core-util": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@azure/core-util/-/core-util-1.3.1.tgz", + "integrity": "sha512-pjfOUAb+MPLODhGuXot/Hy8wUgPD0UTqYkY3BiYcwEETrLcUCVM1t0roIvlQMgvn1lc48TGy5bsonsFpF862Jw==", + "dependencies": { + "@azure/abort-controller": "^1.0.0", + "tslib": "^2.2.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@azure/identity": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@azure/identity/-/identity-2.1.0.tgz", + "integrity": "sha512-BPDz1sK7Ul9t0l9YKLEa8PHqWU4iCfhGJ+ELJl6c8CP3TpJt2urNCbm0ZHsthmxRsYoMPbz2Dvzj30zXZVmAFw==", + "dependencies": { + "@azure/abort-controller": "^1.0.0", + "@azure/core-auth": "^1.3.0", + "@azure/core-client": "^1.4.0", + "@azure/core-rest-pipeline": "^1.1.0", + "@azure/core-tracing": "^1.0.0", + "@azure/core-util": "^1.0.0", + "@azure/logger": "^1.0.0", + "@azure/msal-browser": "^2.26.0", + "@azure/msal-common": "^7.0.0", + "@azure/msal-node": "^1.10.0", + "events": "^3.0.0", + "jws": "^4.0.0", + "open": "^8.0.0", + "stoppable": "^1.1.0", + "tslib": "^2.2.0", + "uuid": "^8.3.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@azure/identity/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/@azure/keyvault-keys": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@azure/keyvault-keys/-/keyvault-keys-4.7.0.tgz", + "integrity": "sha512-HScWdORbRCKi1vdKI6EChe/t/P/zV7jcGZWfj18BOyeensk5d1/Ynfx1t6xfAy5zUIQvAWVU97hXdCznDpULbQ==", + "dependencies": { + "@azure/abort-controller": "^1.0.0", + "@azure/core-auth": "^1.3.0", + "@azure/core-client": "^1.5.0", + "@azure/core-http-compat": "^1.3.0", + "@azure/core-lro": "^2.2.0", + "@azure/core-paging": "^1.1.1", + "@azure/core-rest-pipeline": "^1.8.0", + "@azure/core-tracing": "^1.0.0", + "@azure/core-util": "^1.0.0", + "@azure/logger": "^1.0.0", + "tslib": "^2.2.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@azure/logger": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@azure/logger/-/logger-1.0.4.tgz", + "integrity": "sha512-ustrPY8MryhloQj7OWGe+HrYx+aoiOxzbXTtgblbV3xwCqpzUK36phH3XNHQKj3EPonyFUuDTfR3qFhTEAuZEg==", + "dependencies": { + "tslib": "^2.2.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@azure/msal-browser": { + "version": "2.36.0", + "resolved": "https://registry.npmjs.org/@azure/msal-browser/-/msal-browser-2.36.0.tgz", + "integrity": "sha512-OrVDZ9ftO7ExqZVHripAt+doKg6G14YbP2LoSygiWQoSqoO4CejoXLRLqANc/HGg18N0p/oaRETw4IHZvwsxZw==", + "dependencies": { + "@azure/msal-common": "^12.1.0" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@azure/msal-browser/node_modules/@azure/msal-common": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-12.1.0.tgz", + "integrity": "sha512-9RUiv0evSHvYtvF7r9ksShw9FgCeT6Rf6JB/SOMbMzI0VySZDUBSE+0b9e7DgL2Ph8wSARIh3m8c5pCK9TRY3w==", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@azure/msal-common": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-7.6.0.tgz", + "integrity": "sha512-XqfbglUTVLdkHQ8F9UQJtKseRr3sSnr9ysboxtoswvaMVaEfvyLtMoHv9XdKUfOc0qKGzNgRFd9yRjIWVepl6Q==", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@azure/msal-node": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/@azure/msal-node/-/msal-node-1.17.1.tgz", + "integrity": "sha512-1lC80yV+Y/gHqkYJ21Qy1Ej/cI/Kt1JcdY0xiM7/+mcEuBAkArR9B1YMY538PMZ5GfyVlYkCHYh/N0CBD5FJlQ==", + "dependencies": { + "@azure/msal-common": "^12.1.0", + "jsonwebtoken": "^9.0.0", + "uuid": "^8.3.0" + }, + "engines": { + "node": "10 || 12 || 14 || 16 || 18" + } + }, + "node_modules/@azure/msal-node/node_modules/@azure/msal-common": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-12.1.0.tgz", + "integrity": "sha512-9RUiv0evSHvYtvF7r9ksShw9FgCeT6Rf6JB/SOMbMzI0VySZDUBSE+0b9e7DgL2Ph8wSARIh3m8c5pCK9TRY3w==", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@azure/msal-node/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/@babel/parser": { "version": "7.20.15", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.15.tgz", @@ -1913,6 +2156,11 @@ "node": ">=6" } }, + "node_modules/@js-joda/core": { + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/@js-joda/core/-/core-5.5.3.tgz", + "integrity": "sha512-7dqNYwG8gCt4hfg5PKgM7xLEcgSBcx/UgC92OMnhMmvAnq11QzDFPrxUkNR/u5kn17WWLZ8beZ4A3Qrz4pZcmQ==" + }, "node_modules/@jsdoc/salty": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/@jsdoc/salty/-/salty-0.2.3.tgz", @@ -1979,6 +2227,11 @@ "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==" }, + "node_modules/@tediousjs/connection-string": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@tediousjs/connection-string/-/connection-string-0.4.2.tgz", + "integrity": "sha512-1R9UC7Qc5wief2oJL+c1+d7v1/oPBayL85u8L/jV2DzIKput1TZ8ZUjj2nxQaSfzu210zp0oFWUrYUiUs8NhBQ==" + }, "node_modules/@tootallnate/once": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", @@ -2200,6 +2453,18 @@ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "optional": true }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz", + "integrity": "sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==", + "dependencies": { + "call-bind": "^1.0.2", + "is-array-buffer": "^3.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/arrify": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", @@ -2222,6 +2487,17 @@ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" }, + "node_modules/available-typed-arrays": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", + "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/axios": { "version": "0.27.2", "resolved": "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz", @@ -2285,6 +2561,39 @@ "node": "*" } }, + "node_modules/bl": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-5.1.0.tgz", + "integrity": "sha512-tv1ZJHLfTDnXE6tMHv73YgSJaWR2AFuPwMntBe7XL/GBFHnT0CLnsHMogfk5+GzCDC5ZWarSCYaIGATZt9dNsQ==", + "dependencies": { + "buffer": "^6.0.3", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/bl/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, "node_modules/bluebird": { "version": "3.7.2", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", @@ -2504,6 +2813,29 @@ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "optional": true }, + "node_modules/define-lazy-prop": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", + "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", + "engines": { + "node": ">=8" + } + }, + "node_modules/define-properties": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.0.tgz", + "integrity": "sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==", + "dependencies": { + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -2558,6 +2890,102 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, + "node_modules/es-abstract": { + "version": "1.21.2", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.21.2.tgz", + "integrity": "sha512-y/B5POM2iBnIxCiernH1G7rC9qQoM77lLIMQLuob0zhp8C56Po81+2Nj0WFKnd0pNReDTnkYryc+zhOzpEIROg==", + "dependencies": { + "array-buffer-byte-length": "^1.0.0", + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "es-set-tostringtag": "^2.0.1", + "es-to-primitive": "^1.2.1", + "function.prototype.name": "^1.1.5", + "get-intrinsic": "^1.2.0", + "get-symbol-description": "^1.0.0", + "globalthis": "^1.0.3", + "gopd": "^1.0.1", + "has": "^1.0.3", + "has-property-descriptors": "^1.0.0", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.5", + "is-array-buffer": "^3.0.2", + "is-callable": "^1.2.7", + "is-negative-zero": "^2.0.2", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.2", + "is-string": "^1.0.7", + "is-typed-array": "^1.1.10", + "is-weakref": "^1.0.2", + "object-inspect": "^1.12.3", + "object-keys": "^1.1.1", + "object.assign": "^4.1.4", + "regexp.prototype.flags": "^1.4.3", + "safe-regex-test": "^1.0.0", + "string.prototype.trim": "^1.2.7", + "string.prototype.trimend": "^1.0.6", + "string.prototype.trimstart": "^1.0.6", + "typed-array-length": "^1.0.4", + "unbox-primitive": "^1.0.2", + "which-typed-array": "^1.1.9" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-aggregate-error": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/es-aggregate-error/-/es-aggregate-error-1.0.9.tgz", + "integrity": "sha512-fvnX40sb538wdU6r4s35cq4EY6Lr09Upj40BEVem4LEsuW8XgQep9yD5Q1U2KftokNp1rWODFJ2qwZSsAjFpbg==", + "dependencies": { + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "function-bind": "^1.1.1", + "functions-have-names": "^1.2.3", + "get-intrinsic": "^1.1.3", + "globalthis": "^1.0.3", + "has-property-descriptors": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz", + "integrity": "sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==", + "dependencies": { + "get-intrinsic": "^1.1.3", + "has": "^1.0.3", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dependencies": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/escalade": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", @@ -2810,6 +3238,14 @@ } } }, + "node_modules/for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dependencies": { + "is-callable": "^1.1.3" + } + }, "node_modules/form-data": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", @@ -2834,12 +3270,37 @@ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" }, + "node_modules/function.prototype.name": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz", + "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.0", + "functions-have-names": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/functional-red-black-tree": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==", "optional": true }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/gaxios": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-5.0.2.tgz", @@ -2895,6 +3356,21 @@ "node": ">=8.0.0" } }, + "node_modules/get-symbol-description": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", + "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/getopts": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/getopts/-/getopts-2.3.0.tgz", @@ -2919,6 +3395,20 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/globalthis": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", + "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", + "dependencies": { + "define-properties": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/google-auth-library": { "version": "8.7.0", "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-8.7.0.tgz", @@ -3072,6 +3562,17 @@ "node": ">=12.0.0" } }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/graceful-fs": { "version": "4.2.10", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", @@ -3102,6 +3603,14 @@ "node": ">= 0.4.0" } }, + "node_modules/has-bigints": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -3111,6 +3620,28 @@ "node": ">=8" } }, + "node_modules/has-property-descriptors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", + "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", + "dependencies": { + "get-intrinsic": "^1.1.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", + "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/has-symbols": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", @@ -3122,6 +3653,20 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/has-tostringtag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", + "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/hpagent": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/hpagent/-/hpagent-1.2.0.tgz", @@ -3160,6 +3705,17 @@ "node": ">= 6" } }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/idb": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/idb/-/idb-7.0.1.tgz", @@ -3199,6 +3755,19 @@ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, + "node_modules/internal-slot": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz", + "integrity": "sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==", + "dependencies": { + "get-intrinsic": "^1.2.0", + "has": "^1.0.3", + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/interpret": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/interpret/-/interpret-2.2.0.tgz", @@ -3220,53 +3789,250 @@ "node": "*" } }, + "node_modules/is-array-buffer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz", + "integrity": "sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==", + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.0", + "is-typed-array": "^1.1.10" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dependencies": { + "has-bigints": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-core-module": { "version": "2.11.0", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==", "dependencies": { - "has": "^1.0.3" + "has": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-negative-zero": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", + "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number-object": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-retry-allowed": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-2.2.0.tgz", + "integrity": "sha512-XVm7LOeLpTW4jV19QSH38vkswxoLud8sQ57YwJVTPWdiaI9I8keEhGFpBlslyVsgdQy4Opg8QOLb8YRgsyZiQg==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", + "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-stream-ended": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-stream-ended/-/is-stream-ended-0.1.4.tgz", + "integrity": "sha512-xj0XPvmr7bQFTvirqnFr50o0hQIh6ZItDqloxt5aJrR4NQsYeSsyFQERYGCAzfindAcnKjINnwEEgLx4IqVzQw==", + "optional": true + }, + "node_modules/is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "node_modules/is-typed-array": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.10.tgz", + "integrity": "sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==", + "dependencies": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.0" + }, "engines": { - "node": ">=8" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-retry-allowed": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-2.2.0.tgz", - "integrity": "sha512-XVm7LOeLpTW4jV19QSH38vkswxoLud8sQ57YwJVTPWdiaI9I8keEhGFpBlslyVsgdQy4Opg8QOLb8YRgsyZiQg==", - "engines": { - "node": ">=10" + "node_modules/is-weakref": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "dependencies": { + "call-bind": "^1.0.2" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dependencies": { + "is-docker": "^2.0.0" + }, "engines": { "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/is-stream-ended": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-stream-ended/-/is-stream-ended-0.1.4.tgz", - "integrity": "sha512-xj0XPvmr7bQFTvirqnFr50o0hQIh6ZItDqloxt5aJrR4NQsYeSsyFQERYGCAzfindAcnKjINnwEEgLx4IqVzQw==", - "optional": true - }, "node_modules/jose": { "version": "4.11.2", "resolved": "https://registry.npmjs.org/jose/-/jose-4.11.2.tgz", @@ -3275,6 +4041,11 @@ "url": "https://github.com/sponsors/panva" } }, + "node_modules/js-md4": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/js-md4/-/js-md4-0.3.2.tgz", + "integrity": "sha512-/GDnfQYsltsjRswQhN9fhv3EMw2sCpUdrdxyWDOUK7eyD++r3gRhzgiQgc/x4MAv2i1iuQ4lxO5mvqM3vj4bwA==" + }, "node_modules/js2xmlparser": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/js2xmlparser/-/js2xmlparser-4.0.2.tgz", @@ -3284,6 +4055,11 @@ "xmlcreate": "^2.0.4" } }, + "node_modules/jsbi": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/jsbi/-/jsbi-4.3.0.tgz", + "integrity": "sha512-SnZNcinB4RIcnEyZqFPdGPVgrg2AcnykiBy0sHVJQKHYeaLUvi3Exj+iaPpLnFVkDPZIV4U0yvgC9/R4uEAZ9g==" + }, "node_modules/jsdoc": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-4.0.0.tgz", @@ -3683,6 +4459,35 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, + "node_modules/mssql": { + "version": "9.1.1", + "resolved": "https://registry.npmjs.org/mssql/-/mssql-9.1.1.tgz", + "integrity": "sha512-m0yTx9xzUtTvJpWJHqknUXUDPRnJXZYOOFNygnNIXn1PBkLsC/rkXQdquObd+M0ZPlBhGC00Jg28zG0wCl7VWg==", + "dependencies": { + "@tediousjs/connection-string": "^0.4.1", + "commander": "^9.4.0", + "debug": "^4.3.3", + "rfdc": "^1.3.0", + "tarn": "^3.0.2", + "tedious": "^15.0.1" + }, + "bin": { + "mssql": "bin/mssql" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/native-duplexpair": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/native-duplexpair/-/native-duplexpair-1.0.0.tgz", + "integrity": "sha512-E7QQoM+3jvNtlmyfqRZ0/U75VFgCls+fSkbml2MpgWkWyz3ox8Y58gNhfuziuQYGNNQAbFZJQck55LHCnCK6CA==" + }, + "node_modules/node-abort-controller": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/node-abort-controller/-/node-abort-controller-3.1.1.tgz", + "integrity": "sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==" + }, "node_modules/node-fetch": { "version": "2.6.7", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", @@ -3746,6 +4551,31 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", + "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "has-symbols": "^1.0.3", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -3754,6 +4584,22 @@ "wrappy": "1" } }, + "node_modules/open": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", + "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==", + "dependencies": { + "define-lazy-prop": "^2.0.0", + "is-docker": "^2.1.1", + "is-wsl": "^2.2.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/optionator": { "version": "0.8.3", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", @@ -4087,6 +4933,22 @@ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.0.tgz", + "integrity": "sha512-0SutC3pNudRKgquxGoRGIz946MZVHqbNfPjBdxeOhBrdgDKlRoXmYLQN9xRbrR09ZXWeGAdPuif7egofn6v5LA==", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "functions-have-names": "^1.2.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -4149,6 +5011,11 @@ "node": ">=12" } }, + "node_modules/rfdc": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz", + "integrity": "sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==" + }, "node_modules/rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", @@ -4225,6 +5092,24 @@ } ] }, + "node_modules/safe-regex-test": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", + "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==", + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "is-regex": "^1.1.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, "node_modules/saslprep": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/saslprep/-/saslprep-1.0.3.tgz", @@ -4317,6 +5202,20 @@ "node": ">= 10.x" } }, + "node_modules/sprintf-js": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.2.tgz", + "integrity": "sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==" + }, + "node_modules/stoppable": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/stoppable/-/stoppable-1.1.0.tgz", + "integrity": "sha512-KXDYZ9dszj6bzvnEMRYvxgeTHU74QBFL54XKtP3nyMuJ81CFYtABZ3bAzL2EdFUaEwJOBOgENyFj3R7oTzDyyw==", + "engines": { + "node": ">=4", + "npm": ">=6" + } + }, "node_modules/stream-events": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/stream-events/-/stream-events-1.0.5.tgz", @@ -4359,6 +5258,48 @@ "node": ">=8" } }, + "node_modules/string.prototype.trim": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.7.tgz", + "integrity": "sha512-p6TmeT1T3411M8Cgg9wBTMRtY2q9+PNy9EV1i2lIXUN/btt763oIfxwN3RR8VU6wHX8j/1CFy0L+YuThm6bgOg==", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz", + "integrity": "sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz", + "integrity": "sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -4436,6 +5377,28 @@ "node": ">=8.0.0" } }, + "node_modules/tedious": { + "version": "15.1.3", + "resolved": "https://registry.npmjs.org/tedious/-/tedious-15.1.3.tgz", + "integrity": "sha512-166EpRm5qknwhEisjZqz/mF7k14fXKJYHRg6XiAXVovd/YkyHJ3SG4Ppy89caPaNFfRr7PVYe+s4dAvKaCMFvw==", + "dependencies": { + "@azure/identity": "^2.0.4", + "@azure/keyvault-keys": "^4.4.0", + "@js-joda/core": "^5.2.0", + "bl": "^5.0.0", + "es-aggregate-error": "^1.0.8", + "iconv-lite": "^0.6.3", + "js-md4": "^0.3.2", + "jsbi": "^4.3.0", + "native-duplexpair": "^1.0.0", + "node-abort-controller": "^3.0.1", + "punycode": "^2.1.0", + "sprintf-js": "^1.1.2" + }, + "engines": { + "node": ">=14" + } + }, "node_modules/teeny-request": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-8.0.2.tgz", @@ -4504,6 +5467,19 @@ "node": ">= 0.8.0" } }, + "node_modules/typed-array-length": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz", + "integrity": "sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==", + "dependencies": { + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "is-typed-array": "^1.1.9" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/uc.micro": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", @@ -4522,6 +5498,20 @@ "node": ">=0.8.0" } }, + "node_modules/unbox-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "dependencies": { + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", + "which-boxed-primitive": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/underscore": { "version": "1.13.6", "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.6.tgz", @@ -4593,6 +5583,40 @@ "node": ">=12" } }, + "node_modules/which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dependencies": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.9.tgz", + "integrity": "sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==", + "dependencies": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.0", + "is-typed-array": "^1.1.10" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/word-wrap": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", @@ -5551,54 +6575,245 @@ "tslib": "^2.3.1" } }, - "@aws-sdk/util-uri-escape": { - "version": "3.201.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-uri-escape/-/util-uri-escape-3.201.0.tgz", - "integrity": "sha512-TeTWbGx4LU2c5rx0obHeDFeO9HvwYwQtMh1yniBz00pQb6Qt6YVOETVQikRZ+XRQwEyCg/dA375UplIpiy54mA==", - "optional": true, - "requires": { - "tslib": "^2.3.1" + "@aws-sdk/util-uri-escape": { + "version": "3.201.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-uri-escape/-/util-uri-escape-3.201.0.tgz", + "integrity": "sha512-TeTWbGx4LU2c5rx0obHeDFeO9HvwYwQtMh1yniBz00pQb6Qt6YVOETVQikRZ+XRQwEyCg/dA375UplIpiy54mA==", + "optional": true, + "requires": { + "tslib": "^2.3.1" + } + }, + "@aws-sdk/util-user-agent-browser": { + "version": "3.257.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.257.0.tgz", + "integrity": "sha512-YdavWK6/8Cw6mypEgysGGX/dT9p9qnzFbnN5PQsUY+JJk2Nx8fKFydjGiQ+6rWPeW17RAv9mmbboh9uPVWxVlw==", + "optional": true, + "requires": { + "@aws-sdk/types": "3.257.0", + "bowser": "^2.11.0", + "tslib": "^2.3.1" + } + }, + "@aws-sdk/util-user-agent-node": { + "version": "3.259.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.259.0.tgz", + "integrity": "sha512-R0VTmNs+ySDDebU98BUbsLyeIM5YmAEr9esPpy15XfSy3AWmAeru8nLlztdaLilHZzLIDzvM2t7NGk/FzZFCvA==", + "optional": true, + "requires": { + "@aws-sdk/node-config-provider": "3.259.0", + "@aws-sdk/types": "3.257.0", + "tslib": "^2.3.1" + } + }, + "@aws-sdk/util-utf8": { + "version": "3.254.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-utf8/-/util-utf8-3.254.0.tgz", + "integrity": "sha512-14Kso/eIt5/qfIBmhEL9L1IfyUqswjSTqO2mY7KOzUZ9SZbwn3rpxmtkhmATkRjD7XIlLKaxBkI7tU9Zjzj8Kw==", + "optional": true, + "requires": { + "@aws-sdk/util-buffer-from": "3.208.0", + "tslib": "^2.3.1" + } + }, + "@aws-sdk/util-utf8-browser": { + "version": "3.259.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-utf8-browser/-/util-utf8-browser-3.259.0.tgz", + "integrity": "sha512-UvFa/vR+e19XookZF8RzFZBrw2EUkQWxiBW0yYQAhvk3C+QVGl0H3ouca8LDBlBfQKXwmW3huo/59H8rwb1wJw==", + "optional": true, + "requires": { + "tslib": "^2.3.1" + } + }, + "@azure/abort-controller": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-1.1.0.tgz", + "integrity": "sha512-TrRLIoSQVzfAJX9H1JeFjzAoDGcoK1IYX1UImfceTZpsyYfWr09Ss1aHW1y5TrrR3iq6RZLBwJ3E24uwPhwahw==", + "requires": { + "tslib": "^2.2.0" + } + }, + "@azure/core-auth": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@azure/core-auth/-/core-auth-1.4.0.tgz", + "integrity": "sha512-HFrcTgmuSuukRf/EdPmqBrc5l6Q5Uu+2TbuhaKbgaCpP2TfAeiNaQPAadxO+CYBRHGUzIDteMAjFspFLDLnKVQ==", + "requires": { + "@azure/abort-controller": "^1.0.0", + "tslib": "^2.2.0" + } + }, + "@azure/core-client": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/@azure/core-client/-/core-client-1.7.2.tgz", + "integrity": "sha512-ye5554gnVnXdfZ64hptUtETgacXoRWxYv1JF5MctoAzTSH5dXhDPZd9gOjDPyWMcLIk58pnP5+p5vGX6PYn1ag==", + "requires": { + "@azure/abort-controller": "^1.0.0", + "@azure/core-auth": "^1.4.0", + "@azure/core-rest-pipeline": "^1.9.1", + "@azure/core-tracing": "^1.0.0", + "@azure/core-util": "^1.0.0", + "@azure/logger": "^1.0.0", + "tslib": "^2.2.0" + } + }, + "@azure/core-http-compat": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@azure/core-http-compat/-/core-http-compat-1.3.0.tgz", + "integrity": "sha512-ZN9avruqbQ5TxopzG3ih3KRy52n8OAbitX3fnZT5go4hzu0J+KVPSzkL+Wt3hpJpdG8WIfg1sBD1tWkgUdEpBA==", + "requires": { + "@azure/abort-controller": "^1.0.4", + "@azure/core-client": "^1.3.0", + "@azure/core-rest-pipeline": "^1.3.0" + } + }, + "@azure/core-lro": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/@azure/core-lro/-/core-lro-2.5.2.tgz", + "integrity": "sha512-tucUutPhBwCPu6v16KEFYML81npEL6gnT+iwewXvK5ZD55sr0/Vw2jfQETMiKVeARRrXHB2QQ3SpxxGi1zAUWg==", + "requires": { + "@azure/abort-controller": "^1.0.0", + "@azure/core-util": "^1.2.0", + "@azure/logger": "^1.0.0", + "tslib": "^2.2.0" + } + }, + "@azure/core-paging": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@azure/core-paging/-/core-paging-1.5.0.tgz", + "integrity": "sha512-zqWdVIt+2Z+3wqxEOGzR5hXFZ8MGKK52x4vFLw8n58pR6ZfKRx3EXYTxTaYxYHc/PexPUTyimcTWFJbji9Z6Iw==", + "requires": { + "tslib": "^2.2.0" + } + }, + "@azure/core-rest-pipeline": { + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/@azure/core-rest-pipeline/-/core-rest-pipeline-1.10.3.tgz", + "integrity": "sha512-AMQb0ttiGJ0MIV/r+4TVra6U4+90mPeOveehFnrqKlo7dknPJYdJ61wOzYJXJjDxF8LcCtSogfRelkq+fCGFTw==", + "requires": { + "@azure/abort-controller": "^1.0.0", + "@azure/core-auth": "^1.4.0", + "@azure/core-tracing": "^1.0.1", + "@azure/core-util": "^1.3.0", + "@azure/logger": "^1.0.0", + "form-data": "^4.0.0", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.0", + "tslib": "^2.2.0" + } + }, + "@azure/core-tracing": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@azure/core-tracing/-/core-tracing-1.0.1.tgz", + "integrity": "sha512-I5CGMoLtX+pI17ZdiFJZgxMJApsK6jjfm85hpgp3oazCdq5Wxgh4wMr7ge/TTWW1B5WBuvIOI1fMU/FrOAMKrw==", + "requires": { + "tslib": "^2.2.0" + } + }, + "@azure/core-util": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@azure/core-util/-/core-util-1.3.1.tgz", + "integrity": "sha512-pjfOUAb+MPLODhGuXot/Hy8wUgPD0UTqYkY3BiYcwEETrLcUCVM1t0roIvlQMgvn1lc48TGy5bsonsFpF862Jw==", + "requires": { + "@azure/abort-controller": "^1.0.0", + "tslib": "^2.2.0" + } + }, + "@azure/identity": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@azure/identity/-/identity-2.1.0.tgz", + "integrity": "sha512-BPDz1sK7Ul9t0l9YKLEa8PHqWU4iCfhGJ+ELJl6c8CP3TpJt2urNCbm0ZHsthmxRsYoMPbz2Dvzj30zXZVmAFw==", + "requires": { + "@azure/abort-controller": "^1.0.0", + "@azure/core-auth": "^1.3.0", + "@azure/core-client": "^1.4.0", + "@azure/core-rest-pipeline": "^1.1.0", + "@azure/core-tracing": "^1.0.0", + "@azure/core-util": "^1.0.0", + "@azure/logger": "^1.0.0", + "@azure/msal-browser": "^2.26.0", + "@azure/msal-common": "^7.0.0", + "@azure/msal-node": "^1.10.0", + "events": "^3.0.0", + "jws": "^4.0.0", + "open": "^8.0.0", + "stoppable": "^1.1.0", + "tslib": "^2.2.0", + "uuid": "^8.3.0" + }, + "dependencies": { + "uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" + } } }, - "@aws-sdk/util-user-agent-browser": { - "version": "3.257.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.257.0.tgz", - "integrity": "sha512-YdavWK6/8Cw6mypEgysGGX/dT9p9qnzFbnN5PQsUY+JJk2Nx8fKFydjGiQ+6rWPeW17RAv9mmbboh9uPVWxVlw==", - "optional": true, + "@azure/keyvault-keys": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@azure/keyvault-keys/-/keyvault-keys-4.7.0.tgz", + "integrity": "sha512-HScWdORbRCKi1vdKI6EChe/t/P/zV7jcGZWfj18BOyeensk5d1/Ynfx1t6xfAy5zUIQvAWVU97hXdCznDpULbQ==", "requires": { - "@aws-sdk/types": "3.257.0", - "bowser": "^2.11.0", - "tslib": "^2.3.1" + "@azure/abort-controller": "^1.0.0", + "@azure/core-auth": "^1.3.0", + "@azure/core-client": "^1.5.0", + "@azure/core-http-compat": "^1.3.0", + "@azure/core-lro": "^2.2.0", + "@azure/core-paging": "^1.1.1", + "@azure/core-rest-pipeline": "^1.8.0", + "@azure/core-tracing": "^1.0.0", + "@azure/core-util": "^1.0.0", + "@azure/logger": "^1.0.0", + "tslib": "^2.2.0" } }, - "@aws-sdk/util-user-agent-node": { - "version": "3.259.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.259.0.tgz", - "integrity": "sha512-R0VTmNs+ySDDebU98BUbsLyeIM5YmAEr9esPpy15XfSy3AWmAeru8nLlztdaLilHZzLIDzvM2t7NGk/FzZFCvA==", - "optional": true, + "@azure/logger": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@azure/logger/-/logger-1.0.4.tgz", + "integrity": "sha512-ustrPY8MryhloQj7OWGe+HrYx+aoiOxzbXTtgblbV3xwCqpzUK36phH3XNHQKj3EPonyFUuDTfR3qFhTEAuZEg==", "requires": { - "@aws-sdk/node-config-provider": "3.259.0", - "@aws-sdk/types": "3.257.0", - "tslib": "^2.3.1" + "tslib": "^2.2.0" } }, - "@aws-sdk/util-utf8": { - "version": "3.254.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-utf8/-/util-utf8-3.254.0.tgz", - "integrity": "sha512-14Kso/eIt5/qfIBmhEL9L1IfyUqswjSTqO2mY7KOzUZ9SZbwn3rpxmtkhmATkRjD7XIlLKaxBkI7tU9Zjzj8Kw==", - "optional": true, + "@azure/msal-browser": { + "version": "2.36.0", + "resolved": "https://registry.npmjs.org/@azure/msal-browser/-/msal-browser-2.36.0.tgz", + "integrity": "sha512-OrVDZ9ftO7ExqZVHripAt+doKg6G14YbP2LoSygiWQoSqoO4CejoXLRLqANc/HGg18N0p/oaRETw4IHZvwsxZw==", "requires": { - "@aws-sdk/util-buffer-from": "3.208.0", - "tslib": "^2.3.1" + "@azure/msal-common": "^12.1.0" + }, + "dependencies": { + "@azure/msal-common": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-12.1.0.tgz", + "integrity": "sha512-9RUiv0evSHvYtvF7r9ksShw9FgCeT6Rf6JB/SOMbMzI0VySZDUBSE+0b9e7DgL2Ph8wSARIh3m8c5pCK9TRY3w==" + } } }, - "@aws-sdk/util-utf8-browser": { - "version": "3.259.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-utf8-browser/-/util-utf8-browser-3.259.0.tgz", - "integrity": "sha512-UvFa/vR+e19XookZF8RzFZBrw2EUkQWxiBW0yYQAhvk3C+QVGl0H3ouca8LDBlBfQKXwmW3huo/59H8rwb1wJw==", - "optional": true, + "@azure/msal-common": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-7.6.0.tgz", + "integrity": "sha512-XqfbglUTVLdkHQ8F9UQJtKseRr3sSnr9ysboxtoswvaMVaEfvyLtMoHv9XdKUfOc0qKGzNgRFd9yRjIWVepl6Q==" + }, + "@azure/msal-node": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/@azure/msal-node/-/msal-node-1.17.1.tgz", + "integrity": "sha512-1lC80yV+Y/gHqkYJ21Qy1Ej/cI/Kt1JcdY0xiM7/+mcEuBAkArR9B1YMY538PMZ5GfyVlYkCHYh/N0CBD5FJlQ==", "requires": { - "tslib": "^2.3.1" + "@azure/msal-common": "^12.1.0", + "jsonwebtoken": "^9.0.0", + "uuid": "^8.3.0" + }, + "dependencies": { + "@azure/msal-common": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-12.1.0.tgz", + "integrity": "sha512-9RUiv0evSHvYtvF7r9ksShw9FgCeT6Rf6JB/SOMbMzI0VySZDUBSE+0b9e7DgL2Ph8wSARIh3m8c5pCK9TRY3w==" + }, + "uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" + } } }, "@babel/parser": { @@ -6269,6 +7484,11 @@ "yargs": "^16.2.0" } }, + "@js-joda/core": { + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/@js-joda/core/-/core-5.5.3.tgz", + "integrity": "sha512-7dqNYwG8gCt4hfg5PKgM7xLEcgSBcx/UgC92OMnhMmvAnq11QzDFPrxUkNR/u5kn17WWLZ8beZ4A3Qrz4pZcmQ==" + }, "@jsdoc/salty": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/@jsdoc/salty/-/salty-0.2.3.tgz", @@ -6332,6 +7552,11 @@ "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==" }, + "@tediousjs/connection-string": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@tediousjs/connection-string/-/connection-string-0.4.2.tgz", + "integrity": "sha512-1R9UC7Qc5wief2oJL+c1+d7v1/oPBayL85u8L/jV2DzIKput1TZ8ZUjj2nxQaSfzu210zp0oFWUrYUiUs8NhBQ==" + }, "@tootallnate/once": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", @@ -6527,6 +7752,15 @@ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "optional": true }, + "array-buffer-byte-length": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz", + "integrity": "sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==", + "requires": { + "call-bind": "^1.0.2", + "is-array-buffer": "^3.0.1" + } + }, "arrify": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", @@ -6546,6 +7780,11 @@ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" }, + "available-typed-arrays": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", + "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==" + }, "axios": { "version": "0.27.2", "resolved": "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz", @@ -6585,6 +7824,27 @@ "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.1.tgz", "integrity": "sha512-pHm4LsMJ6lzgNGVfZHjMoO8sdoRhOzOH4MLmY65Jg70bpxCKu5iOHNJyfF6OyvYw7t8Fpf35RuzUyqnQsj8Vig==" }, + "bl": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-5.1.0.tgz", + "integrity": "sha512-tv1ZJHLfTDnXE6tMHv73YgSJaWR2AFuPwMntBe7XL/GBFHnT0CLnsHMogfk5+GzCDC5ZWarSCYaIGATZt9dNsQ==", + "requires": { + "buffer": "^6.0.3", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + }, + "dependencies": { + "buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + } + } + }, "bluebird": { "version": "3.7.2", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", @@ -6751,6 +8011,20 @@ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "optional": true }, + "define-lazy-prop": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", + "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==" + }, + "define-properties": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.0.tgz", + "integrity": "sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==", + "requires": { + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + } + }, "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -6799,6 +8073,81 @@ "integrity": "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==", "optional": true }, + "es-abstract": { + "version": "1.21.2", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.21.2.tgz", + "integrity": "sha512-y/B5POM2iBnIxCiernH1G7rC9qQoM77lLIMQLuob0zhp8C56Po81+2Nj0WFKnd0pNReDTnkYryc+zhOzpEIROg==", + "requires": { + "array-buffer-byte-length": "^1.0.0", + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "es-set-tostringtag": "^2.0.1", + "es-to-primitive": "^1.2.1", + "function.prototype.name": "^1.1.5", + "get-intrinsic": "^1.2.0", + "get-symbol-description": "^1.0.0", + "globalthis": "^1.0.3", + "gopd": "^1.0.1", + "has": "^1.0.3", + "has-property-descriptors": "^1.0.0", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.5", + "is-array-buffer": "^3.0.2", + "is-callable": "^1.2.7", + "is-negative-zero": "^2.0.2", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.2", + "is-string": "^1.0.7", + "is-typed-array": "^1.1.10", + "is-weakref": "^1.0.2", + "object-inspect": "^1.12.3", + "object-keys": "^1.1.1", + "object.assign": "^4.1.4", + "regexp.prototype.flags": "^1.4.3", + "safe-regex-test": "^1.0.0", + "string.prototype.trim": "^1.2.7", + "string.prototype.trimend": "^1.0.6", + "string.prototype.trimstart": "^1.0.6", + "typed-array-length": "^1.0.4", + "unbox-primitive": "^1.0.2", + "which-typed-array": "^1.1.9" + } + }, + "es-aggregate-error": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/es-aggregate-error/-/es-aggregate-error-1.0.9.tgz", + "integrity": "sha512-fvnX40sb538wdU6r4s35cq4EY6Lr09Upj40BEVem4LEsuW8XgQep9yD5Q1U2KftokNp1rWODFJ2qwZSsAjFpbg==", + "requires": { + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "function-bind": "^1.1.1", + "functions-have-names": "^1.2.3", + "get-intrinsic": "^1.1.3", + "globalthis": "^1.0.3", + "has-property-descriptors": "^1.0.0" + } + }, + "es-set-tostringtag": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz", + "integrity": "sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==", + "requires": { + "get-intrinsic": "^1.1.3", + "has": "^1.0.3", + "has-tostringtag": "^1.0.0" + } + }, + "es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, "escalade": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", @@ -6975,6 +8324,14 @@ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==" }, + "for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "requires": { + "is-callable": "^1.1.3" + } + }, "form-data": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", @@ -6996,12 +8353,28 @@ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" }, + "function.prototype.name": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz", + "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==", + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.0", + "functions-have-names": "^1.2.2" + } + }, "functional-red-black-tree": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==", "optional": true }, + "functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==" + }, "gaxios": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-5.0.2.tgz", @@ -7042,6 +8415,15 @@ "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==" }, + "get-symbol-description": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", + "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", + "requires": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" + } + }, "getopts": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/getopts/-/getopts-2.3.0.tgz", @@ -7060,6 +8442,14 @@ "once": "^1.3.0" } }, + "globalthis": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", + "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", + "requires": { + "define-properties": "^1.1.3" + } + }, "google-auth-library": { "version": "8.7.0", "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-8.7.0.tgz", @@ -7178,6 +8568,14 @@ "node-forge": "^1.3.1" } }, + "gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "requires": { + "get-intrinsic": "^1.1.3" + } + }, "graceful-fs": { "version": "4.2.10", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", @@ -7202,17 +8600,43 @@ "function-bind": "^1.1.1" } }, + "has-bigints": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==" + }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "optional": true }, + "has-property-descriptors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", + "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", + "requires": { + "get-intrinsic": "^1.1.1" + } + }, + "has-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", + "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==" + }, "has-symbols": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==" }, + "has-tostringtag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", + "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "requires": { + "has-symbols": "^1.0.2" + } + }, "hpagent": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/hpagent/-/hpagent-1.2.0.tgz", @@ -7242,6 +8666,14 @@ "debug": "4" } }, + "iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + } + }, "idb": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/idb/-/idb-7.0.1.tgz", @@ -7267,6 +8699,16 @@ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, + "internal-slot": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz", + "integrity": "sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==", + "requires": { + "get-intrinsic": "^1.2.0", + "has": "^1.0.3", + "side-channel": "^1.0.4" + } + }, "interpret": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/interpret/-/interpret-2.2.0.tgz", @@ -7282,6 +8724,38 @@ "resolved": "https://registry.npmjs.org/is/-/is-3.3.0.tgz", "integrity": "sha512-nW24QBoPcFGGHJGUwnfpI7Yc5CdqWNdsyHQszVE/z2pKHXzh7FZ5GWhJqSyaQ9wMkQnsTx+kAI8bHlCX4tKdbg==" }, + "is-array-buffer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz", + "integrity": "sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==", + "requires": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.0", + "is-typed-array": "^1.1.10" + } + }, + "is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "requires": { + "has-bigints": "^1.0.1" + } + }, + "is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "requires": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + } + }, + "is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==" + }, "is-core-module": { "version": "2.11.0", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", @@ -7290,16 +8764,59 @@ "has": "^1.0.3" } }, + "is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "requires": { + "has-tostringtag": "^1.0.0" + } + }, + "is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==" + }, "is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" }, + "is-negative-zero": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", + "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==" + }, + "is-number-object": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "requires": { + "has-tostringtag": "^1.0.0" + } + }, + "is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "requires": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + } + }, "is-retry-allowed": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-2.2.0.tgz", "integrity": "sha512-XVm7LOeLpTW4jV19QSH38vkswxoLud8sQ57YwJVTPWdiaI9I8keEhGFpBlslyVsgdQy4Opg8QOLb8YRgsyZiQg==" }, + "is-shared-array-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", + "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", + "requires": { + "call-bind": "^1.0.2" + } + }, "is-stream": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", @@ -7311,11 +8828,60 @@ "integrity": "sha512-xj0XPvmr7bQFTvirqnFr50o0hQIh6ZItDqloxt5aJrR4NQsYeSsyFQERYGCAzfindAcnKjINnwEEgLx4IqVzQw==", "optional": true }, + "is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "requires": { + "has-tostringtag": "^1.0.0" + } + }, + "is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "requires": { + "has-symbols": "^1.0.2" + } + }, + "is-typed-array": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.10.tgz", + "integrity": "sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==", + "requires": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.0" + } + }, + "is-weakref": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "requires": { + "call-bind": "^1.0.2" + } + }, + "is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "requires": { + "is-docker": "^2.0.0" + } + }, "jose": { "version": "4.11.2", "resolved": "https://registry.npmjs.org/jose/-/jose-4.11.2.tgz", "integrity": "sha512-njj0VL2TsIxCtgzhO+9RRobBvws4oYyCM8TpvoUQwl/MbIM3NFJRR9+e6x0sS5xXaP1t6OCBkaBME98OV9zU5A==" }, + "js-md4": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/js-md4/-/js-md4-0.3.2.tgz", + "integrity": "sha512-/GDnfQYsltsjRswQhN9fhv3EMw2sCpUdrdxyWDOUK7eyD++r3gRhzgiQgc/x4MAv2i1iuQ4lxO5mvqM3vj4bwA==" + }, "js2xmlparser": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/js2xmlparser/-/js2xmlparser-4.0.2.tgz", @@ -7325,6 +8891,11 @@ "xmlcreate": "^2.0.4" } }, + "jsbi": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/jsbi/-/jsbi-4.3.0.tgz", + "integrity": "sha512-SnZNcinB4RIcnEyZqFPdGPVgrg2AcnykiBy0sHVJQKHYeaLUvi3Exj+iaPpLnFVkDPZIV4U0yvgC9/R4uEAZ9g==" + }, "jsdoc": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-4.0.0.tgz", @@ -7636,6 +9207,29 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, + "mssql": { + "version": "9.1.1", + "resolved": "https://registry.npmjs.org/mssql/-/mssql-9.1.1.tgz", + "integrity": "sha512-m0yTx9xzUtTvJpWJHqknUXUDPRnJXZYOOFNygnNIXn1PBkLsC/rkXQdquObd+M0ZPlBhGC00Jg28zG0wCl7VWg==", + "requires": { + "@tediousjs/connection-string": "^0.4.1", + "commander": "^9.4.0", + "debug": "^4.3.3", + "rfdc": "^1.3.0", + "tarn": "^3.0.2", + "tedious": "^15.0.1" + } + }, + "native-duplexpair": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/native-duplexpair/-/native-duplexpair-1.0.0.tgz", + "integrity": "sha512-E7QQoM+3jvNtlmyfqRZ0/U75VFgCls+fSkbml2MpgWkWyz3ox8Y58gNhfuziuQYGNNQAbFZJQck55LHCnCK6CA==" + }, + "node-abort-controller": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/node-abort-controller/-/node-abort-controller-3.1.1.tgz", + "integrity": "sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==" + }, "node-fetch": { "version": "2.6.7", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", @@ -7681,6 +9275,22 @@ "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==" }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" + }, + "object.assign": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", + "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "has-symbols": "^1.0.3", + "object-keys": "^1.1.1" + } + }, "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -7689,6 +9299,16 @@ "wrappy": "1" } }, + "open": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", + "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==", + "requires": { + "define-lazy-prop": "^2.0.0", + "is-docker": "^2.1.1", + "is-wsl": "^2.2.0" + } + }, "optionator": { "version": "0.8.3", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", @@ -7936,6 +9556,16 @@ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" }, + "regexp.prototype.flags": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.0.tgz", + "integrity": "sha512-0SutC3pNudRKgquxGoRGIz946MZVHqbNfPjBdxeOhBrdgDKlRoXmYLQN9xRbrR09ZXWeGAdPuif7egofn6v5LA==", + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "functions-have-names": "^1.2.3" + } + }, "require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -7980,6 +9610,11 @@ "extend": "^3.0.2" } }, + "rfdc": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz", + "integrity": "sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==" + }, "rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", @@ -8029,6 +9664,21 @@ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" }, + "safe-regex-test": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", + "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==", + "requires": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "is-regex": "^1.1.4" + } + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, "saslprep": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/saslprep/-/saslprep-1.0.3.tgz", @@ -8095,6 +9745,16 @@ "resolved": "https://registry.npmjs.org/split2/-/split2-4.1.0.tgz", "integrity": "sha512-VBiJxFkxiXRlUIeyMQi8s4hgvKCSjtknJv/LVYbrgALPwf5zSKmEwV9Lst25AkvMDnvxODugjdl6KZgwKM1WYQ==" }, + "sprintf-js": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.2.tgz", + "integrity": "sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==" + }, + "stoppable": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/stoppable/-/stoppable-1.1.0.tgz", + "integrity": "sha512-KXDYZ9dszj6bzvnEMRYvxgeTHU74QBFL54XKtP3nyMuJ81CFYtABZ3bAzL2EdFUaEwJOBOgENyFj3R7oTzDyyw==" + }, "stream-events": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/stream-events/-/stream-events-1.0.5.tgz", @@ -8131,6 +9791,36 @@ "strip-ansi": "^6.0.1" } }, + "string.prototype.trim": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.7.tgz", + "integrity": "sha512-p6TmeT1T3411M8Cgg9wBTMRtY2q9+PNy9EV1i2lIXUN/btt763oIfxwN3RR8VU6wHX8j/1CFy0L+YuThm6bgOg==", + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + } + }, + "string.prototype.trimend": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz", + "integrity": "sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==", + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + } + }, + "string.prototype.trimstart": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz", + "integrity": "sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==", + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + } + }, "strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -8184,6 +9874,25 @@ "resolved": "https://registry.npmjs.org/tarn/-/tarn-3.0.2.tgz", "integrity": "sha512-51LAVKUSZSVfI05vjPESNc5vwqqZpbXCsU+/+wxlOrUjk2SnFTt97v9ZgQrD4YmxYW1Px6w2KjaDitCfkvgxMQ==" }, + "tedious": { + "version": "15.1.3", + "resolved": "https://registry.npmjs.org/tedious/-/tedious-15.1.3.tgz", + "integrity": "sha512-166EpRm5qknwhEisjZqz/mF7k14fXKJYHRg6XiAXVovd/YkyHJ3SG4Ppy89caPaNFfRr7PVYe+s4dAvKaCMFvw==", + "requires": { + "@azure/identity": "^2.0.4", + "@azure/keyvault-keys": "^4.4.0", + "@js-joda/core": "^5.2.0", + "bl": "^5.0.0", + "es-aggregate-error": "^1.0.8", + "iconv-lite": "^0.6.3", + "js-md4": "^0.3.2", + "jsbi": "^4.3.0", + "native-duplexpair": "^1.0.0", + "node-abort-controller": "^3.0.1", + "punycode": "^2.1.0", + "sprintf-js": "^1.1.2" + } + }, "teeny-request": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-8.0.2.tgz", @@ -8237,6 +9946,16 @@ "prelude-ls": "~1.1.2" } }, + "typed-array-length": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz", + "integrity": "sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==", + "requires": { + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "is-typed-array": "^1.1.9" + } + }, "uc.micro": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", @@ -8249,6 +9968,17 @@ "integrity": "sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==", "optional": true }, + "unbox-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "requires": { + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", + "which-boxed-primitive": "^1.0.2" + } + }, "underscore": { "version": "1.13.6", "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.6.tgz", @@ -8302,6 +10032,31 @@ "webidl-conversions": "^7.0.0" } }, + "which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "requires": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + } + }, + "which-typed-array": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.9.tgz", + "integrity": "sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==", + "requires": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.0", + "is-typed-array": "^1.1.10" + } + }, "word-wrap": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", diff --git a/catalog/destinations/package.json b/catalog/destinations/package.json index 9816c951..ffa28df7 100644 --- a/catalog/destinations/package.json +++ b/catalog/destinations/package.json @@ -12,6 +12,7 @@ "kafkajs": "^2.2.4", "knex": "^2.3.0", "mongodb": "^4.11.0", + "mssql": "^9.1.1", "pg": "^8.8.0", "stripe": "^8.219.0", "uuid": "^9.0.0" From ad3d815f2ba261b9aef6d8aebd5fc711d7545c9d Mon Sep 17 00:00:00 2001 From: Paul Krishnamurthy Date: Thu, 27 Apr 2023 12:44:25 -0400 Subject: [PATCH 24/30] add encrypt option to mssql connection --- catalog/destinations/mssql/mssql.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/catalog/destinations/mssql/mssql.ts b/catalog/destinations/mssql/mssql.ts index a6a16571..87055168 100644 --- a/catalog/destinations/mssql/mssql.ts +++ b/catalog/destinations/mssql/mssql.ts @@ -49,6 +49,9 @@ class MSSQLDriver implements DestinationClassI { password: MSSQL_PASSWORD, port: MSSQL_PORT, database: MSSQL_DATABASE, + options: { + encrypt: true, + }, }, }); From 3d7ac060bfbf5510958fc96f814184e71a116296 Mon Sep 17 00:00:00 2001 From: Paul Krishnamurthy Date: Thu, 27 Apr 2023 14:47:56 -0400 Subject: [PATCH 25/30] update mssql destination server -> host --- catalog/destinations/mssql/config.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/catalog/destinations/mssql/config.json b/catalog/destinations/mssql/config.json index fa2c1610..887d3721 100644 --- a/catalog/destinations/mssql/config.json +++ b/catalog/destinations/mssql/config.json @@ -8,7 +8,7 @@ "tags": ["Microsoft", "db", "sql"], "authentication": [ { - "name": "MSSQL_SERVER", + "name": "MSSQL_HOST", "label": "Enter your MSSQL Server", "placeholder": "my-server" }, From 518085fdc4217a8bdccfb95a7cb49b6fb7685ec4 Mon Sep 17 00:00:00 2001 From: Paul Krishnamurthy Date: Thu, 27 Apr 2023 16:20:01 -0400 Subject: [PATCH 26/30] Format mssql port to number --- catalog/destinations/mssql/mssql.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/catalog/destinations/mssql/mssql.ts b/catalog/destinations/mssql/mssql.ts index 87055168..5aa40a90 100644 --- a/catalog/destinations/mssql/mssql.ts +++ b/catalog/destinations/mssql/mssql.ts @@ -28,7 +28,7 @@ class MSSQLDriver implements DestinationClassI { this.MSSQL_HOST = MSSQL_HOST; this.MSSQL_USERNAME = MSSQL_USERNAME; this.MSSQL_PASSWORD = MSSQL_PASSWORD; - this.MSSQL_PORT = parseFloat(MSSQL_PORT); + this.MSSQL_PORT = Number(MSSQL_PORT); this.MSSQL_DATABASE = MSSQL_DATABASE; } @@ -47,7 +47,7 @@ class MSSQLDriver implements DestinationClassI { host: MSSQL_HOST, user: MSSQL_USERNAME, password: MSSQL_PASSWORD, - port: MSSQL_PORT, + port: Number(MSSQL_PORT), database: MSSQL_DATABASE, options: { encrypt: true, From 3d1a71310d46d8fb8083d1a458a12d084cdc7be6 Mon Sep 17 00:00:00 2001 From: Paul Krishnamurthy Date: Fri, 28 Apr 2023 14:53:52 -0400 Subject: [PATCH 27/30] Update Kafka destination --- catalog/destinations/kafka/docs/connect.md | 4 +-- catalog/destinations/kafka/docs/setup.md | 4 +-- .../docs/transformations/actions/pushData.md | 28 ++++++++++--------- .../kafka/docs/transformations/header.md | 4 +-- catalog/destinations/kafka/kafka.ts | 8 +++++- catalog/destinations/kafka/lib/types.ts | 8 +++--- 6 files changed, 32 insertions(+), 24 deletions(-) diff --git a/catalog/destinations/kafka/docs/connect.md b/catalog/destinations/kafka/docs/connect.md index 869f904c..4cdd1bef 100644 --- a/catalog/destinations/kafka/docs/connect.md +++ b/catalog/destinations/kafka/docs/connect.md @@ -1,10 +1,10 @@ ## Connect -BigQuery is a fully-managed, serverless data warehouse provided by Google Cloud. It allows you to store, manage, and analyze large amounts of data with ease and at a low cost. BigQuery supports both structured and semi-structured data and can handle terabyte-scale datasets with fast query performance. +Kafka is an open-source streaming platform used for building real-time data pipelines and streaming applications. ### IP Whitelisting -To enable Buildable to connect to your database, please ensure that your firewall accepts incoming requests from the following IP addresses: +To enable Buildable to connect to your Kafka Server, please ensure that your firewall accepts incoming requests from the following IP addresses: `35.245.232.82` `35.245.100.81` diff --git a/catalog/destinations/kafka/docs/setup.md b/catalog/destinations/kafka/docs/setup.md index 417ba0b1..4c9efc44 100644 --- a/catalog/destinations/kafka/docs/setup.md +++ b/catalog/destinations/kafka/docs/setup.md @@ -1,10 +1,10 @@ -## BigQuery Destination Setup +## Kafka Destination Setup Check out our [quick start guide](https://docs.buildable.dev/) to learn how to get started. ### IP Whitelisting -To enable Buildable to connect to your database, please ensure that your firewall accepts incoming requests from the following IP addresses: +To enable Buildable to connect to your Kafka Server, please ensure that your firewall accepts incoming requests from the following IP addresses: `35.245.232.82` `35.245.100.81` diff --git a/catalog/destinations/kafka/docs/transformations/actions/pushData.md b/catalog/destinations/kafka/docs/transformations/actions/pushData.md index 75eefbd1..258dd567 100644 --- a/catalog/destinations/kafka/docs/transformations/actions/pushData.md +++ b/catalog/destinations/kafka/docs/transformations/actions/pushData.md @@ -10,25 +10,27 @@ Allows to push data into a Kafka topic interface IKafkaPushData { topic: string; data: string | string[] | Buffer | Buffer[] | AnyObject | AnyObject[]; - headers?: AnyObject - partition?: number - key?: string - timestamp?: string + headers?: AnyObject; + partition?: number; + key?: string; + timestamp?: number; } ``` **Sample Payload** ```json { - "topic": "topic_0", - "data": [{ "id": 1, "name": "John Doe" }], - "headers": { - "header1": "value1", - "header2": "value2" - }, - "partition": 3, - "key": "1", - "timestamp": "2023-01-01T00:00:00.000Z" + "topic": "topic_0", + "data": [{ + "id": 1, + "name": "John Doe" + }], + "headers": { + "header1": "value1", + "header2": "value2" + }, + "partition": 3, + "key": "1" } ``` diff --git a/catalog/destinations/kafka/docs/transformations/header.md b/catalog/destinations/kafka/docs/transformations/header.md index dae23af0..7ee3b64e 100644 --- a/catalog/destinations/kafka/docs/transformations/header.md +++ b/catalog/destinations/kafka/docs/transformations/header.md @@ -1,11 +1,11 @@ ## Documentation ---- -BigQuery is a fully-managed, serverless data warehouse provided by Google Cloud. It allows you to store, manage, and analyze large amounts of data with ease and at a low cost. BigQuery supports both structured and semi-structured data and can handle terabyte-scale datasets with fast query performance. +Kafka is an open-source streaming platform used for building real-time data pipelines and streaming applications ### IP Whitelisting -To enable Buildable to connect to your database, please ensure that your firewall accepts incoming requests from the following IP addresses: +To enable Buildable to connect to your Kafka Server, please ensure that your firewall accepts incoming requests from the following IP addresses: `35.245.232.82` `35.245.100.81` diff --git a/catalog/destinations/kafka/kafka.ts b/catalog/destinations/kafka/kafka.ts index 23c63a6e..a4417c7f 100644 --- a/catalog/destinations/kafka/kafka.ts +++ b/catalog/destinations/kafka/kafka.ts @@ -98,7 +98,13 @@ export class KafkaDriver implements DestinationClassI { async pushData({ topic, data, headers, partition, key, timestamp }: IKafkaPushData) { return this.producer.send({ topic, - messages: [{ value: JSON.stringify(data), headers, partition, key, timestamp }], + messages: [{ + value: JSON.stringify(data), + headers, + partition, + key, + timestamp, + }], }); } } diff --git a/catalog/destinations/kafka/lib/types.ts b/catalog/destinations/kafka/lib/types.ts index 44d19336..23fee7e5 100644 --- a/catalog/destinations/kafka/lib/types.ts +++ b/catalog/destinations/kafka/lib/types.ts @@ -3,8 +3,8 @@ import { AnyObject } from "../../../../types/destinationClassDefinition"; export interface IKafkaPushData { topic: string; data: string | string[] | Buffer | Buffer[] | AnyObject | AnyObject[]; - headers?: AnyObject - partition?: number - key?: string - timestamp?: string + headers?: AnyObject; + partition?: number; + key?: string; + timestamp?: number; } From b886ce4f04fbf25074cd38ee5bc833d650966f05 Mon Sep 17 00:00:00 2001 From: Paul Krishnamurthy Date: Fri, 28 Apr 2023 15:06:27 -0400 Subject: [PATCH 28/30] Update kafka pushData timestamp type --- .../destinations/kafka/docs/transformations/actions/pushData.md | 2 +- catalog/destinations/kafka/lib/types.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/catalog/destinations/kafka/docs/transformations/actions/pushData.md b/catalog/destinations/kafka/docs/transformations/actions/pushData.md index 258dd567..041193d2 100644 --- a/catalog/destinations/kafka/docs/transformations/actions/pushData.md +++ b/catalog/destinations/kafka/docs/transformations/actions/pushData.md @@ -13,7 +13,7 @@ interface IKafkaPushData { headers?: AnyObject; partition?: number; key?: string; - timestamp?: number; + timestamp?: string; // UTC String } ``` diff --git a/catalog/destinations/kafka/lib/types.ts b/catalog/destinations/kafka/lib/types.ts index 23fee7e5..2410e831 100644 --- a/catalog/destinations/kafka/lib/types.ts +++ b/catalog/destinations/kafka/lib/types.ts @@ -6,5 +6,5 @@ export interface IKafkaPushData { headers?: AnyObject; partition?: number; key?: string; - timestamp?: number; + timestamp?: string; } From ec03ddca92383d18ae50862139f217f83b8e59f5 Mon Sep 17 00:00:00 2001 From: Houssem Eddine Zerrad Date: Fri, 28 Apr 2023 20:12:22 +0100 Subject: [PATCH 29/30] extract clientId as a form field in Kafka destination --- catalog/destinations/kafka/config.json | 5 +++++ .../docs/transformations/actions/pushData.md | 2 +- catalog/destinations/kafka/kafka.ts | 20 +++++++++++++++---- catalog/destinations/kafka/lib/types.ts | 2 +- catalog/destinations/kafka/test/index.spec.ts | 10 +++++++++- 5 files changed, 32 insertions(+), 7 deletions(-) diff --git a/catalog/destinations/kafka/config.json b/catalog/destinations/kafka/config.json index 17296857..4354c80a 100644 --- a/catalog/destinations/kafka/config.json +++ b/catalog/destinations/kafka/config.json @@ -21,6 +21,11 @@ "name": "KAFKA_PASSWORD", "label": "Enter your Kafka Password", "placeholder": "mypassword" + }, + { + "name": "KAFKA_CLIENT_ID", + "label": "Enter a Client ID for your connection", + "placeholder": "myclientid" } ], "eventSchema": {}, diff --git a/catalog/destinations/kafka/docs/transformations/actions/pushData.md b/catalog/destinations/kafka/docs/transformations/actions/pushData.md index 258dd567..46835d6f 100644 --- a/catalog/destinations/kafka/docs/transformations/actions/pushData.md +++ b/catalog/destinations/kafka/docs/transformations/actions/pushData.md @@ -13,7 +13,7 @@ interface IKafkaPushData { headers?: AnyObject; partition?: number; key?: string; - timestamp?: number; + timestamp?: number | string; } ``` diff --git a/catalog/destinations/kafka/kafka.ts b/catalog/destinations/kafka/kafka.ts index a4417c7f..c3105007 100644 --- a/catalog/destinations/kafka/kafka.ts +++ b/catalog/destinations/kafka/kafka.ts @@ -1,8 +1,15 @@ -import { Kafka, KafkaConfig, Producer } from "kafkajs"; +import crypto from "crypto"; + +import { Kafka, Producer } from "kafkajs"; import { AnyObject, DestinationClassI, TestConnection, Truthy } from "../../../types/destinationClassDefinition"; import { IKafkaPushData } from "./lib/types"; +function generateRandomHexString(n: number): string { + const bytes = crypto.randomBytes(n / 2); + return bytes.toString("hex"); +} + export class KafkaDriver implements DestinationClassI { public client: Kafka = null; @@ -14,17 +21,20 @@ export class KafkaDriver implements DestinationClassI { public readonly KAFKA_PASSWORD: string; - constructor({ KAFKA_BROKER_URLS, KAFKA_USERNAME, KAFKA_PASSWORD }: AnyObject) { + public readonly KAFKA_CLIENT_ID: string | null = null; + + constructor({ KAFKA_BROKER_URLS, KAFKA_USERNAME, KAFKA_PASSWORD, KAFKA_CLIENT_ID }: AnyObject) { this.KAFKA_BROKER_URLS = KAFKA_BROKER_URLS; this.KAFKA_USERNAME = KAFKA_USERNAME; this.KAFKA_PASSWORD = KAFKA_PASSWORD; + this.KAFKA_CLIENT_ID = KAFKA_CLIENT_ID; } async connect(config?: AnyObject): Promise { const { KAFKA_BROKER_URLS, KAFKA_USERNAME, KAFKA_PASSWORD } = config || this; this.client = new Kafka({ - clientId: "buildable", + clientId: this.KAFKA_CLIENT_ID || `event-${generateRandomHexString(8)}`, brokers: KAFKA_BROKER_URLS.split(",").map((url) => url.trim()), ssl: true, sasl: { @@ -55,7 +65,7 @@ export class KafkaDriver implements DestinationClassI { // If the client is not yet initialized, initialize it if (!this.client) { this.client = new Kafka({ - clientId: "buildable", + clientId: this.KAFKA_CLIENT_ID || `event-${generateRandomHexString(8)}`, brokers: this.KAFKA_BROKER_URLS.split(",").map((url) => url.trim()), ssl: true, sasl: { @@ -96,6 +106,8 @@ export class KafkaDriver implements DestinationClassI { * @param timestamp - Timestamp in UTC format */ async pushData({ topic, data, headers, partition, key, timestamp }: IKafkaPushData) { + timestamp = timestamp ? timestamp.toString() : undefined; + return this.producer.send({ topic, messages: [{ diff --git a/catalog/destinations/kafka/lib/types.ts b/catalog/destinations/kafka/lib/types.ts index 23fee7e5..4e6bd7f7 100644 --- a/catalog/destinations/kafka/lib/types.ts +++ b/catalog/destinations/kafka/lib/types.ts @@ -6,5 +6,5 @@ export interface IKafkaPushData { headers?: AnyObject; partition?: number; key?: string; - timestamp?: number; + timestamp?: number | string; } diff --git a/catalog/destinations/kafka/test/index.spec.ts b/catalog/destinations/kafka/test/index.spec.ts index e1bc8ab5..68895117 100644 --- a/catalog/destinations/kafka/test/index.spec.ts +++ b/catalog/destinations/kafka/test/index.spec.ts @@ -11,6 +11,7 @@ describe("Test: Kafka Destination", () => { KAFKA_BROKER_URLS: process.env.KAFKA_BROKER_URLS, KAFKA_USERNAME: process.env.KAFKA_USERNAME, KAFKA_PASSWORD: process.env.KAFKA_PASSWORD, + KAFKA_CLIENT_ID: process.env.KAFKA_CLIENT_ID, }, ); @@ -28,6 +29,7 @@ describe("Test: Kafka Destination", () => { KAFKA_BROKER_URLS: process.env.KAFKA_BROKER_URLS, KAFKA_USERNAME: process.env.KAFKA_USERNAME, KAFKA_PASSWORD: process.env.KAFKA_PASSWORD, + KAFKA_CLIENT_ID: process.env.KAFKA_CLIENT_ID, }, ); @@ -47,6 +49,7 @@ describe("Test: Kafka Destination", () => { KAFKA_BROKER_URLS: process.env.KAFKA_BROKER_URLS, KAFKA_USERNAME: process.env.KAFKA_USERNAME, KAFKA_PASSWORD: process.env.KAFKA_PASSWORD, + KAFKA_CLIENT_ID: process.env.KAFKA_CLIENT_ID, }, ); }); @@ -65,7 +68,9 @@ describe("Test: Kafka Destination", () => { it("should accept config passed as arguments", async () => { return driver.connect({ KAFKA_BROKER_URLS: process.env.KAFKA_BROKER_URLS, KAFKA_USERNAME: process.env.KAFKA_USERNAME, - KAFKA_PASSWORD: process.env.KAFKA_PASSWORD }).then(async () => { + KAFKA_PASSWORD: process.env.KAFKA_PASSWORD, + KAFKA_CLIENT_ID: process.env.KAFKA_CLIENT_ID, + }).then(async () => { const result = await driver.testConnection(); expect(result.success).toBeTruthy(); @@ -80,6 +85,7 @@ describe("Test: Kafka Destination", () => { KAFKA_BROKER_URLS: process.env.KAFKA_BROKER_URLS, KAFKA_USERNAME: process.env.KAFKA_USERNAME, KAFKA_PASSWORD: process.env.KAFKA_PASSWORD, + KAFKA_CLIENT_ID: process.env.KAFKA_CLIENT_ID, }, ); @@ -104,6 +110,7 @@ describe("Test: Kafka Destination", () => { KAFKA_BROKER_URLS: process.env.KAFKA_BROKER_URLS, KAFKA_USERNAME: process.env.KAFKA_USERNAME, KAFKA_PASSWORD: process.env.KAFKA_PASSWORD, + KAFKA_CLIENT_ID: process.env.KAFKA_CLIENT_ID, }, ); }); @@ -161,6 +168,7 @@ describe("Test: Kafka Destination", () => { KAFKA_BROKER_URLS: process.env.KAFKA_BROKER_URLS, KAFKA_USERNAME: process.env.KAFKA_USERNAME, KAFKA_PASSWORD: process.env.KAFKA_PASSWORD, + KAFKA_CLIENT_ID: process.env.KAFKA_CLIENT_ID, }, ); From 63de5f7f1be79f17868b9d5d3d2964b10bc73edc Mon Sep 17 00:00:00 2001 From: Houssem Eddine Zerrad Date: Fri, 28 Apr 2023 20:27:37 +0100 Subject: [PATCH 30/30] add a default value to clientId in Kafka destination --- catalog/destinations/kafka/config.json | 3 ++- catalog/sources/ftp/config.json | 10 +++------- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/catalog/destinations/kafka/config.json b/catalog/destinations/kafka/config.json index 4354c80a..07df4024 100644 --- a/catalog/destinations/kafka/config.json +++ b/catalog/destinations/kafka/config.json @@ -25,7 +25,8 @@ { "name": "KAFKA_CLIENT_ID", "label": "Enter a Client ID for your connection", - "placeholder": "myclientid" + "placeholder": "myclientid", + "value": "event" } ], "eventSchema": {}, diff --git a/catalog/sources/ftp/config.json b/catalog/sources/ftp/config.json index 83b113ae..b903bb85 100644 --- a/catalog/sources/ftp/config.json +++ b/catalog/sources/ftp/config.json @@ -40,12 +40,8 @@ "name": "FTP_SCAN_INTERVAL", "label": "Select the FTP Scan Interval", "type": "select", - "value": "*/1 * * * *", + "value": "*/15 * * * *", "options": [ - { - "value": "*/1 * * * *", - "name": "At every minute (Sandbox Feature)" - }, { "value": "*/15 * * * *", "name": "At every 15th minute" @@ -175,8 +171,8 @@ "name": "FTP_EXTRACTOR_FILE_SIZE_LIMIT", "label": "Enter the Maximum Allowed Extractor File Size", "subText": "The maximum allowed value is 5GB", - "placeholder": "2mb", - "value": "2mb", + "placeholder": "50mb", + "value": "50mb", "required": false, "disabled": true }