From 453baf78a174740878943220cf0877b5fcb06c6d Mon Sep 17 00:00:00 2001 From: Adrian Pilarczyk Date: Mon, 17 Apr 2023 13:58:21 +0200 Subject: [PATCH] feat: finalize order process (#355) * feat: :sparkles: add dummy order-created * refactor: :fire: unused private-providers-configuration-service * feat: :sparkles: add dummy order-fulfilled * refactor: :truck: move provider-config * refactor: :truck: crudSettingsConfigurator -> crudSettingsManager * refactor: :recycle: [tax-provider].ts -> [tax-provider]-webhook.service.ts * feat: :sparkles: add dummy createOrder * refactor: :recycle: distinguish between salesOrder and salesInvoice in avatax * refactor: :truck: [provider]-calculate.ts to [provider]-transform.ts * refactor: :truck: ResponseTaxPayload to tax-provider-webhook.ts * refactor: :truck: ResponseTaxPayload -> CalculateTaxesResponse * refactor: :recycle: webhooks with active-tax-provider.service.ts * feat: :sparkles: add skeleton orderCreate functionality * refactor: :recycle: [provider]-transform.ts -> [provider]-[webhook]-transform.ts * feat: :sparkles: add order-fulfilled with avatax call * refactor: :recycle: move getActiveTaxProvider to active-tax-provider * refactor: :label: export types for [provider]-client function args * refactor: :truck: UpdateAppMetadata -> UpdateMetadata * feat: :sparkles: fulfill order with id from metadata * build: :arrow_up: upgrade avatax * feat: :sparkles: commit transaction on fulfill in avatax * fix: :bug: return of webhooks to ensure valid retry behavior * refactor: :truck: [provider]-[webhook]-transform -> [provider]-[webhook]-map * refactor: :label: export types of avatax-calculate-taxes mapPayload * refactor: :recycle: extract address-map to separate function * refactor: :recycle: remove schema.ts * refactor: :recycle: move addressSchema to channels-config.ts * feat: :sparkles: add tests & placeholder tests for avatax & taxjar maps * refactor: :recycle: throw error if no metadata * refactor: :recycle: change EXTERNAL_ID_KEY to PROVIDER_ORDER_ID_KEY add comments * refactor: :recycle: comments -> it.todo in tests * refactor: :bulb: add comment about shipping_item_code * refactor: :white_check_mark: add todo items for tests * refactor: :recycle: remove export and add sumLines to taxJarOrderCreated * refactor: :recycle: address-map with avatarAddressFactory * docs: :bulb: add comment about MOCKED_SALEOR_PAYLOAD * refactor: :recycle: remove export of mapLines and add to avataxCalculateTaxes * style: :art: add newline-after-var warn to eslint-config-saleor * style: :art: autofix newline-after-var in taxes * test: :white_check_mark: restructure tests according to new naming in address-map * refactor: :recycle: add shippingItemCode to avataxCalculateTaxes wrapper object * refactor: :truck: payloadProps -> payloadArgs * refactor: :recycle: add Maps suffix to map wrapper objects * refactor: :recycle: remove data: null from ActiveTaxProviderResult * refactor: :recycle: maintain the object hierarchy in tests * refactor: :recycle: refactor webhook responses with WebhookResponseFactory * build: :arrow_up: vitest * test: :white_check_mark: add tests for get-app-config-test * test: :white_check_mark: add tests for getActiveTaxProvider * refactor: :recycle: use address fragment for taxBase and order * refactor: :recycle: rename WebhookResponseFactory -> WebhookResponse * style: :construction_worker: add multiline-comment-style * fix: :bug: dummy test in get-app-config.test.ts * refactor: :recycle: rename AddressFragment -> Address * refactor: :recycle: use debug instead of error in webhook-response noRetry * refactor: :recycle: refactor as variables in mutation * build: :construction_worker: add changeset * refactor: :recycle: split changesets in two * build: :arrow_up: vite * build: :arrow_up: vite && vitest in all apps --- .changeset/polite-mugs-perform.md | 5 + .changeset/tender-monkeys-deny.md | 5 + apps/cms/package.json | 4 +- apps/crm/package.json | 4 +- apps/data-importer/package.json | 4 +- apps/emails-and-messages/package.json | 4 +- apps/invoices/package.json | 6 +- apps/monitoring/package.json | 4 +- apps/products-feed/package.json | 4 +- apps/taxes/graphql/fragments/Address.graphql | 4 +- apps/taxes/graphql/fragments/TaxBase.graphql | 21 +- ...etadata.graphql => UpdateMetadata.graphql} | 2 +- .../subscriptions/OrderCreated.graphql | 21 +- .../subscriptions/OrderFulfilled.graphql | 70 ++ apps/taxes/package.json | 8 +- apps/taxes/src/lib/saleor/schema.ts | 89 -- .../app-configurator.ts | 0 .../src/modules/app/get-app-config.test.ts | 77 ++ .../get-app-config.ts | 11 +- .../metadata-manager.ts | 14 +- .../taxes/src/modules/app/webhook-response.ts | 28 + .../src/modules/avatax/avatax-calculate.ts | 112 -- .../taxes/src/modules/avatax/avatax-client.ts | 24 +- .../taxes/src/modules/avatax/avatax-config.ts | 4 +- .../avatax/avatax-configuration.router.ts | 1 + .../avatax/avatax-configuration.service.ts | 25 +- .../src/modules/avatax/avatax-provider.ts | 34 - .../modules/avatax/avatax-webhook.service.ts | 67 + .../avatax/maps/address-factory.test.ts | 70 ++ .../modules/avatax/maps/address-factory.ts | 31 + .../maps/avatax-calculate-taxes-map.test.ts | 126 ++ .../avatax/maps/avatax-calculate-taxes-map.ts | 112 ++ .../maps/avatax-order-created-map.test.ts | 14 + .../avatax/maps/avatax-order-created-map.ts | 56 + .../maps/avatax-order-fulfilled-map.test.ts | 14 + .../avatax/maps/avatax-order-fulfilled-map.ts | 37 + .../avatax/ui/avatax-configuration-form.tsx | 14 +- .../channels-configuration/channels-config.ts | 11 +- .../channels-configuration.router.ts | 4 +- .../channels-configurator.ts | 2 +- .../get-channels-configuration.service.ts | 3 +- .../channels/ui/channel-tax-provider-form.tsx | 1 + .../channels/ui/channel-tax-provider.tsx | 4 +- .../crud-settings/crud-settings.service.ts | 6 +- ...private-providers-configuration-service.ts | 28 - .../public-providers-configuration-service.ts | 1 + .../ui/configuration.tsx | 3 +- .../ui/delete-provider-dialog.tsx | 1 + .../ui/provider-icon.tsx | 3 +- .../modules/taxes/active-tax-provider.test.ts | 149 +++ .../src/modules/taxes/active-tax-provider.ts | 94 +- apps/taxes/src/modules/taxes/numbers.test.ts | 22 + apps/taxes/src/modules/taxes/numbers.ts | 7 + .../config.ts => provider-config.ts} | 2 +- .../src/modules/taxes/tax-common-schema.ts | 9 - .../src/modules/taxes/tax-line-resolver.ts | 1 + .../src/modules/taxes/tax-provider-webhook.ts | 26 + apps/taxes/src/modules/taxes/tax-provider.ts | 9 - apps/taxes/src/modules/taxes/types.ts | 3 - .../maps/taxjar-calculate-taxes-map.test.ts | 14 + .../taxjar-calculate-taxes-map.ts} | 89 +- .../maps/taxjar-order-created-map.test.ts | 175 +++ .../taxjar/maps/taxjar-order-created-map.ts | 76 ++ .../taxes/src/modules/taxjar/taxjar-client.ts | 24 +- .../taxjar/taxjar-configuration.router.ts | 1 + .../taxjar/taxjar-configuration.service.ts | 27 +- .../src/modules/taxjar/taxjar-provider.ts | 42 - .../modules/taxjar/taxjar-webhook.service.ts | 48 + .../taxjar/ui/taxjar-configuration-form.tsx | 2 + apps/taxes/src/modules/ui/app-paper.tsx | 1 + apps/taxes/src/modules/ui/instructions.tsx | 1 + apps/taxes/src/pages/_app.tsx | 1 + apps/taxes/src/pages/_error.js | 22 +- apps/taxes/src/pages/api/manifest.ts | 4 + .../api/webhooks/checkout-calculate-taxes.ts | 76 +- .../api/webhooks/order-calculate-taxes.ts | 75 +- .../src/pages/api/webhooks/order-created.ts | 110 ++ .../src/pages/api/webhooks/order-fulfilled.ts | 61 + packages/eslint-config-saleor/index.js | 2 + pnpm-lock.yaml | 1088 +++++++---------- 80 files changed, 2246 insertions(+), 1208 deletions(-) create mode 100644 .changeset/polite-mugs-perform.md create mode 100644 .changeset/tender-monkeys-deny.md rename apps/taxes/graphql/mutations/{UpdateAppMetadata.graphql => UpdateMetadata.graphql} (66%) create mode 100644 apps/taxes/graphql/subscriptions/OrderFulfilled.graphql delete mode 100644 apps/taxes/src/lib/saleor/schema.ts rename apps/taxes/src/modules/{app-configuration => app}/app-configurator.ts (100%) create mode 100644 apps/taxes/src/modules/app/get-app-config.test.ts rename apps/taxes/src/modules/{app-configuration => app}/get-app-config.ts (74%) rename apps/taxes/src/modules/{app-configuration => app}/metadata-manager.ts (89%) create mode 100644 apps/taxes/src/modules/app/webhook-response.ts delete mode 100644 apps/taxes/src/modules/avatax/avatax-calculate.ts delete mode 100644 apps/taxes/src/modules/avatax/avatax-provider.ts create mode 100644 apps/taxes/src/modules/avatax/avatax-webhook.service.ts create mode 100644 apps/taxes/src/modules/avatax/maps/address-factory.test.ts create mode 100644 apps/taxes/src/modules/avatax/maps/address-factory.ts create mode 100644 apps/taxes/src/modules/avatax/maps/avatax-calculate-taxes-map.test.ts create mode 100644 apps/taxes/src/modules/avatax/maps/avatax-calculate-taxes-map.ts create mode 100644 apps/taxes/src/modules/avatax/maps/avatax-order-created-map.test.ts create mode 100644 apps/taxes/src/modules/avatax/maps/avatax-order-created-map.ts create mode 100644 apps/taxes/src/modules/avatax/maps/avatax-order-fulfilled-map.test.ts create mode 100644 apps/taxes/src/modules/avatax/maps/avatax-order-fulfilled-map.ts delete mode 100644 apps/taxes/src/modules/providers-configuration/private-providers-configuration-service.ts create mode 100644 apps/taxes/src/modules/taxes/active-tax-provider.test.ts create mode 100644 apps/taxes/src/modules/taxes/numbers.test.ts create mode 100644 apps/taxes/src/modules/taxes/numbers.ts rename apps/taxes/src/modules/taxes/{providers/config.ts => provider-config.ts} (78%) delete mode 100644 apps/taxes/src/modules/taxes/tax-common-schema.ts create mode 100644 apps/taxes/src/modules/taxes/tax-provider-webhook.ts delete mode 100644 apps/taxes/src/modules/taxes/tax-provider.ts delete mode 100644 apps/taxes/src/modules/taxes/types.ts create mode 100644 apps/taxes/src/modules/taxjar/maps/taxjar-calculate-taxes-map.test.ts rename apps/taxes/src/modules/taxjar/{taxjar-calculate.ts => maps/taxjar-calculate-taxes-map.ts} (60%) create mode 100644 apps/taxes/src/modules/taxjar/maps/taxjar-order-created-map.test.ts create mode 100644 apps/taxes/src/modules/taxjar/maps/taxjar-order-created-map.ts delete mode 100644 apps/taxes/src/modules/taxjar/taxjar-provider.ts create mode 100644 apps/taxes/src/modules/taxjar/taxjar-webhook.service.ts create mode 100644 apps/taxes/src/pages/api/webhooks/order-created.ts create mode 100644 apps/taxes/src/pages/api/webhooks/order-fulfilled.ts diff --git a/.changeset/polite-mugs-perform.md b/.changeset/polite-mugs-perform.md new file mode 100644 index 0000000..43ae269 --- /dev/null +++ b/.changeset/polite-mugs-perform.md @@ -0,0 +1,5 @@ +--- +"saleor-app-taxes": minor +--- + +Completed the order flow with new webhooks: `order_created` and `order_fulfilled`. In TaxJar, an order will be created on `order_created` with no actions on `order_fulfilled`. In Avatax, a transaction will be created on `order_created` and commited on `order_fulfilled`. diff --git a/.changeset/tender-monkeys-deny.md b/.changeset/tender-monkeys-deny.md new file mode 100644 index 0000000..a5ab0d4 --- /dev/null +++ b/.changeset/tender-monkeys-deny.md @@ -0,0 +1,5 @@ +--- +"eslint-config-saleor": minor +--- + +Added new warn rules to eslint-config-saleor: `newline-after-var` and `multiline-comment-style`. diff --git a/apps/cms/package.json b/apps/cms/package.json index d381a28..99bac57 100644 --- a/apps/cms/package.json +++ b/apps/cms/package.json @@ -38,7 +38,7 @@ "urql": "^3.0.3", "usehooks-ts": "^2.9.1", "uuid": "^9.0.0", - "vite": "^4.1.4", + "vite": "^4.2.1", "zod": "^3.19.1" }, "devDependencies": { @@ -62,7 +62,7 @@ "jsdom": "^20.0.3", "prettier": "^2.7.1", "typescript": "4.9", - "vitest": "^0.29.2" + "vitest": "^0.30.1" }, "lint-staged": { "*.{js,ts,tsx}": "eslint --cache --fix", diff --git a/apps/crm/package.json b/apps/crm/package.json index 17749ee..a06cf1f 100644 --- a/apps/crm/package.json +++ b/apps/crm/package.json @@ -40,8 +40,8 @@ "react-is": "^18.2.0", "urql": "^3.0.3", "usehooks-ts": "^2.9.1", - "vite": "^4.0.4", - "vitest": "^0.27.1", + "vite": "^4.2.1", + "vitest": "^0.30.1", "zod": "^3.20.2" }, "devDependencies": { diff --git a/apps/data-importer/package.json b/apps/data-importer/package.json index a3f8075..06ae9df 100644 --- a/apps/data-importer/package.json +++ b/apps/data-importer/package.json @@ -37,8 +37,8 @@ "react-dom": "18.2.0", "urql": "^3.0.3", "usehooks-ts": "^2.9.1", - "vite": "^4.1.1", - "vitest": "^0.28.4", + "vite": "^4.2.1", + "vitest": "^0.30.1", "zod": "^3.20.2" }, "devDependencies": { diff --git a/apps/emails-and-messages/package.json b/apps/emails-and-messages/package.json index f90243d..79fdf70 100644 --- a/apps/emails-and-messages/package.json +++ b/apps/emails-and-messages/package.json @@ -50,8 +50,8 @@ "react-query": "^3.39.3", "urql": "^3.0.3", "usehooks-ts": "^2.9.1", - "vite": "^4.0.4", - "vitest": "^0.27.1", + "vite": "^4.2.1", + "vitest": "^0.30.1", "zod": "^3.20.2" }, "devDependencies": { diff --git a/apps/invoices/package.json b/apps/invoices/package.json index 27222d9..523cb0e 100644 --- a/apps/invoices/package.json +++ b/apps/invoices/package.json @@ -68,9 +68,9 @@ "jsdom": "^20.0.3", "rimraf": "^3.0.2", "typescript": "4.9.5", - "vite": "^4.1.1", - "vitest": "^0.28.4", - "@types/semver": "^7.3.13" + "vite": "^4.2.1", + "vitest": "^0.30.1", +"@types/semver": "^7.3.13" }, "lint-staged": { "*.{js,ts,tsx}": "eslint --cache --fix", diff --git a/apps/monitoring/package.json b/apps/monitoring/package.json index eb9d943..e946483 100644 --- a/apps/monitoring/package.json +++ b/apps/monitoring/package.json @@ -31,8 +31,8 @@ "react-dom": "18.2.0", "react-hook-form": "^7.42.1", "urql": "^3.0.3", - "vite": "^4.0.4", - "vitest": "^0.27.1" + "vite": "^4.2.1", + "vitest": "^0.30.1" }, "devDependencies": { "@graphql-codegen/cli": "3.2.2", diff --git a/apps/products-feed/package.json b/apps/products-feed/package.json index c37f649..da8e0c5 100644 --- a/apps/products-feed/package.json +++ b/apps/products-feed/package.json @@ -44,8 +44,8 @@ "react-is": "^18.2.0", "urql": "^3.0.3", "usehooks-ts": "^2.9.1", - "vite": "^4.0.4", - "vitest": "^0.27.1", + "vite": "^4.2.1", + "vitest": "^0.30.1", "zod": "^3.20.2" }, "devDependencies": { diff --git a/apps/taxes/graphql/fragments/Address.graphql b/apps/taxes/graphql/fragments/Address.graphql index 2017cfc..b87337f 100644 --- a/apps/taxes/graphql/fragments/Address.graphql +++ b/apps/taxes/graphql/fragments/Address.graphql @@ -1,4 +1,4 @@ -fragment AddressFragment on Address { +fragment Address on Address { streetAddress1 streetAddress2 city @@ -7,4 +7,4 @@ fragment AddressFragment on Address { country { code } -} \ No newline at end of file +} diff --git a/apps/taxes/graphql/fragments/TaxBase.graphql b/apps/taxes/graphql/fragments/TaxBase.graphql index fd669d1..fda51bd 100644 --- a/apps/taxes/graphql/fragments/TaxBase.graphql +++ b/apps/taxes/graphql/fragments/TaxBase.graphql @@ -51,14 +51,7 @@ fragment TaxBase on TaxableObject { ...TaxDiscount } address { - streetAddress1 - streetAddress2 - city - country { - code - } - countryArea - postalCode + ...Address } shippingPrice { amount @@ -66,4 +59,16 @@ fragment TaxBase on TaxableObject { lines { ...TaxBaseLine } + sourceObject { + ... on Checkout { + user { + id + } + } + ... on Order { + user { + id + } + } + } } diff --git a/apps/taxes/graphql/mutations/UpdateAppMetadata.graphql b/apps/taxes/graphql/mutations/UpdateMetadata.graphql similarity index 66% rename from apps/taxes/graphql/mutations/UpdateAppMetadata.graphql rename to apps/taxes/graphql/mutations/UpdateMetadata.graphql index 718ec63..76281b0 100644 --- a/apps/taxes/graphql/mutations/UpdateAppMetadata.graphql +++ b/apps/taxes/graphql/mutations/UpdateMetadata.graphql @@ -1,4 +1,4 @@ -mutation UpdateAppMetadata($id: ID!, $input: [MetadataInput!]!) { +mutation UpdateMetadata($id: ID!, $input: [MetadataInput!]!) { updatePrivateMetadata(id: $id, input: $input) { item { privateMetadata { diff --git a/apps/taxes/graphql/subscriptions/OrderCreated.graphql b/apps/taxes/graphql/subscriptions/OrderCreated.graphql index 0809eb0..10c5ea1 100644 --- a/apps/taxes/graphql/subscriptions/OrderCreated.graphql +++ b/apps/taxes/graphql/subscriptions/OrderCreated.graphql @@ -14,21 +14,26 @@ fragment OrderLineFragment on OrderLine { } } -fragment OrderSubscription on Order { +fragment OrderCreatedSubscription on Order { id - userEmail + user { + id + email + } created + status channel { id slug } shippingAddress { - ...AddressFragment + ...Address } billingAddress { - ...AddressFragment + ...Address } total { + currency net { amount } @@ -49,7 +54,13 @@ fragment OrderCreatedEventSubscription on Event { __typename ... on OrderCreated { order { - ...OrderSubscription + ...OrderCreatedSubscription + } + } + recipient { + privateMetadata { + key + value } } } diff --git a/apps/taxes/graphql/subscriptions/OrderFulfilled.graphql b/apps/taxes/graphql/subscriptions/OrderFulfilled.graphql new file mode 100644 index 0000000..61c2357 --- /dev/null +++ b/apps/taxes/graphql/subscriptions/OrderFulfilled.graphql @@ -0,0 +1,70 @@ +fragment OrderLineFragment on OrderLine { + productSku + productName + quantity + unitPrice { + net { + amount + } + } + totalPrice { + tax { + amount + } + } +} + +fragment OrderFulfilledSubscription on Order { + id + userEmail + created + channel { + id + slug + } + shippingAddress { + ...Address + } + billingAddress { + ...Address + } + total { + net { + amount + } + tax { + amount + } + } + shippingPrice { + net { + amount + } + } + privateMetadata { + key + value + } + lines { + ...OrderLineFragment + } +} +fragment OrderFulfilledEventSubscription on Event { + __typename + ... on OrderFulfilled { + order { + ...OrderFulfilledSubscription + } + recipient { + privateMetadata { + key + value + } + } + } +} +subscription OrderFulfilledSubscription { + event { + ...OrderFulfilledEventSubscription + } +} diff --git a/apps/taxes/package.json b/apps/taxes/package.json index 0885f9c..bcd7af4 100644 --- a/apps/taxes/package.json +++ b/apps/taxes/package.json @@ -30,7 +30,7 @@ "@trpc/server": "^10.9.0", "@urql/exchange-auth": "^1.0.0", "@urql/exchange-multipart-fetch": "^1.0.1", - "avatax": "^23.2.0", + "avatax": "^23.3.2", "clsx": "^1.2.1", "graphql": "^16.6.0", "graphql-tag": "^2.12.6", @@ -44,8 +44,8 @@ "taxjar": "^4.0.1", "urql": "^3.0.3", "usehooks-ts": "^2.9.1", - "vite": "^4.0.1", - "vitest": "^0.25.8", + "vite": "^4.2.1", + "vitest": "^0.30.1", "zod": "^3.20.2" }, "devDependencies": { @@ -63,7 +63,7 @@ "@types/node": "^18.8.1", "@types/react": "^18.0.21", "@types/react-dom": "^18.0.6", - "@vitejs/plugin-react": "^3.0.0", + "@vitejs/plugin-react": "^3.1.0", "eslint": "8.25.0", "eslint-config-next": "12.3.1", "eslint-config-prettier": "^8.5.0", diff --git a/apps/taxes/src/lib/saleor/schema.ts b/apps/taxes/src/lib/saleor/schema.ts deleted file mode 100644 index c44d23a..0000000 --- a/apps/taxes/src/lib/saleor/schema.ts +++ /dev/null @@ -1,89 +0,0 @@ -import { z } from "zod"; -import { CalculateTaxesEventFragment } from "../../../generated/graphql"; - -export type ExpectedWebhookPayload = Extract< - CalculateTaxesEventFragment, - { __typename: "CalculateTaxes" } ->; - -const taxDiscountSchema = z.object({ - name: z.string(), - amount: z.object({ - amount: z.number(), - }), -}); -const checkoutBaseLineSchema = z.object({ - __typename: z.literal("CheckoutLine"), - id: z.string(), - productVariant: z.object({ - id: z.string(), - product: z.object({ - metafield: z.string().nullable(), - productType: z.object({ - metafield: z.string().nullable(), - }), - }), - }), -}); - -const orderBaseLineSchema = z.object({ - __typename: z.literal("OrderLine"), - id: z.string(), - variant: z.object({ - id: z.string(), - product: z.object({ - metafield: z.string().nullable(), - productType: z.object({ - metafield: z.string().nullable(), - }), - }), - }), -}); - -const taxAddressSchema = z.object({ - streetAddress1: z.string(), - streetAddress2: z.string(), - city: z.string(), - country: z.object({ - code: z.string(), - }), - countryArea: z.string(), - postalCode: z.string(), -}); - -const taxBaseLineSchema = z.object({ - chargeTaxes: z.boolean(), - quantity: z.number(), - unitPrice: z.object({ - amount: z.number(), - }), - totalPrice: z.object({ - amount: z.number(), - }), - sourceLine: z.union([checkoutBaseLineSchema, orderBaseLineSchema]), -}); - -export const calculateTaxesPayloadSchema: z.ZodType = z.object({ - __typename: z.literal("CalculateTaxes"), - recipient: z.object({ - privateMetadata: z.array( - z.object({ - key: z.string(), - value: z.string(), - }) - ), - }), - taxBase: z.object({ - currency: z.string(), - channel: z.object({ - slug: z.string(), - }), - __typename: z.literal("TaxableObject").optional(), - discounts: z.array(taxDiscountSchema), - address: taxAddressSchema, - shippingPrice: z.object({ - amount: z.number(), - }), - lines: z.array(taxBaseLineSchema).min(1), - }), -}); diff --git a/apps/taxes/src/modules/app-configuration/app-configurator.ts b/apps/taxes/src/modules/app/app-configurator.ts similarity index 100% rename from apps/taxes/src/modules/app-configuration/app-configurator.ts rename to apps/taxes/src/modules/app/app-configurator.ts diff --git a/apps/taxes/src/modules/app/get-app-config.test.ts b/apps/taxes/src/modules/app/get-app-config.test.ts new file mode 100644 index 0000000..421e9eb --- /dev/null +++ b/apps/taxes/src/modules/app/get-app-config.test.ts @@ -0,0 +1,77 @@ +import { encrypt } from "@saleor/app-sdk/settings-manager"; +import { getAppConfig } from "./get-app-config"; +import { describe, expect, it, vi } from "vitest"; +import { ProvidersConfig } from "../providers-configuration/providers-config"; +import { MetadataItem } from "../../../generated/graphql"; +import { ChannelsConfig } from "../channels-configuration/channels-config"; + +const mockedSecretKey = "test_secret_key"; +const mockedProviders: ProvidersConfig = [ + { + provider: "avatax", + id: "1", + config: { + companyCode: "DEFAULT", + isAutocommit: false, + isSandbox: true, + name: "avatax-1", + password: "avatax-password", + username: "avatax-username", + }, + }, + { + provider: "taxjar", + id: "2", + config: { + name: "taxjar-1", + apiKey: "taxjar-api-key", + isSandbox: true, + }, + }, +]; +const mockedEncryptedProviders = encrypt(JSON.stringify(mockedProviders), mockedSecretKey); + +const mockedChannels: ChannelsConfig = { + "default-channel": { + address: { + city: "New York", + country: "US", + state: "NY", + street: "123 Main St", + zip: "10001", + }, + enabled: true, + providerInstanceId: "1", + }, +}; + +const mockedEncryptedChannels = encrypt(JSON.stringify(mockedChannels), mockedSecretKey); + +const mockedMetadata: MetadataItem[] = [ + { + key: "providers", + value: mockedEncryptedProviders, + }, + { + key: "channels", + value: mockedEncryptedChannels, + }, +]; + +vi.stubEnv("SECRET_KEY", mockedSecretKey); + +describe("getAppConfig", () => { + it("should return empty providers and channels config when no metadata", () => { + const { providers, channels } = getAppConfig([]); + + expect(providers).toEqual([]); + expect(channels).toEqual({}); + }); + + it("should return decrypted providers and channels config when metadata provided", () => { + const { providers, channels } = getAppConfig(mockedMetadata); + + expect(providers).toEqual(mockedProviders); + expect(channels).toEqual(mockedChannels); + }); +}); diff --git a/apps/taxes/src/modules/app-configuration/get-app-config.ts b/apps/taxes/src/modules/app/get-app-config.ts similarity index 74% rename from apps/taxes/src/modules/app-configuration/get-app-config.ts rename to apps/taxes/src/modules/app/get-app-config.ts index 91b8320..0c44927 100644 --- a/apps/taxes/src/modules/app-configuration/get-app-config.ts +++ b/apps/taxes/src/modules/app/get-app-config.ts @@ -1,10 +1,9 @@ import { decrypt } from "@saleor/app-sdk/settings-manager"; -import { ExpectedWebhookPayload } from "../../lib/saleor/schema"; +import { MetadataItem } from "../../../generated/graphql"; import { ChannelsConfig, channelsSchema } from "../channels-configuration/channels-config"; import { ProvidersConfig, providersSchema } from "../providers-configuration/providers-config"; -export const getAppConfig = (payload: ExpectedWebhookPayload) => { - const metadata = payload.recipient?.privateMetadata; +export const getAppConfig = (metadata: MetadataItem[]) => { let providersConfig = [] as ProvidersConfig; let channelsConfig = {} as ChannelsConfig; @@ -14,8 +13,10 @@ export const getAppConfig = (payload: ExpectedWebhookPayload) => { throw new Error("SECRET_KEY env variable is not set"); } - // * The App Config contains two types of data: providers and channels. - // * We must recognize which one we are dealing with and parse it accordingly. + /** + * The App Config contains two types of data: providers and channels. + * We must recognize which one we are dealing with and parse it accordingly. + */ metadata?.forEach((item) => { const decrypted = decrypt(item.value, secretKey); const parsed = JSON.parse(decrypted); diff --git a/apps/taxes/src/modules/app-configuration/metadata-manager.ts b/apps/taxes/src/modules/app/metadata-manager.ts similarity index 89% rename from apps/taxes/src/modules/app-configuration/metadata-manager.ts rename to apps/taxes/src/modules/app/metadata-manager.ts index 9cf6a4c..ac27f46 100644 --- a/apps/taxes/src/modules/app-configuration/metadata-manager.ts +++ b/apps/taxes/src/modules/app/metadata-manager.ts @@ -3,12 +3,13 @@ import { Client } from "urql"; import { FetchAppDetailsDocument, FetchAppDetailsQuery, - UpdateAppMetadataDocument, + UpdateMetadataDocument, } from "../../../generated/graphql"; import { logger as pinoLogger } from "../../lib/logger"; export async function fetchAllMetadata(client: Client): Promise { const logger = pinoLogger.child({ service: "fetchAllMetadata" }); + logger.debug("Fetching metadata from Saleor"); const { error, data } = await client @@ -27,6 +28,7 @@ export async function fetchAllMetadata(client: Client): Promise export async function mutateMetadata(client: Client, metadata: MetadataEntry[]) { const logger = pinoLogger.child({ service: "mutateMetadata" }); + logger.debug({ metadata }, "Mutating metadata"); // to update the metadata, ID is required const { error: idQueryError, data: idQueryData } = await client @@ -48,7 +50,7 @@ export async function mutateMetadata(client: Client, metadata: MetadataEntry[]) } const { error: mutationError, data: mutationData } = await client - .mutation(UpdateAppMetadataDocument, { + .mutation(UpdateMetadataDocument, { id: appId, input: metadata, }) @@ -67,9 +69,11 @@ export async function mutateMetadata(client: Client, metadata: MetadataEntry[]) } export const createSettingsManager = (client: Client) => { - // EncryptedMetadataManager gives you interface to manipulate metadata and cache values in memory. - // We recommend it for production, because all values are encrypted. - // If your use case require plain text values, you can use MetadataManager. + /** + * EncryptedMetadataManager gives you interface to manipulate metadata and cache values in memory. + * We recommend it for production, because all values are encrypted. + * If your use case require plain text values, you can use MetadataManager. + */ return new EncryptedMetadataManager({ // Secret key should be randomly created for production and set as environment variable encryptionKey: process.env.SECRET_KEY!, diff --git a/apps/taxes/src/modules/app/webhook-response.ts b/apps/taxes/src/modules/app/webhook-response.ts new file mode 100644 index 0000000..80a3db6 --- /dev/null +++ b/apps/taxes/src/modules/app/webhook-response.ts @@ -0,0 +1,28 @@ +import { NextApiResponse } from "next"; +import { Logger } from "pino"; +import { createLogger } from "../../lib/logger"; + +export class WebhookResponse { + private logger: Logger; + constructor(private res: NextApiResponse) { + this.logger = createLogger({ event: "WebhookResponse" }); + } + + failureNoRetry(error: string) { + this.logger.debug({ error }, "failureNoRetry called with:"); + return this.res.status(200).json({ success: false, error }); + } + + failureRetry(error: string) { + this.logger.error({ error }, "failureRetry called with:"); + return this.res.status(500).json({ success: false, error }); + } + + success(data?: any) { + this.logger.debug({ data }, "success called with:"); + return this.res.status(200).json({ + status: 200, + data, + }); + } +} diff --git a/apps/taxes/src/modules/avatax/avatax-calculate.ts b/apps/taxes/src/modules/avatax/avatax-calculate.ts deleted file mode 100644 index 4af420d..0000000 --- a/apps/taxes/src/modules/avatax/avatax-calculate.ts +++ /dev/null @@ -1,112 +0,0 @@ -import { CreateTransactionModel } from "avatax/lib/models/CreateTransactionModel"; -import { LineItemModel } from "avatax/lib/models/LineItemModel"; -import { TransactionModel } from "avatax/lib/models/TransactionModel"; -import { TaxBaseFragment } from "../../../generated/graphql"; - -import { ChannelConfig } from "../channels-configuration/channels-config"; -import { taxLineResolver } from "../taxes/tax-line-resolver"; -import { ResponseTaxPayload } from "../taxes/types"; -import { AvataxConfig } from "./avatax-config"; - -const SHIPPING_ITEM_CODE = "Shipping"; - -const formatCalculatedAmount = (amount: number) => { - return Number(amount.toFixed(2)); -}; - -const prepareLines = (taxBase: TaxBaseFragment): LineItemModel[] => { - const productLines = taxBase.lines.map((line) => ({ - amount: line.unitPrice.amount, - taxIncluded: line.chargeTaxes, - taxCode: taxLineResolver.getLineTaxCode(line), - quantity: line.quantity, - itemCode: "Product", - })); - - if (taxBase.shippingPrice.amount !== 0) { - // * In Avatax, shipping is a regular line - const shippingLine: LineItemModel = { - amount: taxBase.shippingPrice.amount, - itemCode: SHIPPING_ITEM_CODE, - quantity: 1, - }; - - return [...productLines, shippingLine]; - } - - return productLines; -}; - -const defaultAvataxTransactionModel = { - // todo: what is customerCode - customerCode: "0", - type: 0, -}; - -const preparePayload = ( - taxBase: TaxBaseFragment, - channel: ChannelConfig, - config: AvataxConfig -): CreateTransactionModel => { - return { - ...defaultAvataxTransactionModel, - companyCode: config.companyName, - // * commit: If true, the transaction will be committed immediately after it is created. See: https://developer.avalara.com/communications/dev-guide_rest_v2/commit-uncommit - commit: config.isAutocommit, - addresses: { - shipFrom: { - line1: channel.address.street, - city: channel.address.city, - region: channel.address.state, - postalCode: channel.address.zip, - country: channel.address.country, - }, - shipTo: { - line1: taxBase.address?.streetAddress1, - line2: taxBase.address?.streetAddress2, - city: taxBase.address?.city, - country: taxBase.address?.country.code, - postalCode: taxBase.address?.postalCode, - region: taxBase.address?.countryArea, - }, - }, - lines: prepareLines(taxBase), - // todo: replace date with order/checkout date - date: new Date(), - }; -}; - -const prepareResponse = (transaction: TransactionModel): ResponseTaxPayload => { - const shippingLine = transaction.lines?.find((line) => line.itemCode === SHIPPING_ITEM_CODE); - - const productLines = transaction.lines?.filter((line) => line.itemCode !== SHIPPING_ITEM_CODE); - - const shippingGrossAmount = shippingLine?.taxableAmount ?? 0; - const shippingTaxCalculated = shippingLine?.taxCalculated ?? 0; - const shippingNetAmount = formatCalculatedAmount(shippingGrossAmount - shippingTaxCalculated); - - return { - shipping_price_gross_amount: shippingGrossAmount, - shipping_price_net_amount: shippingNetAmount, - // todo: add shipping tax rate - shipping_tax_rate: 0, - lines: - productLines?.map((line) => { - const lineTaxCalculated = line.taxCalculated ?? 0; - const lineTotalNetAmount = line.taxableAmount ?? 0; - const lineTotalGrossAmount = formatCalculatedAmount(lineTotalNetAmount + lineTaxCalculated); - return { - total_gross_amount: lineTotalGrossAmount, - total_net_amount: lineTotalNetAmount, - // todo: add tax rate - tax_rate: 0, - }; - }) ?? [], - }; -}; - -export const avataxCalculate = { - preparePayload, - prepareResponse, - prepareLines, -}; diff --git a/apps/taxes/src/modules/avatax/avatax-client.ts b/apps/taxes/src/modules/avatax/avatax-client.ts index d9cdeca..d0ea3ff 100644 --- a/apps/taxes/src/modules/avatax/avatax-client.ts +++ b/apps/taxes/src/modules/avatax/avatax-client.ts @@ -4,6 +4,8 @@ import pino from "pino"; import packageJson from "../../../package.json"; import { createLogger } from "../../lib/logger"; import { AvataxConfig } from "./avatax-config"; +import { CommitTransactionModel } from "avatax/lib/models/CommitTransactionModel"; +import { DocumentType } from "avatax/lib/enums/DocumentType"; type AvataxSettings = { appName: string; @@ -35,6 +37,17 @@ const createAvataxSettings = (config: AvataxConfig): AvataxSettings => { return settings; }; +export type CommitTransactionArgs = { + companyCode: string; + transactionCode: string; + model: CommitTransactionModel; + documentType: DocumentType; +}; + +export type CreateTransactionArgs = { + model: CreateTransactionModel; +}; + export class AvataxClient { private client: Avatax; private logger: pino.Logger; @@ -49,16 +62,23 @@ export class AvataxClient { }; const settings = createAvataxSettings(config); const avataxClient = new Avatax(settings).withSecurity(credentials); + this.logger.trace({ client: avataxClient }, "External Avatax client created"); this.client = avataxClient; } - async fetchTaxesForOrder(model: CreateTransactionModel) { - this.logger.debug({ model }, "fetchTaxesForOrder called with:"); + async createTransaction({ model }: CreateTransactionArgs) { + this.logger.debug({ model }, "createTransaction called with:"); return this.client.createTransaction({ model }); } + async commitTransaction(args: CommitTransactionArgs) { + this.logger.debug(args, "commitTransaction called with:"); + + return this.client.commitTransaction(args); + } + async ping() { this.logger.debug("ping called"); try { diff --git a/apps/taxes/src/modules/avatax/avatax-config.ts b/apps/taxes/src/modules/avatax/avatax-config.ts index 4a6ced2..16e8bad 100644 --- a/apps/taxes/src/modules/avatax/avatax-config.ts +++ b/apps/taxes/src/modules/avatax/avatax-config.ts @@ -6,7 +6,7 @@ export const avataxConfigSchema = z.object({ username: z.string().min(1, { message: "Username requires at least one character." }), password: z.string().min(1, { message: "Password requires at least one character." }), isSandbox: z.boolean(), - companyName: z.string().min(1, { message: "Company name requires at least one character." }), + companyCode: z.string().min(1, { message: "Company code requires at least one character." }), isAutocommit: z.boolean(), }); @@ -16,7 +16,7 @@ export const defaultAvataxConfig: AvataxConfig = { name: "", username: "", password: "", - companyName: "", + companyCode: "", isSandbox: true, isAutocommit: false, }; diff --git a/apps/taxes/src/modules/avatax/avatax-configuration.router.ts b/apps/taxes/src/modules/avatax/avatax-configuration.router.ts index b668344..0951fbe 100644 --- a/apps/taxes/src/modules/avatax/avatax-configuration.router.ts +++ b/apps/taxes/src/modules/avatax/avatax-configuration.router.ts @@ -18,6 +18,7 @@ const patchInputSchema = z.object({ id: z.string(), value: avataxConfigSchema.partial().transform((c) => { const { username, password, ...config } = c ?? {}; + return { ...config, ...(username && !isObfuscated(username) && { username }), diff --git a/apps/taxes/src/modules/avatax/avatax-configuration.service.ts b/apps/taxes/src/modules/avatax/avatax-configuration.service.ts index 6038587..6a67f4b 100644 --- a/apps/taxes/src/modules/avatax/avatax-configuration.service.ts +++ b/apps/taxes/src/modules/avatax/avatax-configuration.service.ts @@ -1,20 +1,22 @@ import pino from "pino"; import { Client } from "urql"; import { createLogger } from "../../lib/logger"; -import { createSettingsManager } from "../app-configuration/metadata-manager"; -import { CrudSettingsConfigurator } from "../crud-settings/crud-settings.service"; +import { createSettingsManager } from "../app/metadata-manager"; +import { CrudSettingsManager } from "../crud-settings/crud-settings.service"; import { providersSchema } from "../providers-configuration/providers-config"; import { TAX_PROVIDER_KEY } from "../providers-configuration/public-providers-configuration-service"; import { AvataxClient } from "./avatax-client"; import { AvataxConfig, AvataxInstanceConfig, avataxInstanceConfigSchema } from "./avatax-config"; const getSchema = avataxInstanceConfigSchema; + export class AvataxConfigurationService { - private crudSettingsConfigurator: CrudSettingsConfigurator; + private crudSettingsManager: CrudSettingsManager; private logger: pino.Logger; constructor(client: Client, saleorApiUrl: string) { const settingsManager = createSettingsManager(client); - this.crudSettingsConfigurator = new CrudSettingsConfigurator( + + this.crudSettingsManager = new CrudSettingsManager( settingsManager, saleorApiUrl, TAX_PROVIDER_KEY @@ -27,7 +29,7 @@ export class AvataxConfigurationService { async getAll(): Promise { this.logger.debug(".getAll called"); - const { data } = await this.crudSettingsConfigurator.readAll(); + const { data } = await this.crudSettingsManager.readAll(); const validation = providersSchema.safeParse(data); if (!validation.success) { @@ -44,8 +46,9 @@ export class AvataxConfigurationService { async get(id: string): Promise { this.logger.debug(`.get called with id: ${id}`); - const { data } = await this.crudSettingsConfigurator.read(id); - this.logger.debug(`Fetched setting from crudSettingsConfigurator`); + const { data } = await this.crudSettingsManager.read(id); + + this.logger.debug(`Fetched setting from CrudSettingsManager`); const validation = getSchema.safeParse(data); @@ -67,7 +70,7 @@ export class AvataxConfigurationService { throw new Error(validation.error); } - const result = await this.crudSettingsConfigurator.create({ + const result = await this.crudSettingsManager.create({ provider: "avatax", config: config, }); @@ -81,7 +84,7 @@ export class AvataxConfigurationService { // omit the key "id" from the result const { id: _, ...setting } = data; - return this.crudSettingsConfigurator.update(id, { + return this.crudSettingsManager.update(id, { ...setting, config: { ...setting.config, ...config }, }); @@ -93,7 +96,7 @@ export class AvataxConfigurationService { const { id: _, ...setting } = data; this.logger.debug(`.put called with id: ${id} and value: ${JSON.stringify(config)}`); - return this.crudSettingsConfigurator.update(id, { + return this.crudSettingsManager.update(id, { ...setting, config: { ...config }, }); @@ -101,6 +104,6 @@ export class AvataxConfigurationService { async delete(id: string): Promise { this.logger.debug(`.delete called with id: ${id}`); - return this.crudSettingsConfigurator.delete(id); + return this.crudSettingsManager.delete(id); } } diff --git a/apps/taxes/src/modules/avatax/avatax-provider.ts b/apps/taxes/src/modules/avatax/avatax-provider.ts deleted file mode 100644 index 4da8add..0000000 --- a/apps/taxes/src/modules/avatax/avatax-provider.ts +++ /dev/null @@ -1,34 +0,0 @@ -import pino from "pino"; -import { TaxBaseFragment } from "../../../generated/graphql"; -import { createLogger } from "../../lib/logger"; -import { ChannelConfig } from "../channels-configuration/channels-config"; -import { TaxProvider } from "../taxes/tax-provider"; -import { avataxCalculate } from "./avatax-calculate"; -import { AvataxClient } from "./avatax-client"; -import { AvataxConfig, defaultAvataxConfig } from "./avatax-config"; - -export class AvataxProvider implements TaxProvider { - readonly name = "avatax"; - config = defaultAvataxConfig; - client: AvataxClient; - private logger: pino.Logger; - - constructor(config: AvataxConfig) { - this.logger = createLogger({ - service: "AvataxProvider", - }); - const avataxClient = new AvataxClient(config); - this.logger.trace({ client: avataxClient }, "Internal Avatax client created"); - - this.config = config; - this.client = avataxClient; - } - - async calculate(payload: TaxBaseFragment, channel: ChannelConfig) { - this.logger.debug({ payload, channel }, "Avatax calculate called with:"); - const model = avataxCalculate.preparePayload(payload, channel, this.config); - const result = await this.client.fetchTaxesForOrder(model); - this.logger.debug({ createOrderTransaction: result }, "Avatax createOrderTransaction response"); - return avataxCalculate.prepareResponse(result); - } -} diff --git a/apps/taxes/src/modules/avatax/avatax-webhook.service.ts b/apps/taxes/src/modules/avatax/avatax-webhook.service.ts new file mode 100644 index 0000000..0701eb3 --- /dev/null +++ b/apps/taxes/src/modules/avatax/avatax-webhook.service.ts @@ -0,0 +1,67 @@ +import pino from "pino"; +import { + OrderCreatedSubscriptionFragment, + OrderFulfilledSubscriptionFragment, + TaxBaseFragment, +} from "../../../generated/graphql"; +import { createLogger } from "../../lib/logger"; +import { ChannelConfig } from "../channels-configuration/channels-config"; +import { ProviderWebhookService } from "../taxes/tax-provider-webhook"; +import { avataxCalculateTaxesMaps } from "./maps/avatax-calculate-taxes-map"; +import { AvataxClient } from "./avatax-client"; +import { AvataxConfig, defaultAvataxConfig } from "./avatax-config"; +import { avataxOrderCreatedMaps } from "./maps/avatax-order-created-map"; +import { avataxOrderFulfilledMaps } from "./maps/avatax-order-fulfilled-map"; + +export class AvataxWebhookService implements ProviderWebhookService { + config = defaultAvataxConfig; + client: AvataxClient; + private logger: pino.Logger; + + constructor(config: AvataxConfig) { + this.logger = createLogger({ + service: "AvataxWebhookService", + }); + const avataxClient = new AvataxClient(config); + + this.logger.trace({ client: avataxClient }, "Internal Avatax client created"); + + this.config = config; + this.client = avataxClient; + } + + async calculateTaxes(payload: TaxBaseFragment, channel: ChannelConfig) { + this.logger.debug({ payload, channel }, "calculateTaxes called with:"); + const args = avataxCalculateTaxesMaps.mapPayload({ + taxBase: payload, + channel, + config: this.config, + }); + const result = await this.client.createTransaction(args); + + this.logger.debug({ result }, "calculateTaxes response"); + return avataxCalculateTaxesMaps.mapResponse(result); + } + + async createOrder(order: OrderCreatedSubscriptionFragment, channel: ChannelConfig) { + this.logger.debug({ order, channel }, "createOrder called with:"); + const model = avataxOrderCreatedMaps.mapPayload(order, channel, this.config); + + this.logger.debug({ model }, "will call createTransaction with"); + const result = await this.client.createTransaction(model); + + this.logger.debug({ result }, "createOrder response"); + return avataxOrderCreatedMaps.mapResponse(result); + } + + async fulfillOrder(order: OrderFulfilledSubscriptionFragment, channel: ChannelConfig) { + this.logger.debug({ order, channel }, "fulfillOrder called with:"); + const args = avataxOrderFulfilledMaps.mapPayload(order, this.config); + + this.logger.debug({ args }, "will call commitTransaction with"); + const result = await this.client.commitTransaction(args); + + this.logger.debug({ result }, "fulfillOrder response"); + return { ok: true }; + } +} diff --git a/apps/taxes/src/modules/avatax/maps/address-factory.test.ts b/apps/taxes/src/modules/avatax/maps/address-factory.test.ts new file mode 100644 index 0000000..fff8df9 --- /dev/null +++ b/apps/taxes/src/modules/avatax/maps/address-factory.test.ts @@ -0,0 +1,70 @@ +import { describe, expect, it } from "vitest"; +import { avataxAddressFactory } from "./address-factory"; + +describe("avataxAddressFactory", () => { + describe("fromChannelAddress", () => { + it("returns fields in the expected format", () => { + const result = avataxAddressFactory.fromChannelAddress({ + city: "LOS ANGELES", + country: "US", + state: "CA", + street: "123 Palm Grove Ln", + zip: "90002", + }); + + expect(result).toEqual({ + line1: "123 Palm Grove Ln", + city: "LOS ANGELES", + region: "CA", + postalCode: "90002", + country: "US", + }); + }); + }); + + describe("fromSaleorAddress", () => { + it("returns fields in the expected format with line1", () => { + const result = avataxAddressFactory.fromSaleorAddress({ + streetAddress1: "123 Palm Grove Ln", + streetAddress2: "", + city: "LOS ANGELES", + country: { + code: "US", + }, + countryArea: "CA", + postalCode: "90002", + }); + + expect(result).toEqual({ + line1: "123 Palm Grove Ln", + line2: "", + city: "LOS ANGELES", + region: "CA", + postalCode: "90002", + country: "US", + }); + }); + + it("returns fields in the expected format with line1 and line2", () => { + const result = avataxAddressFactory.fromSaleorAddress({ + streetAddress1: "123 Palm", + streetAddress2: "Grove Ln", + city: "LOS ANGELES", + country: { + code: "US", + }, + countryArea: "CA", + postalCode: "90002", + }); + + expect(result).toEqual({ + line1: "123 Palm", + line2: "Grove Ln", + city: "LOS ANGELES", + region: "CA", + postalCode: "90002", + country: "US", + }); + }); + }); +}); diff --git a/apps/taxes/src/modules/avatax/maps/address-factory.ts b/apps/taxes/src/modules/avatax/maps/address-factory.ts new file mode 100644 index 0000000..d1275e0 --- /dev/null +++ b/apps/taxes/src/modules/avatax/maps/address-factory.ts @@ -0,0 +1,31 @@ +import { AddressLocationInfo as AvataxAddress } from "avatax/lib/models/AddressLocationInfo"; +import { ChannelAddress } from "../../channels-configuration/channels-config"; +import { AddressFragment } from "../../../../generated/graphql"; + +type SaleorAddress = AddressFragment; + +function mapSaleorAddressToAvataxAddress(address: SaleorAddress): AvataxAddress { + return { + line1: address.streetAddress1, + line2: address.streetAddress2, + city: address.city, + region: address.countryArea, + postalCode: address.postalCode, + country: address.country.code, + }; +} + +function mapChannelAddressToAvataxAddress(address: ChannelAddress): AvataxAddress { + return { + line1: address.street, + city: address.city, + region: address.state, + postalCode: address.zip, + country: address.country, + }; +} + +export const avataxAddressFactory = { + fromSaleorAddress: mapSaleorAddressToAvataxAddress, + fromChannelAddress: mapChannelAddressToAvataxAddress, +}; diff --git a/apps/taxes/src/modules/avatax/maps/avatax-calculate-taxes-map.test.ts b/apps/taxes/src/modules/avatax/maps/avatax-calculate-taxes-map.test.ts new file mode 100644 index 0000000..81a3583 --- /dev/null +++ b/apps/taxes/src/modules/avatax/maps/avatax-calculate-taxes-map.test.ts @@ -0,0 +1,126 @@ +import { describe, expect, it } from "vitest"; +import { + AvataxCalculateTaxesMapPayloadArgs, + avataxCalculateTaxesMaps, +} from "./avatax-calculate-taxes-map"; + +// * Mocked payload data, channel config and avatax config +const MOCKED_CALCULATE_TAXES_ARGS: AvataxCalculateTaxesMapPayloadArgs = { + taxBase: { + currency: "PLN", + channel: { + slug: "channel-pln", + }, + sourceObject: { + __typename: "Order", + user: { + id: "VXNlcjo5ZjY3ZjY0Zi1iZjY5LTQ5ZjYtYjQ4Zi1iZjY3ZjY0ZjY0ZjY=", + }, + }, + discounts: [], + address: { + streetAddress1: "123 Palm Grove Ln", + streetAddress2: "", + city: "LOS ANGELES", + country: { + code: "US", + }, + countryArea: "CA", + postalCode: "90002", + }, + shippingPrice: { + amount: 48.33, + }, + lines: [ + { + chargeTaxes: true, + quantity: 1, + unitPrice: { + amount: 84, + }, + totalPrice: { + amount: 84, + }, + sourceLine: { + __typename: "OrderLine", + id: "T3JkZXJMaW5lOmY1NGQ1MWY2LTc1OTctNGY2OC1hNDk0LTFjYjZlYjRmOTlhMQ==", + variant: { + id: "UHJvZHVjdFZhcmlhbnQ6MzQ2", + product: { + metafield: null, + productType: { + metafield: null, + }, + }, + }, + }, + }, + { + chargeTaxes: true, + quantity: 1, + unitPrice: { + amount: 5.99, + }, + totalPrice: { + amount: 5.99, + }, + sourceLine: { + __typename: "OrderLine", + id: "T3JkZXJMaW5lOjU1NTFjNTFjLTM5MWQtNGI0Ny04MGU0LWVjY2Q5ZjU4MjQyNQ==", + variant: { + id: "UHJvZHVjdFZhcmlhbnQ6Mzg1", + product: { + metafield: null, + productType: { + metafield: null, + }, + }, + }, + }, + }, + ], + }, + channel: { + providerInstanceId: "b8c29f49-7cae-4762-8458-e9a27eb83081", + enabled: false, + address: { + country: "US", + zip: "92093", + state: "CA", + city: "La Jolla", + street: "9500 Gilman Drive", + }, + }, + config: { + companyCode: "DEFAULT", + isAutocommit: false, + isSandbox: true, + name: "Avatax-1", + password: "password", + username: "username", + }, +}; + +describe("avataxCalculateTaxesMaps", () => { + describe.todo("mapResponse", () => { + it.todo("calculation of fields"); + it.todo("formatting the fields"); + it.todo("rounding of numbers"); + }); + describe.todo("mapPayload", () => { + it.todo("calculation of fields"); + it.todo("formatting the fields"); + it.todo("rounding of numbers"); + }); + describe("mapLines", () => { + it("includes shipping as a line", () => { + const lines = avataxCalculateTaxesMaps.mapLines(MOCKED_CALCULATE_TAXES_ARGS.taxBase); + + expect(lines).toContainEqual({ + itemCode: avataxCalculateTaxesMaps.shippingItemCode, + quantity: 1, + amount: 48.33, + }); + }); + }); +}); diff --git a/apps/taxes/src/modules/avatax/maps/avatax-calculate-taxes-map.ts b/apps/taxes/src/modules/avatax/maps/avatax-calculate-taxes-map.ts new file mode 100644 index 0000000..524c210 --- /dev/null +++ b/apps/taxes/src/modules/avatax/maps/avatax-calculate-taxes-map.ts @@ -0,0 +1,112 @@ +import { LineItemModel } from "avatax/lib/models/LineItemModel"; +import { TransactionModel } from "avatax/lib/models/TransactionModel"; +import { TaxBaseFragment } from "../../../../generated/graphql"; + +import { DocumentType } from "avatax/lib/enums/DocumentType"; +import { ChannelConfig } from "../../channels-configuration/channels-config"; +import { taxLineResolver } from "../../taxes/tax-line-resolver"; +import { CalculateTaxesResponse } from "../../taxes/tax-provider-webhook"; +import { CreateTransactionArgs } from "../avatax-client"; +import { AvataxConfig } from "../avatax-config"; +import { avataxAddressFactory } from "./address-factory"; +import { numbers } from "../../taxes/numbers"; + +/** + * * Shipping is a regular line item in Avatax + * https://developer.avalara.com/avatax/dev-guide/shipping-and-handling/taxability-of-shipping-charges/ + */ +const SHIPPING_ITEM_CODE = "Shipping"; + +function mapLines(taxBase: TaxBaseFragment): LineItemModel[] { + const productLines = taxBase.lines.map((line) => ({ + amount: line.unitPrice.amount, + taxIncluded: line.chargeTaxes, + // todo: get from tax code matcher + taxCode: taxLineResolver.getLineTaxCode(line), + quantity: line.quantity, + })); + + if (taxBase.shippingPrice.amount !== 0) { + // * In Avatax, shipping is a regular line + const shippingLine: LineItemModel = { + amount: taxBase.shippingPrice.amount, + itemCode: SHIPPING_ITEM_CODE, + /** + * todo: add taxCode + * * Different shipping methods can have different tax codes + * https://developer.avalara.com/ecommerce-integration-guide/sales-tax-badge/designing/non-standard-items/\ + */ + quantity: 1, + }; + + return [...productLines, shippingLine]; + } + + return productLines; +} + +export type AvataxCalculateTaxesMapPayloadArgs = { + taxBase: TaxBaseFragment; + channel: ChannelConfig; + config: AvataxConfig; +}; + +const mapPayload = (props: AvataxCalculateTaxesMapPayloadArgs): CreateTransactionArgs => { + const { taxBase, channel, config } = props; + + return { + model: { + type: DocumentType.SalesOrder, + customerCode: taxBase.sourceObject.user?.id ?? "", + companyCode: config.companyCode, + // * commit: If true, the transaction will be committed immediately after it is created. See: https://developer.avalara.com/communications/dev-guide_rest_v2/commit-uncommit + commit: config.isAutocommit, + addresses: { + shipFrom: avataxAddressFactory.fromChannelAddress(channel.address), + shipTo: avataxAddressFactory.fromSaleorAddress(taxBase.address!), + }, + currencyCode: taxBase.currency, + lines: mapLines(taxBase), + date: new Date(), + }, + }; +}; + +const mapResponse = (transaction: TransactionModel): CalculateTaxesResponse => { + const shippingLine = transaction.lines?.find((line) => line.itemCode === SHIPPING_ITEM_CODE); + const productLines = transaction.lines?.filter((line) => line.itemCode !== SHIPPING_ITEM_CODE); + const shippingGrossAmount = shippingLine?.taxableAmount ?? 0; + const shippingTaxCalculated = shippingLine?.taxCalculated ?? 0; + const shippingNetAmount = numbers.roundFloatToTwoDecimals( + shippingGrossAmount - shippingTaxCalculated + ); + + return { + shipping_price_gross_amount: shippingGrossAmount, + shipping_price_net_amount: shippingNetAmount, + // todo: add shipping tax rate + shipping_tax_rate: 0, + lines: + productLines?.map((line) => { + const lineTaxCalculated = line.taxCalculated ?? 0; + const lineTotalNetAmount = line.taxableAmount ?? 0; + const lineTotalGrossAmount = numbers.roundFloatToTwoDecimals( + lineTotalNetAmount + lineTaxCalculated + ); + + return { + total_gross_amount: lineTotalGrossAmount, + total_net_amount: lineTotalNetAmount, + // todo: add tax rate + tax_rate: 0, + }; + }) ?? [], + }; +}; + +export const avataxCalculateTaxesMaps = { + mapPayload, + mapResponse, + mapLines, + shippingItemCode: SHIPPING_ITEM_CODE, +}; diff --git a/apps/taxes/src/modules/avatax/maps/avatax-order-created-map.test.ts b/apps/taxes/src/modules/avatax/maps/avatax-order-created-map.test.ts new file mode 100644 index 0000000..f1e32f9 --- /dev/null +++ b/apps/taxes/src/modules/avatax/maps/avatax-order-created-map.test.ts @@ -0,0 +1,14 @@ +import { describe, it } from "vitest"; + +describe.skip("avataxOrderCreatedMaps", () => { + describe.todo("mapResponse", () => { + it.todo("calculation of fields"); + it.todo("formatting the fields"); + it.todo("rounding of numbers"); + }); + describe.todo("mapPayload", () => { + it.todo("calculation of fields"); + it.todo("formatting the fields"); + it.todo("rounding of numbers"); + }); +}); diff --git a/apps/taxes/src/modules/avatax/maps/avatax-order-created-map.ts b/apps/taxes/src/modules/avatax/maps/avatax-order-created-map.ts new file mode 100644 index 0000000..d72c951 --- /dev/null +++ b/apps/taxes/src/modules/avatax/maps/avatax-order-created-map.ts @@ -0,0 +1,56 @@ +import { DocumentType } from "avatax/lib/enums/DocumentType"; +import { LineItemModel } from "avatax/lib/models/LineItemModel"; +import { TransactionModel } from "avatax/lib/models/TransactionModel"; +import { OrderCreatedSubscriptionFragment } from "../../../../generated/graphql"; +import { ChannelConfig } from "../../channels-configuration/channels-config"; +import { CreateOrderResponse } from "../../taxes/tax-provider-webhook"; +import { CreateTransactionArgs } from "../avatax-client"; +import { AvataxConfig } from "../avatax-config"; +import { avataxAddressFactory } from "./address-factory"; + +const mapLines = (order: OrderCreatedSubscriptionFragment): LineItemModel[] => { + const productLines = order.lines.map((line) => ({ + amount: line.unitPrice.net.amount, + quantity: line.quantity, + // todo: get from tax code matcher + taxCode: "", + })); + + return productLines; +}; + +const mapPayload = ( + order: OrderCreatedSubscriptionFragment, + channel: ChannelConfig, + config: AvataxConfig +): CreateTransactionArgs => { + return { + model: { + type: DocumentType.SalesInvoice, + customerCode: order.user?.id ?? "", + companyCode: config.companyCode, + // * commit: If true, the transaction will be committed immediately after it is created. See: https://developer.avalara.com/communications/dev-guide_rest_v2/commit-uncommit + commit: config.isAutocommit, + addresses: { + shipFrom: avataxAddressFactory.fromChannelAddress(channel.address), + // billing or shipping address? + shipTo: avataxAddressFactory.fromSaleorAddress(order.billingAddress!), + }, + currencyCode: order.total.currency, + email: order.user?.email ?? "", + lines: mapLines(order), + date: new Date(order.created), + }, + }; +}; + +const mapResponse = (response: TransactionModel): CreateOrderResponse => { + return { + id: response.code ?? "", + }; +}; + +export const avataxOrderCreatedMaps = { + mapPayload, + mapResponse, +}; diff --git a/apps/taxes/src/modules/avatax/maps/avatax-order-fulfilled-map.test.ts b/apps/taxes/src/modules/avatax/maps/avatax-order-fulfilled-map.test.ts new file mode 100644 index 0000000..fcfb6ef --- /dev/null +++ b/apps/taxes/src/modules/avatax/maps/avatax-order-fulfilled-map.test.ts @@ -0,0 +1,14 @@ +import { describe, it } from "vitest"; + +describe.skip("avataxOrderFulfilledMaps", () => { + describe.todo("mapResponse", () => { + it.todo("calculation of fields"); + it.todo("formatting the fields"); + it.todo("rounding of numbers"); + }); + describe.todo("mapPayload", () => { + it.todo("calculation of fields"); + it.todo("formatting the fields"); + it.todo("rounding of numbers"); + }); +}); diff --git a/apps/taxes/src/modules/avatax/maps/avatax-order-fulfilled-map.ts b/apps/taxes/src/modules/avatax/maps/avatax-order-fulfilled-map.ts new file mode 100644 index 0000000..6379e7b --- /dev/null +++ b/apps/taxes/src/modules/avatax/maps/avatax-order-fulfilled-map.ts @@ -0,0 +1,37 @@ +import { DocumentType } from "avatax/lib/enums/DocumentType"; +import { OrderFulfilledSubscriptionFragment } from "../../../../generated/graphql"; +import { PROVIDER_ORDER_ID_KEY } from "../../../pages/api/webhooks/order-created"; +import { CommitTransactionArgs } from "../avatax-client"; +import { AvataxConfig } from "../avatax-config"; + +function getTransactionCodeFromMetadata( + metadata: OrderFulfilledSubscriptionFragment["privateMetadata"] +) { + const transactionCode = metadata.find((item) => item.key === PROVIDER_ORDER_ID_KEY); + + if (!transactionCode) { + throw new Error("Transaction code not found"); + } + + return transactionCode.value; +} + +const mapPayload = ( + order: OrderFulfilledSubscriptionFragment, + config: AvataxConfig +): CommitTransactionArgs => { + const transactionCode = getTransactionCodeFromMetadata(order.privateMetadata); + + return { + transactionCode, + companyCode: config.companyCode, + documentType: DocumentType.SalesInvoice, + model: { + commit: true, + }, + }; +}; + +export const avataxOrderFulfilledMaps = { + mapPayload, +}; diff --git a/apps/taxes/src/modules/avatax/ui/avatax-configuration-form.tsx b/apps/taxes/src/modules/avatax/ui/avatax-configuration-form.tsx index a68bc7e..3b51020 100644 --- a/apps/taxes/src/modules/avatax/ui/avatax-configuration-form.tsx +++ b/apps/taxes/src/modules/avatax/ui/avatax-configuration-form.tsx @@ -27,10 +27,11 @@ const useStyles = makeStyles((theme) => ({ })); const schema = avataxConfigSchema; + type FormValues = z.infer; const defaultValues: FormValues = { - companyName: "", + companyCode: "", isAutocommit: false, isSandbox: false, password: "", @@ -72,6 +73,7 @@ export const AvataxConfigurationForm = () => { React.useEffect(() => { if (instance) { const { config } = instance; + reset(config); } else { reset(defaultValues); @@ -245,15 +247,15 @@ export const AvataxConfigurationForm = () => { ( - + )} /> - {formState.errors.companyName && ( - {formState.errors.companyName.message} + {formState.errors.companyCode && ( + {formState.errors.companyCode.message} )} diff --git a/apps/taxes/src/modules/channels-configuration/channels-config.ts b/apps/taxes/src/modules/channels-configuration/channels-config.ts index f11a3e9..9e111af 100644 --- a/apps/taxes/src/modules/channels-configuration/channels-config.ts +++ b/apps/taxes/src/modules/channels-configuration/channels-config.ts @@ -1,6 +1,15 @@ import { z } from "zod"; import { ChannelFragment } from "../../../generated/graphql"; -import { addressSchema } from "../taxes/tax-common-schema"; + +const addressSchema = z.object({ + country: z.string(), + zip: z.string(), + state: z.string(), + city: z.string(), + street: z.string(), +}); + +export type ChannelAddress = z.infer; export const channelSchema = z.object({ providerInstanceId: z.string(), diff --git a/apps/taxes/src/modules/channels-configuration/channels-configuration.router.ts b/apps/taxes/src/modules/channels-configuration/channels-configuration.router.ts index d9d7bff..edd3c4b 100644 --- a/apps/taxes/src/modules/channels-configuration/channels-configuration.router.ts +++ b/apps/taxes/src/modules/channels-configuration/channels-configuration.router.ts @@ -1,5 +1,5 @@ import { logger as pinoLogger } from "../../lib/logger"; -import { createSettingsManager } from "../app-configuration/metadata-manager"; +import { createSettingsManager } from "../app/metadata-manager"; import { protectedClientProcedure } from "../trpc/protected-client-procedure"; import { router } from "../trpc/trpc-server"; import { ChannelsConfig } from "./channels-config"; @@ -29,12 +29,14 @@ export const channelsConfigurationRouter = router({ saleorApiUrl: ctx.saleorApiUrl, procedure: "channelsConfigurationRouter.upsert", }); + logger.debug(input, "channelsConfigurationRouter.upsert called with input"); const config = await new GetChannelsConfigurationService({ apiClient: ctx.apiClient, saleorApiUrl: ctx.saleorApiUrl, }).getConfiguration(); + logger.debug(config, "Fetched current channels config to update it"); const taxChannelsConfigurator = new TaxChannelsConfigurator( diff --git a/apps/taxes/src/modules/channels-configuration/channels-configurator.ts b/apps/taxes/src/modules/channels-configuration/channels-configurator.ts index 6abd36b..9a5150a 100644 --- a/apps/taxes/src/modules/channels-configuration/channels-configurator.ts +++ b/apps/taxes/src/modules/channels-configuration/channels-configurator.ts @@ -1,5 +1,5 @@ import { SettingsManager } from "@saleor/app-sdk/settings-manager"; -import { PrivateMetadataAppConfigurator } from "../app-configuration/app-configurator"; +import { PrivateMetadataAppConfigurator } from "../app/app-configurator"; import { ChannelsConfig } from "./channels-config"; export class TaxChannelsConfigurator extends PrivateMetadataAppConfigurator { diff --git a/apps/taxes/src/modules/channels-configuration/get-channels-configuration.service.ts b/apps/taxes/src/modules/channels-configuration/get-channels-configuration.service.ts index 35ef53a..b967c58 100644 --- a/apps/taxes/src/modules/channels-configuration/get-channels-configuration.service.ts +++ b/apps/taxes/src/modules/channels-configuration/get-channels-configuration.service.ts @@ -1,6 +1,6 @@ import { Client } from "urql"; import { logger as pinoLogger } from "../../lib/logger"; -import { createSettingsManager } from "../app-configuration/metadata-manager"; +import { createSettingsManager } from "../app/metadata-manager"; import { TaxChannelsConfigurator } from "./channels-configurator"; export class GetChannelsConfigurationService { @@ -24,7 +24,6 @@ export class GetChannelsConfigurationService { saleorApiUrl ); - // todo: validate config const appChannelsConfig = (await taxConfigurator.getConfig()) ?? null; logger.debug(appChannelsConfig, "Retrieved channels config from Metadata"); diff --git a/apps/taxes/src/modules/channels/ui/channel-tax-provider-form.tsx b/apps/taxes/src/modules/channels/ui/channel-tax-provider-form.tsx index 3b7a1a2..2e00915 100644 --- a/apps/taxes/src/modules/channels/ui/channel-tax-provider-form.tsx +++ b/apps/taxes/src/modules/channels/ui/channel-tax-provider-form.tsx @@ -106,6 +106,7 @@ export const ChannelTaxProviderForm = () => { React.useEffect(() => { const defaultValues = getDefaultFormValues(channelConfig, providerInstances); + reset(defaultValues); }, [channelConfig, providerInstances, reset]); diff --git a/apps/taxes/src/modules/channels/ui/channel-tax-provider.tsx b/apps/taxes/src/modules/channels/ui/channel-tax-provider.tsx index 3cd46d7..8005f58 100644 --- a/apps/taxes/src/modules/channels/ui/channel-tax-provider.tsx +++ b/apps/taxes/src/modules/channels/ui/channel-tax-provider.tsx @@ -5,7 +5,7 @@ import { Button, makeStyles } from "@saleor/macaw-ui"; import { PropsWithChildren } from "react"; import { useAppRedirect } from "../../../lib/app/redirect"; import { ProviderIcon } from "../../providers-configuration/ui/provider-icon"; -import { providerConfig, TaxProviderName } from "../../taxes/providers/config"; +import { providerConfig, TaxProviderName } from "../../taxes/provider-config"; import { useActiveTab, useChannelSlug, useInstanceId } from "../../taxes/tax-context"; import { trpcClient } from "../../trpc/trpc-client"; import { AppLink } from "../../ui/app-link"; @@ -27,6 +27,7 @@ const NoDataPlaceholder = ({ title: string; }>) => { const styles = useStyles(); + return (
@@ -45,6 +46,7 @@ const NoDataPlaceholder = ({ const NoChannelPlaceholder = () => { const { redirect } = useAppRedirect(); + return ( diff --git a/apps/taxes/src/modules/crud-settings/crud-settings.service.ts b/apps/taxes/src/modules/crud-settings/crud-settings.service.ts index 130d759..90a0af7 100644 --- a/apps/taxes/src/modules/crud-settings/crud-settings.service.ts +++ b/apps/taxes/src/modules/crud-settings/crud-settings.service.ts @@ -7,7 +7,7 @@ import { createId } from "../../lib/utils"; const settingSchema = z.record(z.any()).and(z.object({ id: z.string() })); const settingsSchema = z.array(settingSchema); -export class CrudSettingsConfigurator { +export class CrudSettingsManager { private logger: pino.Logger; constructor( @@ -16,7 +16,7 @@ export class CrudSettingsConfigurator { private metadataKey: string ) { this.metadataKey = metadataKey; - this.logger = createLogger({ service: "CrudSettingsConfigurator", metadataKey }); + this.logger = createLogger({ service: "CrudSettingsManager", metadataKey }); } async readAll() { @@ -47,6 +47,7 @@ export class CrudSettingsConfigurator { const { data: settings } = result; const item = settings.find((item) => item.id === id); + if (!item) { this.logger.error({ id }, "Item not found"); throw new Error("Item not found"); @@ -65,6 +66,7 @@ export class CrudSettingsConfigurator { const id = createId(); const newData = [...prevData, { ...data, id }]; + await this.metadataManager.set({ key: this.metadataKey, value: JSON.stringify(newData), diff --git a/apps/taxes/src/modules/providers-configuration/private-providers-configuration-service.ts b/apps/taxes/src/modules/providers-configuration/private-providers-configuration-service.ts deleted file mode 100644 index 5f5c042..0000000 --- a/apps/taxes/src/modules/providers-configuration/private-providers-configuration-service.ts +++ /dev/null @@ -1,28 +0,0 @@ -import pino from "pino"; -import { Client } from "urql"; -import { createLogger } from "../../lib/logger"; -import { AvataxConfigurationService } from "../avatax/avatax-configuration.service"; -import { TaxJarConfigurationService } from "../taxjar/taxjar-configuration.service"; - -export const TAX_PROVIDER_KEY = "tax-providers"; - -export class PrivateTaxProvidersConfigurationService { - private avataxConfigurationService: AvataxConfigurationService; - private taxJarConfigurationService: TaxJarConfigurationService; - private logger: pino.Logger; - constructor(client: Client, saleorApiUrl: string) { - this.avataxConfigurationService = new AvataxConfigurationService(client, saleorApiUrl); - this.taxJarConfigurationService = new TaxJarConfigurationService(client, saleorApiUrl); - this.logger = createLogger({ - service: "PrivateTaxProvidersConfigurationService", - metadataKey: TAX_PROVIDER_KEY, - }); - } - - async getAll() { - this.logger.debug(".getAll called"); - const taxJar = await this.taxJarConfigurationService.getAll(); - const avatax = await this.avataxConfigurationService.getAll(); - return [...taxJar, ...avatax]; - } -} diff --git a/apps/taxes/src/modules/providers-configuration/public-providers-configuration-service.ts b/apps/taxes/src/modules/providers-configuration/public-providers-configuration-service.ts index 969647a..8babece 100644 --- a/apps/taxes/src/modules/providers-configuration/public-providers-configuration-service.ts +++ b/apps/taxes/src/modules/providers-configuration/public-providers-configuration-service.ts @@ -25,6 +25,7 @@ export class PublicTaxProvidersConfigurationService { this.logger.debug(".getAll called"); const taxJar = await this.taxJarConfigurationService.getAll(); const avatax = await this.avataxConfigurationService.getAll(); + return [...obfuscateTaxJarInstances(taxJar), ...obfuscateAvataxInstances(avatax)]; } } diff --git a/apps/taxes/src/modules/providers-configuration/ui/configuration.tsx b/apps/taxes/src/modules/providers-configuration/ui/configuration.tsx index 5154ca3..6b7f721 100644 --- a/apps/taxes/src/modules/providers-configuration/ui/configuration.tsx +++ b/apps/taxes/src/modules/providers-configuration/ui/configuration.tsx @@ -2,7 +2,7 @@ import { FormControlLabel, Grid, Radio, RadioGroup, Typography } from "@material import { makeStyles } from "@saleor/macaw-ui"; import React from "react"; import { AvataxConfiguration } from "../../avatax/ui/avatax-configuration"; -import { providerConfig, TaxProviderName } from "../../taxes/providers/config"; +import { providerConfig, TaxProviderName } from "../../taxes/provider-config"; import { TaxJarConfiguration } from "../../taxjar/ui/taxjar-configuration"; import { useInstanceId } from "../../taxes/tax-context"; import { trpcClient } from "../../trpc/trpc-client"; @@ -48,6 +48,7 @@ export const Configuration = () => { React.useEffect(() => { const instance = providersConfigurationData?.find((instance) => instance.id === instanceId); + setProvider(instance?.provider ?? "taxjar"); }, [instanceId, providersConfigurationData]); diff --git a/apps/taxes/src/modules/providers-configuration/ui/delete-provider-dialog.tsx b/apps/taxes/src/modules/providers-configuration/ui/delete-provider-dialog.tsx index 2c22cde..247c70b 100644 --- a/apps/taxes/src/modules/providers-configuration/ui/delete-provider-dialog.tsx +++ b/apps/taxes/src/modules/providers-configuration/ui/delete-provider-dialog.tsx @@ -23,6 +23,7 @@ const useStyles = makeStyles((theme) => ({ export const DeleteProviderDialog = (p: DeleteProviderDialogProps) => { const styles = useStyles(); + return ( Delete provider instance? diff --git a/apps/taxes/src/modules/providers-configuration/ui/provider-icon.tsx b/apps/taxes/src/modules/providers-configuration/ui/provider-icon.tsx index 59274fe..d62d127 100644 --- a/apps/taxes/src/modules/providers-configuration/ui/provider-icon.tsx +++ b/apps/taxes/src/modules/providers-configuration/ui/provider-icon.tsx @@ -1,5 +1,5 @@ import Image, { ImageProps } from "next/image"; -import { providerConfig, TaxProviderName } from "../../taxes/providers/config"; +import { providerConfig, TaxProviderName } from "../../taxes/provider-config"; type Size = "small" | "medium" | "large" | "xlarge"; @@ -18,6 +18,7 @@ type ProviderIconProps = { export const ProviderIcon = ({ provider, size = "medium", ...props }: ProviderIconProps) => { const { icon, label } = providerConfig[provider]; const matchedSize = sizes[size]; + return ( {`${label} ); diff --git a/apps/taxes/src/modules/taxes/active-tax-provider.test.ts b/apps/taxes/src/modules/taxes/active-tax-provider.test.ts new file mode 100644 index 0000000..6384ea2 --- /dev/null +++ b/apps/taxes/src/modules/taxes/active-tax-provider.test.ts @@ -0,0 +1,149 @@ +import { encrypt } from "@saleor/app-sdk/settings-manager"; +import { describe, expect, it, vi } from "vitest"; +import { MetadataItem } from "../../../generated/graphql"; +import { ChannelsConfig } from "../channels-configuration/channels-config"; +import { ProvidersConfig } from "../providers-configuration/providers-config"; +import { getActiveTaxProvider } from "./active-tax-provider"; + +const mockedInvalidMetadata: MetadataItem[] = [ + { + key: "providers", + value: JSON.stringify({ + foo: "bar", + }), + }, +]; + +const mockedSecretKey = "test_secret_key"; +const mockedProviders: ProvidersConfig = [ + { + provider: "avatax", + id: "1", + config: { + companyCode: "DEFAULT", + isAutocommit: false, + isSandbox: true, + name: "avatax-1", + password: "avatax-password", + username: "avatax-username", + }, + }, + { + provider: "taxjar", + id: "2", + config: { + name: "taxjar-1", + apiKey: "taxjar-api-key", + isSandbox: true, + }, + }, +]; +const mockedEncryptedProviders = encrypt(JSON.stringify(mockedProviders), mockedSecretKey); + +const mockedChannelsWithInvalidProviderInstanceId: ChannelsConfig = { + "default-channel": { + address: { + city: "New York", + country: "US", + state: "NY", + street: "123 Main St", + zip: "10001", + }, + enabled: true, + providerInstanceId: "3", + }, +}; + +const mockedValidChannels: ChannelsConfig = { + "default-channel": { + address: { + city: "New York", + country: "US", + state: "NY", + street: "123 Main St", + zip: "10001", + }, + enabled: true, + providerInstanceId: "1", + }, +}; + +const mockedInvalidEncryptedChannels = encrypt( + JSON.stringify(mockedChannelsWithInvalidProviderInstanceId), + mockedSecretKey +); + +const mockedValidEncryptedChannels = encrypt(JSON.stringify(mockedValidChannels), mockedSecretKey); + +vi.stubEnv("SECRET_KEY", mockedSecretKey); + +describe("getActiveTaxProvider", () => { + it("should return ok: false when channel slug is missing", () => { + const result = getActiveTaxProvider("", mockedInvalidMetadata); + + expect(result.ok).toBe(false); + if (!result.ok) { + expect(result.error).toBe("channel_slug_missing"); + } + }); + + it("should return ok: false when there are no metadata items", () => { + const result = getActiveTaxProvider("default-channel", []); + + expect(result.ok).toBe(false); + if (!result.ok) { + expect(result.error).toBe("app_encrypted_metadata_missing"); + } + }); + + it("should return ok: false when no providerInstanceId was found", () => { + const result = getActiveTaxProvider("default-channel", [ + { + key: "providers", + value: mockedEncryptedProviders, + }, + { + key: "channels", + value: mockedInvalidEncryptedChannels, + }, + ]); + + expect(result.ok).toBe(false); + if (!result.ok) { + expect(result.error).toBe("no_match_for_channel_provider_instance_id"); + } + }); + + it("should return ok: false when no channel was found for channelSlug", () => { + const result = getActiveTaxProvider("invalid-channel", [ + { + key: "providers", + value: mockedEncryptedProviders, + }, + { + key: "channels", + value: mockedValidEncryptedChannels, + }, + ]); + + expect(result.ok).toBe(false); + if (!result.ok) { + expect(result.error).toBe("channel_config_not_found"); + } + }); + + it("should return ok: true when data is correct", () => { + const result = getActiveTaxProvider("default-channel", [ + { + key: "providers", + value: mockedEncryptedProviders, + }, + { + key: "channels", + value: mockedValidEncryptedChannels, + }, + ]); + + expect(result.ok).toBe(true); + }); +}); diff --git a/apps/taxes/src/modules/taxes/active-tax-provider.ts b/apps/taxes/src/modules/taxes/active-tax-provider.ts index 7fe233a..8024b46 100644 --- a/apps/taxes/src/modules/taxes/active-tax-provider.ts +++ b/apps/taxes/src/modules/taxes/active-tax-provider.ts @@ -1,45 +1,113 @@ -import { TaxBaseFragment } from "../../../generated/graphql"; +import { + MetadataItem, + OrderCreatedSubscriptionFragment, + OrderFulfilledSubscriptionFragment, + TaxBaseFragment, +} from "../../../generated/graphql"; import { createLogger } from "../../lib/logger"; import { ChannelConfig } from "../channels-configuration/channels-config"; import { ProviderConfig } from "../providers-configuration/providers-config"; -import { AvataxProvider } from "../avatax/avatax-provider"; -import { TaxJarProvider } from "../taxjar/taxjar-provider"; -import { TaxProvider } from "./tax-provider"; +import { AvataxWebhookService } from "../avatax/avatax-webhook.service"; +import { TaxJarWebhookService } from "../taxjar/taxjar-webhook.service"; +import { ProviderWebhookService } from "./tax-provider-webhook"; import { TaxProviderError } from "./tax-provider-error"; import pino from "pino"; +import { getAppConfig } from "../app/get-app-config"; -export class ActiveTaxProvider { - private client: TaxProvider; +type ActiveTaxProviderResult = { ok: true; data: ActiveTaxProvider } | { ok: false; error: string }; + +export function getActiveTaxProvider( + channelSlug: string | undefined, + encryptedMetadata: MetadataItem[] +): ActiveTaxProviderResult { + const logger = createLogger({ service: "getActiveTaxProvider" }); + + if (!channelSlug) { + logger.error("Channel slug is missing"); + return { error: "channel_slug_missing", ok: false }; + } + + if (!encryptedMetadata.length) { + logger.error("App encryptedMetadata is missing"); + return { error: "app_encrypted_metadata_missing", ok: false }; + } + + const { providers, channels } = getAppConfig(encryptedMetadata); + + const channelConfig = channels[channelSlug]; + + if (!channelConfig) { + // * will happen when `order-created` webhook is triggered by creating an order in a channel that doesn't use the tax app + logger.info(`Channel config not found for channel ${channelSlug}`); + return { error: `channel_config_not_found`, ok: false }; + } + + const providerInstance = providers.find( + (instance) => instance.id === channelConfig.providerInstanceId + ); + + if (!providerInstance) { + logger.error(`Channel (${channelSlug}) providerInstanceId does not match any providers`); + return { + error: `no_match_for_channel_provider_instance_id`, + ok: false, + }; + } + + // todo: refactor so it doesnt create activeTaxProvider + const taxProvider = new ActiveTaxProvider(providerInstance, channelConfig); + + return { data: taxProvider, ok: true }; +} + +// todo: refactor to a factory +export class ActiveTaxProvider implements ProviderWebhookService { + private client: ProviderWebhookService; private logger: pino.Logger; + private channel: ChannelConfig; - constructor(providerInstance: ProviderConfig) { + constructor(providerInstance: ProviderConfig, channelConfig: ChannelConfig) { this.logger = createLogger({ service: "ActiveTaxProvider", }); const taxProviderName = providerInstance.provider; + this.logger.trace({ taxProviderName }, "Constructing tax provider: "); + this.channel = channelConfig; switch (taxProviderName) { case "taxjar": - this.client = new TaxJarProvider(providerInstance.config); + this.client = new TaxJarWebhookService(providerInstance.config); break; case "avatax": - this.client = new AvataxProvider(providerInstance.config); + this.client = new AvataxWebhookService(providerInstance.config); break; default: { - throw new TaxProviderError(`Tax provider ${taxProviderName} doesnt match`, { + throw new TaxProviderError(`Tax provider ${taxProviderName} doesn't match`, { cause: "TaxProviderNotFound", }); } } } - async calculate(payload: TaxBaseFragment, channel: ChannelConfig) { - this.logger.debug({ payload, channel }, ".calculate called"); + async calculateTaxes(payload: TaxBaseFragment) { + this.logger.debug({ payload }, ".calculate called"); - return this.client.calculate(payload, channel); + return this.client.calculateTaxes(payload, this.channel); + } + + async createOrder(order: OrderCreatedSubscriptionFragment) { + this.logger.debug(".createOrder called"); + + return this.client.createOrder(order, this.channel); + } + + async fulfillOrder(payload: OrderFulfilledSubscriptionFragment) { + this.logger.debug(".fulfillOrder called"); + + return this.client.fulfillOrder(payload, this.channel); } } diff --git a/apps/taxes/src/modules/taxes/numbers.test.ts b/apps/taxes/src/modules/taxes/numbers.test.ts new file mode 100644 index 0000000..bd927a0 --- /dev/null +++ b/apps/taxes/src/modules/taxes/numbers.test.ts @@ -0,0 +1,22 @@ +import { describe, expect, it } from "vitest"; +import { numbers } from "./numbers"; + +describe("roundFloatToTwoDecimals", () => { + it("returns the correct value when multiple decimals", () => { + const result = numbers.roundFloatToTwoDecimals(24.33333); + + expect(result).toBe(24.33); + }); + + it("returns the correct value when single decimal", () => { + const result = numbers.roundFloatToTwoDecimals(24.3); + + expect(result).toBe(24.3); + }); + + it("returns the correct value when no decimals", () => { + const result = numbers.roundFloatToTwoDecimals(24); + + expect(result).toBe(24); + }); +}); diff --git a/apps/taxes/src/modules/taxes/numbers.ts b/apps/taxes/src/modules/taxes/numbers.ts new file mode 100644 index 0000000..f124431 --- /dev/null +++ b/apps/taxes/src/modules/taxes/numbers.ts @@ -0,0 +1,7 @@ +function roundFloatToTwoDecimals(float: number): number { + return Math.round(float * 100) / 100; +} + +export const numbers = { + roundFloatToTwoDecimals, +}; diff --git a/apps/taxes/src/modules/taxes/providers/config.ts b/apps/taxes/src/modules/taxes/provider-config.ts similarity index 78% rename from apps/taxes/src/modules/taxes/providers/config.ts rename to apps/taxes/src/modules/taxes/provider-config.ts index 8d621d6..90aa289 100644 --- a/apps/taxes/src/modules/taxes/providers/config.ts +++ b/apps/taxes/src/modules/taxes/provider-config.ts @@ -1,4 +1,4 @@ -import { AvataxIcon, TaxJarIcon } from "../../../assets"; +import { AvataxIcon, TaxJarIcon } from "../../assets"; export const providerConfig = { taxjar: { diff --git a/apps/taxes/src/modules/taxes/tax-common-schema.ts b/apps/taxes/src/modules/taxes/tax-common-schema.ts deleted file mode 100644 index fc3208b..0000000 --- a/apps/taxes/src/modules/taxes/tax-common-schema.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { z } from "zod"; - -export const addressSchema = z.object({ - country: z.string(), - zip: z.string(), - state: z.string(), - city: z.string(), - street: z.string(), -}); diff --git a/apps/taxes/src/modules/taxes/tax-line-resolver.ts b/apps/taxes/src/modules/taxes/tax-line-resolver.ts index e2fa024..bb72404 100644 --- a/apps/taxes/src/modules/taxes/tax-line-resolver.ts +++ b/apps/taxes/src/modules/taxes/tax-line-resolver.ts @@ -10,6 +10,7 @@ const getLineDiscount = ( } const lineTotalAmount = Number(line.totalPrice.amount); const discountAmount = (lineTotalAmount / allLinesTotal) * totalDiscount; + if (discountAmount > lineTotalAmount) { return lineTotalAmount; } diff --git a/apps/taxes/src/modules/taxes/tax-provider-webhook.ts b/apps/taxes/src/modules/taxes/tax-provider-webhook.ts new file mode 100644 index 0000000..1b778ba --- /dev/null +++ b/apps/taxes/src/modules/taxes/tax-provider-webhook.ts @@ -0,0 +1,26 @@ +import { SyncWebhookResponsesMap } from "@saleor/app-sdk/handlers/next"; +import { + OrderCreatedSubscriptionFragment, + OrderFulfilledSubscriptionFragment, + TaxBaseFragment, +} from "../../../generated/graphql"; +import { ChannelConfig } from "../channels-configuration/channels-config"; + +export type CalculateTaxesResponse = SyncWebhookResponsesMap["ORDER_CALCULATE_TAXES"]; + +export type CreateOrderResponse = { id: string }; + +export interface ProviderWebhookService { + calculateTaxes: ( + payload: TaxBaseFragment, + channel: ChannelConfig + ) => Promise; + createOrder: ( + payload: OrderCreatedSubscriptionFragment, + channel: ChannelConfig + ) => Promise; + fulfillOrder: ( + payload: OrderFulfilledSubscriptionFragment, + channel: ChannelConfig + ) => Promise<{ ok: boolean }>; +} diff --git a/apps/taxes/src/modules/taxes/tax-provider.ts b/apps/taxes/src/modules/taxes/tax-provider.ts deleted file mode 100644 index bede921..0000000 --- a/apps/taxes/src/modules/taxes/tax-provider.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { TaxBaseFragment } from "../../../generated/graphql"; -import { ChannelConfig } from "../channels-configuration/channels-config"; -import { TaxProviderName } from "./providers/config"; -import { ResponseTaxPayload } from "./types"; - -export interface TaxProvider { - name: TaxProviderName; - calculate: (payload: TaxBaseFragment, channel: ChannelConfig) => Promise; -} diff --git a/apps/taxes/src/modules/taxes/types.ts b/apps/taxes/src/modules/taxes/types.ts deleted file mode 100644 index fea6042..0000000 --- a/apps/taxes/src/modules/taxes/types.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { SyncWebhookResponsesMap } from "@saleor/app-sdk/handlers/next"; - -export type ResponseTaxPayload = SyncWebhookResponsesMap["ORDER_CALCULATE_TAXES"]; diff --git a/apps/taxes/src/modules/taxjar/maps/taxjar-calculate-taxes-map.test.ts b/apps/taxes/src/modules/taxjar/maps/taxjar-calculate-taxes-map.test.ts new file mode 100644 index 0000000..55b8f59 --- /dev/null +++ b/apps/taxes/src/modules/taxjar/maps/taxjar-calculate-taxes-map.test.ts @@ -0,0 +1,14 @@ +import { describe, it } from "vitest"; + +describe.skip("taxJarCalculateTaxesMaps", () => { + describe.todo("mapResponse", () => { + it.todo("calculation of fields"); + it.todo("formatting the fields"); + it.todo("rounding of numbers"); + }); + describe.todo("mapPayload", () => { + it.todo("calculation of fields"); + it.todo("formatting the fields"); + it.todo("rounding of numbers"); + }); +}); diff --git a/apps/taxes/src/modules/taxjar/taxjar-calculate.ts b/apps/taxes/src/modules/taxjar/maps/taxjar-calculate-taxes-map.ts similarity index 60% rename from apps/taxes/src/modules/taxjar/taxjar-calculate.ts rename to apps/taxes/src/modules/taxjar/maps/taxjar-calculate-taxes-map.ts index 190ab6c..4e91f61 100644 --- a/apps/taxes/src/modules/taxjar/taxjar-calculate.ts +++ b/apps/taxes/src/modules/taxjar/maps/taxjar-calculate-taxes-map.ts @@ -1,13 +1,13 @@ -import { TaxParams } from "taxjar/dist/types/paramTypes"; import { TaxForOrderRes } from "taxjar/dist/types/returnTypes"; import { TaxBaseFragment, TaxBaseLineFragment, TaxDiscountFragment, -} from "../../../generated/graphql"; -import { ChannelConfig } from "../channels-configuration/channels-config"; -import { taxLineResolver } from "../taxes/tax-line-resolver"; -import { ResponseTaxPayload } from "../taxes/types"; +} from "../../../../generated/graphql"; +import { ChannelConfig } from "../../channels-configuration/channels-config"; +import { taxLineResolver } from "../../taxes/tax-line-resolver"; +import { CalculateTaxesResponse } from "../../taxes/tax-provider-webhook"; +import { FetchTaxForOrderArgs } from "../taxjar-client"; const formatCalculatedAmount = (amount: number) => { return Number(amount.toFixed(2)); @@ -54,16 +54,19 @@ const prepareLinesWithDiscountPayload = ( }); }; -const prepareResponse = ( +const mapResponse = ( payload: TaxBaseFragment, - response: TaxForOrderRes, - linesWithChargeTaxes: FetchTaxesLinePayload[], - linesWithDiscount: FetchTaxesLinePayload[] -): ResponseTaxPayload => { + response: TaxForOrderRes +): CalculateTaxesResponse => { + const linesWithDiscount = prepareLinesWithDiscountPayload(payload.lines, payload.discounts); + const linesWithChargeTaxes = linesWithDiscount.filter((line) => line.chargeTaxes === true); + const taxResponse = linesWithChargeTaxes.length !== 0 ? response : undefined; const taxDetails = taxResponse?.tax.breakdown; - // todo: investigate - // ! There is no shipping in tax.breakdown from TaxJar. + /** + * todo: investigate + * ! There is no shipping in tax.breakdown from TaxJar. + */ const shippingDetails = taxDetails?.shipping; const shippingPriceGross = shippingDetails @@ -74,12 +77,15 @@ const prepareResponse = ( : payload.shippingPrice.amount; const shippingTaxRate = shippingDetails ? shippingDetails.combined_tax_rate : 0; // ! It appears shippingTaxRate is always 0 from TaxJar. + return { shipping_price_gross_amount: formatCalculatedAmount(shippingPriceGross), shipping_price_net_amount: formatCalculatedAmount(shippingPriceNet), shipping_tax_rate: shippingTaxRate, - // lines order needs to be the same as for recieved payload. - // lines that have chargeTaxes === false will have returned default value + /** + * lines order needs to be the same as for received payload. + * lines that have chargeTaxes === false will have returned default value + */ lines: linesWithDiscount.map((line) => { const lineTax = taxDetails?.line_items?.find((l) => l.id === line.id); const totalGrossAmount = lineTax @@ -87,6 +93,7 @@ const prepareResponse = ( : line.totalAmount - line.discount; const totalNetAmount = lineTax ? lineTax.taxable_amount : line.totalAmount - line.discount; const taxRate = lineTax ? lineTax.combined_tax_rate : 0; + return { total_gross_amount: formatCalculatedAmount(totalGrossAmount), total_net_amount: formatCalculatedAmount(totalNetAmount), @@ -96,37 +103,37 @@ const prepareResponse = ( }; }; -const preparePayload = ( - taxBase: TaxBaseFragment, - channel: ChannelConfig, - linesWithChargeTaxes: FetchTaxesLinePayload[] -): TaxParams => { +const mapPayload = (taxBase: TaxBaseFragment, channel: ChannelConfig): FetchTaxForOrderArgs => { + const linesWithDiscount = prepareLinesWithDiscountPayload(taxBase.lines, taxBase.discounts); + const linesWithChargeTaxes = linesWithDiscount.filter((line) => line.chargeTaxes === true); + const taxParams = { - from_country: channel.address.country, - from_zip: channel.address.zip, - from_state: channel.address.state, - from_city: channel.address.city, - from_street: channel.address.street, - to_country: taxBase.address!.country.code, - to_zip: taxBase.address!.postalCode, - to_state: taxBase.address!.countryArea, - to_city: taxBase.address!.city, - to_street: `${taxBase.address!.streetAddress1} ${taxBase.address!.streetAddress2}`, - shipping: taxBase.shippingPrice.amount, - line_items: linesWithChargeTaxes.map((line) => ({ - id: line.id, - quantity: line.quantity, - product_tax_code: line.taxCode || undefined, - unit_price: line.unitAmount, - discount: line.discount, - })), + params: { + from_country: channel.address.country, + from_zip: channel.address.zip, + from_state: channel.address.state, + from_city: channel.address.city, + from_street: channel.address.street, + to_country: taxBase.address!.country.code, + to_zip: taxBase.address!.postalCode, + to_state: taxBase.address!.countryArea, + to_city: taxBase.address!.city, + to_street: `${taxBase.address!.streetAddress1} ${taxBase.address!.streetAddress2}`, + shipping: taxBase.shippingPrice.amount, + line_items: linesWithChargeTaxes.map((line) => ({ + id: line.id, + quantity: line.quantity, + product_tax_code: line.taxCode || undefined, + unit_price: line.unitAmount, + discount: line.discount, + })), + }, }; return taxParams; }; -export const taxJarCalculate = { - prepareLinesWithDiscountPayload, - prepareResponse, - preparePayload, +export const taxJarCalculateTaxesMaps = { + mapPayload, + mapResponse, }; diff --git a/apps/taxes/src/modules/taxjar/maps/taxjar-order-created-map.test.ts b/apps/taxes/src/modules/taxjar/maps/taxjar-order-created-map.test.ts new file mode 100644 index 0000000..593a249 --- /dev/null +++ b/apps/taxes/src/modules/taxjar/maps/taxjar-order-created-map.test.ts @@ -0,0 +1,175 @@ +import { describe, expect, it } from "vitest"; +import { OrderStatus } from "../../../../generated/graphql"; +import { + TaxJarOrderCreatedMapPayloadArgs, + taxJarOrderCreatedMaps, +} from "./taxjar-order-created-map"; + +const MOCKED_ORDER: TaxJarOrderCreatedMapPayloadArgs = { + order: { + id: "T3JkZXI6OTU4MDA5YjQtNDUxZC00NmQ1LThhMWUtMTRkMWRmYjFhNzI5", + created: "2023-04-11T11:03:09.304109+00:00", + status: OrderStatus.Unfulfilled, + user: { + id: "VXNlcjo5ZjY3ZjY0Zi1iZjY5LTQ5ZjYtYjQ4Zi1iZjY3ZjY0ZjY0ZjY=", + email: "tester@saleor.io", + }, + channel: { + id: "Q2hhbm5lbDoy", + slug: "channel-pln", + }, + shippingAddress: { + streetAddress1: "123 Palm Grove Ln", + streetAddress2: "", + city: "LOS ANGELES", + countryArea: "CA", + postalCode: "90002", + country: { + code: "US", + }, + }, + billingAddress: { + streetAddress1: "123 Palm Grove Ln", + streetAddress2: "", + city: "LOS ANGELES", + countryArea: "CA", + postalCode: "90002", + country: { + code: "US", + }, + }, + total: { + net: { + amount: 183.33, + }, + tax: { + amount: 12.83, + }, + currency: "USD", + }, + shippingPrice: { + net: { + amount: 48.33, + }, + }, + lines: [ + { + productSku: "328223581", + productName: "Monospace Tee", + quantity: 1, + unitPrice: { + net: { + amount: 90, + }, + }, + totalPrice: { + tax: { + amount: 8.55, + }, + }, + }, + { + productSku: "328223580", + productName: "Monospace Tee", + quantity: 1, + unitPrice: { + net: { + amount: 45, + }, + }, + totalPrice: { + tax: { + amount: 4.28, + }, + }, + }, + ], + }, + channel: { + providerInstanceId: "b8c29f49-7cae-4762-8458-e9a27eb83081", + enabled: false, + address: { + country: "US", + zip: "92093", + state: "CA", + city: "La Jolla", + street: "9500 Gilman Drive", + }, + }, +}; + +describe("taxJarOrderCreatedMaps", () => { + describe("mapPayload", () => { + it.todo("calculation of fields"); + it.todo("formatting the fields"); + it.todo("rounding of numbers"); + it("returns the correct order amount", () => { + const result = taxJarOrderCreatedMaps.mapPayload(MOCKED_ORDER); + + expect(result.params.amount).toBe(183.33); + }); + }); + + describe.todo("mapResponse", () => { + it.todo("calculation of fields"); + it.todo("formatting the fields"); + it.todo("rounding of numbers"); + }); + + describe("sumLines", () => { + it("returns the sum of all line items when items quantity = 1", () => { + const result = taxJarOrderCreatedMaps.sumLines([ + { + quantity: 1, + unit_price: 90.45, + product_identifier: "328223581", + }, + { + quantity: 1, + unit_price: 45.25, + product_identifier: "328223580", + }, + ]); + + expect(result).toBe(135.7); + }); + it("returns the sum of all line items when items quantity > 1", () => { + const result = taxJarOrderCreatedMaps.sumLines([ + { + quantity: 3, + unit_price: 90.45, + product_identifier: "328223581", + }, + { + quantity: 2, + unit_price: 45.25, + product_identifier: "328223580", + }, + { + quantity: 1, + unit_price: 50.25, + product_identifier: "328223580", + }, + ]); + + expect(result).toBe(412.1); + }); + + it("returns the rounded sum of all line items when line items n of decimals > 2", () => { + const result = taxJarOrderCreatedMaps.sumLines([ + { + quantity: 3, + unit_price: 10.256, + product_identifier: "328223581", + }, + { + quantity: 2, + unit_price: 50.512, + product_identifier: "328223580", + }, + ]); + + expect(result).toBe(131.79); + }); + }); +}); diff --git a/apps/taxes/src/modules/taxjar/maps/taxjar-order-created-map.ts b/apps/taxes/src/modules/taxjar/maps/taxjar-order-created-map.ts new file mode 100644 index 0000000..65cec8c --- /dev/null +++ b/apps/taxes/src/modules/taxjar/maps/taxjar-order-created-map.ts @@ -0,0 +1,76 @@ +import { LineItem } from "taxjar/dist/types/paramTypes"; +import { CreateOrderRes } from "taxjar/dist/types/returnTypes"; +import { OrderCreatedSubscriptionFragment } from "../../../../generated/graphql"; +import { ChannelConfig } from "../../channels-configuration/channels-config"; +import { CreateOrderResponse } from "../../taxes/tax-provider-webhook"; +import { CreateOrderArgs } from "../taxjar-client"; +import { numbers } from "../../taxes/numbers"; + +function mapLines(lines: OrderCreatedSubscriptionFragment["lines"]): LineItem[] { + return lines.map((line) => ({ + quantity: line.quantity, + unit_price: line.unitPrice.net.amount, + product_identifier: line.productSku ?? "", + // todo: add from tax code matcher + product_tax_code: "", + sales_tax: line.totalPrice.tax.amount, + })); +} + +function sumLines(lines: LineItem[]): number { + return numbers.roundFloatToTwoDecimals( + lines.reduce((prev, next) => prev + (next.unit_price ?? 0) * (next.quantity ?? 0), 0) + ); +} + +export type TaxJarOrderCreatedMapPayloadArgs = { + order: OrderCreatedSubscriptionFragment; + channel: ChannelConfig; +}; + +const mapPayload = ({ order, channel }: TaxJarOrderCreatedMapPayloadArgs): CreateOrderArgs => { + const lineItems = mapLines(order.lines); + const lineSum = sumLines(lineItems); + const shippingAmount = order.shippingPrice.net.amount; + /** + * "The TaxJar API performs arbitrary-precision decimal arithmetic for accurately calculating sales tax." + * but we want to round to 2 decimals for consistency + */ + const orderAmount = numbers.roundFloatToTwoDecimals(shippingAmount + lineSum); + + return { + params: { + from_country: channel.address.country, + from_zip: channel.address.zip, + from_state: channel.address.state, + from_city: channel.address.city, + from_street: channel.address.street, + to_country: order.shippingAddress!.country.code, + to_zip: order.shippingAddress!.postalCode, + to_state: order.shippingAddress!.countryArea, + to_city: order.shippingAddress!.city, + to_street: `${order.shippingAddress!.streetAddress1} ${ + order.shippingAddress!.streetAddress2 + }`, + shipping: shippingAmount, + line_items: lineItems, + transaction_date: order.created, + transaction_id: order.id, + amount: orderAmount, // Total amount of the order with shipping, excluding sales tax in dollars. + // todo: add sales_tax + sales_tax: 0, + }, + }; +}; + +const mapResponse = (response: CreateOrderRes): CreateOrderResponse => { + return { + id: response.order.transaction_id, + }; +}; + +export const taxJarOrderCreatedMaps = { + mapPayload, + mapResponse, + sumLines, +}; diff --git a/apps/taxes/src/modules/taxjar/taxjar-client.ts b/apps/taxes/src/modules/taxjar/taxjar-client.ts index 4cf7ada..d4d3812 100644 --- a/apps/taxes/src/modules/taxjar/taxjar-client.ts +++ b/apps/taxes/src/modules/taxjar/taxjar-client.ts @@ -1,6 +1,6 @@ import pino from "pino"; import TaxJar from "taxjar"; -import { Config, TaxForOrderRes, TaxParams } from "taxjar/dist/util/types"; +import { Config, CreateOrderParams, TaxParams } from "taxjar/dist/util/types"; import { createLogger } from "../../lib/logger"; import { TaxJarConfig } from "./taxjar-config"; @@ -13,6 +13,14 @@ const createTaxJarSettings = (config: TaxJarConfig): Config => { return settings; }; +export type FetchTaxForOrderArgs = { + params: TaxParams; +}; + +export type CreateOrderArgs = { + params: CreateOrderParams; +}; + export class TaxJarClient { private client: TaxJar; private logger: pino.Logger; @@ -22,13 +30,15 @@ export class TaxJarClient { this.logger.trace("TaxJarClient constructor"); const settings = createTaxJarSettings(providerConfig); const taxJarClient = new TaxJar(settings); + this.logger.trace({ client: taxJarClient }, "External TaxJar client created"); this.client = taxJarClient; } - async fetchTaxesForOrder(params: TaxParams) { - this.logger.debug({ params }, "fetchTaxesForOrder called with:"); - const response: TaxForOrderRes = await this.client.taxForOrder(params); + async fetchTaxForOrder({ params }: FetchTaxForOrderArgs) { + this.logger.debug({ params }, "fetchTaxForOrder called with:"); + const response = await this.client.taxForOrder(params); + return response; } @@ -44,4 +54,10 @@ export class TaxJarClient { }; } } + + async createOrder({ params }: CreateOrderArgs) { + this.logger.debug("createOrder called with:"); + + return this.client.createOrder(params); + } } diff --git a/apps/taxes/src/modules/taxjar/taxjar-configuration.router.ts b/apps/taxes/src/modules/taxjar/taxjar-configuration.router.ts index 6e94c29..25c8a80 100644 --- a/apps/taxes/src/modules/taxjar/taxjar-configuration.router.ts +++ b/apps/taxes/src/modules/taxjar/taxjar-configuration.router.ts @@ -18,6 +18,7 @@ const patchInputSchema = z.object({ id: z.string(), value: taxJarConfigSchema.partial().transform((c) => { const { apiKey, ...config } = c ?? {}; + return { ...config, ...(apiKey && !isObfuscated(apiKey) && { apiKey }), diff --git a/apps/taxes/src/modules/taxjar/taxjar-configuration.service.ts b/apps/taxes/src/modules/taxjar/taxjar-configuration.service.ts index 6134f79..b6b40e9 100644 --- a/apps/taxes/src/modules/taxjar/taxjar-configuration.service.ts +++ b/apps/taxes/src/modules/taxjar/taxjar-configuration.service.ts @@ -1,8 +1,8 @@ import pino from "pino"; import { Client } from "urql"; import { createLogger } from "../../lib/logger"; -import { createSettingsManager } from "../app-configuration/metadata-manager"; -import { CrudSettingsConfigurator } from "../crud-settings/crud-settings.service"; +import { createSettingsManager } from "../app/metadata-manager"; +import { CrudSettingsManager } from "../crud-settings/crud-settings.service"; import { providersSchema } from "../providers-configuration/providers-config"; import { TAX_PROVIDER_KEY } from "../providers-configuration/public-providers-configuration-service"; import { TaxJarClient } from "./taxjar-client"; @@ -11,11 +11,12 @@ import { TaxJarConfig, TaxJarInstanceConfig, taxJarInstanceConfigSchema } from " const getSchema = taxJarInstanceConfigSchema; export class TaxJarConfigurationService { - private crudSettingsConfigurator: CrudSettingsConfigurator; + private crudSettingsManager: CrudSettingsManager; private logger: pino.Logger; constructor(client: Client, saleorApiUrl: string) { const settingsManager = createSettingsManager(client); - this.crudSettingsConfigurator = new CrudSettingsConfigurator( + + this.crudSettingsManager = new CrudSettingsManager( settingsManager, saleorApiUrl, TAX_PROVIDER_KEY @@ -28,8 +29,9 @@ export class TaxJarConfigurationService { async getAll(): Promise { this.logger.debug(".getAll called"); - const { data } = await this.crudSettingsConfigurator.readAll(); - this.logger.debug(`Fetched settings from crudSettingsConfigurator`); + const { data } = await this.crudSettingsManager.readAll(); + + this.logger.debug(`Fetched settings from CrudSettingsManager`); const validation = providersSchema.safeParse(data); if (!validation.success) { @@ -46,8 +48,9 @@ export class TaxJarConfigurationService { async get(id: string): Promise { this.logger.debug(`.get called with id: ${id}`); - const { data } = await this.crudSettingsConfigurator.read(id); - this.logger.debug(`Fetched setting from crudSettingsConfigurator`); + const { data } = await this.crudSettingsManager.read(id); + + this.logger.debug(`Fetched setting from CrudSettingsManager`); const validation = getSchema.safeParse(data); @@ -68,7 +71,7 @@ export class TaxJarConfigurationService { this.logger.error({ error: validation.error }, "Validation error while post"); throw new Error(validation.error); } - const result = await this.crudSettingsConfigurator.create({ + const result = await this.crudSettingsManager.create({ provider: "taxjar", config: config, }); @@ -82,7 +85,7 @@ export class TaxJarConfigurationService { // omit the key "id" from the result const { id: _, ...setting } = data; - return this.crudSettingsConfigurator.update(id, { + return this.crudSettingsManager.update(id, { ...setting, config: { ...setting.config, ...config }, }); @@ -94,7 +97,7 @@ export class TaxJarConfigurationService { const { id: _, ...setting } = data; this.logger.debug(`.put called with id: ${id} and value: ${JSON.stringify(config)}`); - return this.crudSettingsConfigurator.update(id, { + return this.crudSettingsManager.update(id, { ...setting, config: { ...config }, }); @@ -102,6 +105,6 @@ export class TaxJarConfigurationService { async delete(id: string): Promise { this.logger.debug(`.delete called with id: ${id}`); - return this.crudSettingsConfigurator.delete(id); + return this.crudSettingsManager.delete(id); } } diff --git a/apps/taxes/src/modules/taxjar/taxjar-provider.ts b/apps/taxes/src/modules/taxjar/taxjar-provider.ts deleted file mode 100644 index cbb7d7a..0000000 --- a/apps/taxes/src/modules/taxjar/taxjar-provider.ts +++ /dev/null @@ -1,42 +0,0 @@ -import pino from "pino"; -import { TaxBaseFragment } from "../../../generated/graphql"; -import { createLogger } from "../../lib/logger"; -import { ChannelConfig } from "../channels-configuration/channels-config"; -import { TaxProvider } from "../taxes/tax-provider"; -import { taxJarCalculate } from "./taxjar-calculate"; -import { TaxJarClient } from "./taxjar-client"; -import { TaxJarConfig } from "./taxjar-config"; - -export class TaxJarProvider implements TaxProvider { - client: TaxJarClient; - readonly name = "taxjar"; - private logger: pino.Logger; - - constructor(config: TaxJarConfig) { - const avataxClient = new TaxJarClient(config); - - this.client = avataxClient; - this.logger = createLogger({ - service: "TaxJarProvider", - }); - } - - async calculate(payload: TaxBaseFragment, channel: ChannelConfig) { - this.logger.debug({ payload, channel }, "TaxJar calculate called with:"); - const linesWithDiscount = taxJarCalculate.prepareLinesWithDiscountPayload( - payload.lines, - payload.discounts - ); - const linesWithChargeTaxes = linesWithDiscount.filter((line) => line.chargeTaxes === true); - const taxParams = taxJarCalculate.preparePayload(payload, channel, linesWithDiscount); - const fetchedTaxes = await this.client.fetchTaxesForOrder(taxParams); - this.logger.debug({ fetchedTaxes }, "TaxJar createOrderTransaction response"); - - return taxJarCalculate.prepareResponse( - payload, - fetchedTaxes, - linesWithChargeTaxes, - linesWithDiscount - ); - } -} diff --git a/apps/taxes/src/modules/taxjar/taxjar-webhook.service.ts b/apps/taxes/src/modules/taxjar/taxjar-webhook.service.ts new file mode 100644 index 0000000..021f25e --- /dev/null +++ b/apps/taxes/src/modules/taxjar/taxjar-webhook.service.ts @@ -0,0 +1,48 @@ +import pino from "pino"; +import { OrderCreatedSubscriptionFragment, TaxBaseFragment } from "../../../generated/graphql"; +import { createLogger } from "../../lib/logger"; +import { ChannelConfig } from "../channels-configuration/channels-config"; +import { ProviderWebhookService } from "../taxes/tax-provider-webhook"; +import { TaxJarClient } from "./taxjar-client"; +import { TaxJarConfig } from "./taxjar-config"; +import { taxJarCalculateTaxesMaps } from "./maps/taxjar-calculate-taxes-map"; +import { taxJarOrderCreatedMaps } from "./maps/taxjar-order-created-map"; + +export class TaxJarWebhookService implements ProviderWebhookService { + client: TaxJarClient; + private logger: pino.Logger; + + constructor(config: TaxJarConfig) { + const avataxClient = new TaxJarClient(config); + + this.client = avataxClient; + this.logger = createLogger({ + service: "TaxJarProvider", + }); + } + + async calculateTaxes(payload: TaxBaseFragment, channel: ChannelConfig) { + this.logger.debug({ payload, channel }, "calculateTaxes called with:"); + const args = taxJarCalculateTaxesMaps.mapPayload(payload, channel); + const fetchedTaxes = await this.client.fetchTaxForOrder(args); + + this.logger.debug({ fetchedTaxes }, "fetchTaxForOrder response"); + + return taxJarCalculateTaxesMaps.mapResponse(payload, fetchedTaxes); + } + + async createOrder(order: OrderCreatedSubscriptionFragment, channel: ChannelConfig) { + this.logger.debug({ order, channel }, "createOrder called with:"); + const args = taxJarOrderCreatedMaps.mapPayload({ order, channel }); + const result = await this.client.createOrder(args); + + this.logger.debug({ createOrder: result }, "createOrder response"); + + return taxJarOrderCreatedMaps.mapResponse(result); + } + + // * TaxJar doesn't require any action on order fulfillment + async fulfillOrder() { + return { ok: true }; + } +} diff --git a/apps/taxes/src/modules/taxjar/ui/taxjar-configuration-form.tsx b/apps/taxes/src/modules/taxjar/ui/taxjar-configuration-form.tsx index fdaa6cb..d696289 100644 --- a/apps/taxes/src/modules/taxjar/ui/taxjar-configuration-form.tsx +++ b/apps/taxes/src/modules/taxjar/ui/taxjar-configuration-form.tsx @@ -26,6 +26,7 @@ const useStyles = makeStyles((theme) => ({ })); const schema = taxJarConfigSchema; + type FormValues = z.infer; const defaultValues: FormValues = { @@ -110,6 +111,7 @@ export const TaxJarConfigurationForm = () => { React.useEffect(() => { if (instance) { const { config } = instance; + reset(config); } else { reset({ ...defaultValues }); diff --git a/apps/taxes/src/modules/ui/app-paper.tsx b/apps/taxes/src/modules/ui/app-paper.tsx index 79cae94..6b58ca9 100644 --- a/apps/taxes/src/modules/ui/app-paper.tsx +++ b/apps/taxes/src/modules/ui/app-paper.tsx @@ -10,6 +10,7 @@ const useStyles = makeStyles({ export const AppPaper = ({ children }: { children: React.ReactNode }) => { const styles = useStyles(); + return ( {children} diff --git a/apps/taxes/src/modules/ui/instructions.tsx b/apps/taxes/src/modules/ui/instructions.tsx index f7c3f5f..112f7ed 100644 --- a/apps/taxes/src/modules/ui/instructions.tsx +++ b/apps/taxes/src/modules/ui/instructions.tsx @@ -19,6 +19,7 @@ const useStyles = makeStyles((theme) => ({ export const Instructions = () => { const styles = useStyles(); + return (
diff --git a/apps/taxes/src/pages/_app.tsx b/apps/taxes/src/pages/_app.tsx index d54fa7b..00dd7bf 100644 --- a/apps/taxes/src/pages/_app.tsx +++ b/apps/taxes/src/pages/_app.tsx @@ -65,6 +65,7 @@ function NextApp({ Component, pageProps }: AppProps) { */ useEffect(() => { const jssStyles = document.querySelector("#jss-server-side"); + if (jssStyles) { jssStyles?.parentElement?.removeChild(jssStyles); } diff --git a/apps/taxes/src/pages/_error.js b/apps/taxes/src/pages/_error.js index 49f0ea6..4402947 100644 --- a/apps/taxes/src/pages/_error.js +++ b/apps/taxes/src/pages/_error.js @@ -16,20 +16,24 @@ * - https://reactjs.org/docs/error-boundaries.html */ -import * as Sentry from '@sentry/nextjs'; -import NextErrorComponent from 'next/error'; +import * as Sentry from "@sentry/nextjs"; +import NextErrorComponent from "next/error"; -const CustomErrorComponent = props => { - // If you're using a Nextjs version prior to 12.2.1, uncomment this to - // compensate for https://github.com/vercel/next.js/issues/8592 - // Sentry.captureUnderscoreErrorException(props); +const CustomErrorComponent = (props) => { + /** + * If you're using a Nextjs version prior to 12.2.1, uncomment this to + * compensate for https://github.com/vercel/next.js/issues/8592 + * Sentry.captureUnderscoreErrorException(props); + */ return ; }; -CustomErrorComponent.getInitialProps = async contextData => { - // In case this is running in a serverless function, await this in order to give Sentry - // time to send the error before the lambda exits +CustomErrorComponent.getInitialProps = async (contextData) => { + /* + * In case this is running in a serverless function, await this in order to give Sentry + * time to send the error before the lambda exits + */ await Sentry.captureUnderscoreErrorException(contextData); // This will contain the status code of the response diff --git a/apps/taxes/src/pages/api/manifest.ts b/apps/taxes/src/pages/api/manifest.ts index 1fd0a3b..3c39efc 100644 --- a/apps/taxes/src/pages/api/manifest.ts +++ b/apps/taxes/src/pages/api/manifest.ts @@ -4,6 +4,8 @@ import { AppManifest } from "@saleor/app-sdk/types"; import packageJson from "../../../package.json"; import { checkoutCalculateTaxesSyncWebhook } from "./webhooks/checkout-calculate-taxes"; import { orderCalculateTaxesSyncWebhook } from "./webhooks/order-calculate-taxes"; +import { orderCreatedAsyncWebhook } from "./webhooks/order-created"; +import { orderFulfilledAsyncWebhook } from "./webhooks/order-fulfilled"; export default createManifestHandler({ async manifestFactory(context) { @@ -17,6 +19,8 @@ export default createManifestHandler({ webhooks: [ orderCalculateTaxesSyncWebhook.getWebhookManifest(context.appBaseUrl), checkoutCalculateTaxesSyncWebhook.getWebhookManifest(context.appBaseUrl), + orderCreatedAsyncWebhook.getWebhookManifest(context.appBaseUrl), + orderFulfilledAsyncWebhook.getWebhookManifest(context.appBaseUrl), ], extensions: [], homepageUrl: "https://github.com/saleor/apps", diff --git a/apps/taxes/src/pages/api/webhooks/checkout-calculate-taxes.ts b/apps/taxes/src/pages/api/webhooks/checkout-calculate-taxes.ts index 09d1933..c474a7b 100644 --- a/apps/taxes/src/pages/api/webhooks/checkout-calculate-taxes.ts +++ b/apps/taxes/src/pages/api/webhooks/checkout-calculate-taxes.ts @@ -1,11 +1,12 @@ import { SaleorSyncWebhook } from "@saleor/app-sdk/handlers/next"; -import { UntypedCalculateTaxesDocument } from "../../../../generated/graphql"; +import { + CalculateTaxesEventFragment, + UntypedCalculateTaxesDocument, +} from "../../../../generated/graphql"; import { saleorApp } from "../../../../saleor-app"; import { createLogger } from "../../../lib/logger"; -import { calculateTaxesPayloadSchema, ExpectedWebhookPayload } from "../../../lib/saleor/schema"; -import { getAppConfig } from "../../../modules/app-configuration/get-app-config"; - -import { ActiveTaxProvider } from "../../../modules/taxes/active-tax-provider"; +import { WebhookResponse } from "../../../modules/app/webhook-response"; +import { getActiveTaxProvider } from "../../../modules/taxes/active-tax-provider"; export const config = { api: { @@ -13,7 +14,21 @@ export const config = { }, }; -export const checkoutCalculateTaxesSyncWebhook = new SaleorSyncWebhook({ +type CalculateTaxesPayload = Extract; + +function verifyCalculateTaxesPayload(payload: CalculateTaxesPayload) { + if (!payload.taxBase.lines) { + throw new Error("No lines found in taxBase"); + } + + if (!payload.taxBase.address) { + throw new Error("No address found in taxBase"); + } + + return payload; +} + +export const checkoutCalculateTaxesSyncWebhook = new SaleorSyncWebhook({ name: "CheckoutCalculateTaxes", apl: saleorApp.apl, event: "CHECKOUT_CALCULATE_TAXES", @@ -24,50 +39,35 @@ export const checkoutCalculateTaxesSyncWebhook = new SaleorSyncWebhook { const logger = createLogger({ event: ctx.event }); const { payload } = ctx; + const webhookResponse = new WebhookResponse(res); + logger.info({ payload }, "Handler called with payload"); - const validation = calculateTaxesPayloadSchema.safeParse(payload); - - if (!validation.success) { - logger.error({ error: validation.error.format() }, "Payload is invalid"); + try { + verifyCalculateTaxesPayload(payload); + logger.info("Payload validated succesfully"); + } catch (error) { logger.info("Returning no data"); - return res.send({}); + return webhookResponse.failureNoRetry("Payload is invalid"); } - const { data } = validation; - logger.info("Payload validated succesfully"); - - const { providers, channels } = getAppConfig(data); - logger.debug("Successfully parsed providers & channels from payload"); - try { - const channelSlug = data.taxBase.channel.slug; - const channelConfig = channels[channelSlug]; + const appMetadata = payload.recipient?.privateMetadata ?? []; + const channelSlug = payload.taxBase.channel.slug; + const activeTaxProvider = getActiveTaxProvider(channelSlug, appMetadata); - if (!channelConfig) { - logger.error(`Channel config not found for channel ${channelSlug}`); + if (!activeTaxProvider.ok) { logger.info("Returning no data"); - return res.send({}); + return webhookResponse.failureNoRetry(activeTaxProvider.error); } - const providerInstance = providers.find( - (instance) => instance.id === channelConfig.providerInstanceId - ); - - if (!providerInstance) { - logger.error(`Channel (${channelSlug}) providerInstanceId does not match any providers`); - logger.info("Returning no data"); - return res.send({}); - } - - const taxProvider = new ActiveTaxProvider(providerInstance); - const calculatedTaxes = await taxProvider.calculate(data.taxBase, channelConfig); + logger.info({ activeTaxProvider }, "Fetched activeTaxProvider"); + const taxProvider = activeTaxProvider.data; + const calculatedTaxes = await taxProvider.calculateTaxes(payload.taxBase); logger.info({ calculatedTaxes }, "Taxes calculated"); - return res.send(ctx.buildResponse(calculatedTaxes)); + return webhookResponse.success(ctx.buildResponse(calculatedTaxes)); } catch (error) { - logger.error({ error }, "Error while calculating taxes"); - logger.info("Returning no data"); - return res.send({}); + return webhookResponse.failureRetry("Error while calculating taxes"); } }); diff --git a/apps/taxes/src/pages/api/webhooks/order-calculate-taxes.ts b/apps/taxes/src/pages/api/webhooks/order-calculate-taxes.ts index 0f3d66a..ae1be5b 100644 --- a/apps/taxes/src/pages/api/webhooks/order-calculate-taxes.ts +++ b/apps/taxes/src/pages/api/webhooks/order-calculate-taxes.ts @@ -1,10 +1,12 @@ import { SaleorSyncWebhook } from "@saleor/app-sdk/handlers/next"; -import { UntypedCalculateTaxesDocument } from "../../../../generated/graphql"; +import { + CalculateTaxesEventFragment, + UntypedCalculateTaxesDocument, +} from "../../../../generated/graphql"; import { saleorApp } from "../../../../saleor-app"; import { createLogger } from "../../../lib/logger"; -import { calculateTaxesPayloadSchema, ExpectedWebhookPayload } from "../../../lib/saleor/schema"; -import { getAppConfig } from "../../../modules/app-configuration/get-app-config"; -import { ActiveTaxProvider } from "../../../modules/taxes/active-tax-provider"; +import { getActiveTaxProvider } from "../../../modules/taxes/active-tax-provider"; +import { WebhookResponse } from "../../../modules/app/webhook-response"; export const config = { api: { @@ -12,7 +14,21 @@ export const config = { }, }; -export const orderCalculateTaxesSyncWebhook = new SaleorSyncWebhook({ +type CalculateTaxesPayload = Extract; + +function verifyCalculateTaxesPayload(payload: CalculateTaxesPayload) { + if (!payload.taxBase.lines) { + throw new Error("No lines found in taxBase"); + } + + if (!payload.taxBase.address) { + throw new Error("No address found in taxBase"); + } + + return payload; +} + +export const orderCalculateTaxesSyncWebhook = new SaleorSyncWebhook({ name: "OrderCalculateTaxes", apl: saleorApp.apl, event: "ORDER_CALCULATE_TAXES", @@ -23,50 +39,35 @@ export const orderCalculateTaxesSyncWebhook = new SaleorSyncWebhook { const logger = createLogger({ event: ctx.event }); const { payload } = ctx; + const webhookResponse = new WebhookResponse(res); + logger.info({ payload }, "Handler called with payload"); - const validation = calculateTaxesPayloadSchema.safeParse(payload); - - if (!validation.success) { - logger.error({ error: validation.error.format() }, "Payload is invalid"); + try { + verifyCalculateTaxesPayload(payload); + logger.info("Payload validated succesfully"); + } catch (error) { logger.info("Returning no data"); - return res.status(200).json({}); + return webhookResponse.failureNoRetry("Payload is invalid"); } - const { data } = validation; - logger.info("Payload validated succesfully"); - - const { providers, channels } = getAppConfig(data); - logger.debug("Parsed providers & channels from payload"); - try { - const channelSlug = data.taxBase.channel.slug; - const channelConfig = channels[channelSlug]; + const appMetadata = payload.recipient?.privateMetadata ?? []; + const channelSlug = payload.taxBase.channel.slug; + const activeTaxProvider = getActiveTaxProvider(channelSlug, appMetadata); - if (!channelConfig) { - logger.error(`Channel config not found for channel ${channelSlug}`); + if (!activeTaxProvider.ok) { logger.info("Returning no data"); - return res.send({}); + return webhookResponse.failureNoRetry(activeTaxProvider.error); } - const providerInstance = providers.find( - (instance) => instance.id === channelConfig.providerInstanceId - ); - - if (!providerInstance) { - logger.error(`Channel (${channelSlug}) providerInstanceId does not match any providers`); - logger.info("Returning no data"); - return res.send({}); - } - - const taxProvider = new ActiveTaxProvider(providerInstance); - const calculatedTaxes = await taxProvider.calculate(data.taxBase, channelConfig); + logger.info({ activeTaxProvider }, "Fetched activeTaxProvider"); + const taxProvider = activeTaxProvider.data; + const calculatedTaxes = await taxProvider.calculateTaxes(payload.taxBase); logger.info({ calculatedTaxes }, "Taxes calculated"); - return res.send(ctx.buildResponse(calculatedTaxes)); + return webhookResponse.success(calculatedTaxes); } catch (error) { - logger.error({ error }, "Error while calculating taxes"); - logger.info("Returning no data"); - return res.send({}); + return webhookResponse.failureRetry("Error while calculating taxes"); } }); diff --git a/apps/taxes/src/pages/api/webhooks/order-created.ts b/apps/taxes/src/pages/api/webhooks/order-created.ts new file mode 100644 index 0000000..89b660f --- /dev/null +++ b/apps/taxes/src/pages/api/webhooks/order-created.ts @@ -0,0 +1,110 @@ +import { SaleorAsyncWebhook } from "@saleor/app-sdk/handlers/next"; +import { + OrderCreatedEventSubscriptionFragment, + OrderStatus, + UntypedOrderCreatedSubscriptionDocument, + UpdateMetadataDocument, + UpdateMetadataMutation, + UpdateMetadataMutationVariables, +} from "../../../../generated/graphql"; +import { saleorApp } from "../../../../saleor-app"; +import { createLogger } from "../../../lib/logger"; +import { getActiveTaxProvider } from "../../../modules/taxes/active-tax-provider"; +import { createClient } from "../../../lib/graphql"; +import { Client } from "urql"; +import { WebhookResponse } from "../../../modules/app/webhook-response"; + +export const config = { + api: { + bodyParser: false, + }, +}; + +type OrderCreatedPayload = Extract< + OrderCreatedEventSubscriptionFragment, + { __typename: "OrderCreated" } +>; + +export const orderCreatedAsyncWebhook = new SaleorAsyncWebhook({ + name: "OrderCreated", + apl: saleorApp.apl, + event: "ORDER_CREATED", + query: UntypedOrderCreatedSubscriptionDocument, + webhookPath: "/api/webhooks/order-created", +}); + +// * This is the key that we use to store the provider order id in the Saleor order metadata. +export const PROVIDER_ORDER_ID_KEY = "externalId"; + +/** + * We need to store the provider order id in the Saleor order metadata so that we can + * update the provider order when the Saleor order is fulfilled. + */ +async function updateOrderMetadataWithExternalId( + client: Client, + orderId: string, + externalId: string +) { + const variables: UpdateMetadataMutationVariables = { + id: orderId, + input: [ + { + key: PROVIDER_ORDER_ID_KEY, + value: externalId, + }, + ], + }; + const { error } = await client + .mutation(UpdateMetadataDocument, variables) + .toPromise(); + + if (error) { + throw error; + } + + return { ok: true }; +} + +export default orderCreatedAsyncWebhook.createHandler(async (req, res, ctx) => { + const logger = createLogger({ event: ctx.event }); + const { payload, authData } = ctx; + const { saleorApiUrl, token } = authData; + const webhookResponse = new WebhookResponse(res); + + logger.info({ payload }, "Handler called with payload"); + + try { + const appMetadata = payload.recipient?.privateMetadata ?? []; + const channelSlug = payload.order?.channel.slug; + const activeTaxProvider = getActiveTaxProvider(channelSlug, appMetadata); + + if (!activeTaxProvider.ok) { + logger.info("Returning no data"); + return webhookResponse.failureNoRetry(activeTaxProvider.error); + } + + logger.info({ activeTaxProvider }, "Fetched activeTaxProvider"); + const taxProvider = activeTaxProvider.data; + + // todo: figure out what fields are needed and add validation + if (!payload.order) { + return webhookResponse.failureNoRetry("Insufficient order data"); + } + + if (payload.order.status === OrderStatus.Fulfilled) { + return webhookResponse.failureNoRetry("Skipping fulfilled order to prevent duplication"); + } + + const createdOrder = await taxProvider.createOrder(payload.order); + + logger.info({ createdOrder }, "Order created"); + const client = createClient(saleorApiUrl, async () => Promise.resolve({ token })); + + await updateOrderMetadataWithExternalId(client, payload.order.id, createdOrder.id); + logger.info("Updated order metadata with externalId"); + + return webhookResponse.success(); + } catch (error) { + return webhookResponse.failureRetry("Error while creating order in tax provider"); + } +}); diff --git a/apps/taxes/src/pages/api/webhooks/order-fulfilled.ts b/apps/taxes/src/pages/api/webhooks/order-fulfilled.ts new file mode 100644 index 0000000..6320e4b --- /dev/null +++ b/apps/taxes/src/pages/api/webhooks/order-fulfilled.ts @@ -0,0 +1,61 @@ +import { SaleorAsyncWebhook } from "@saleor/app-sdk/handlers/next"; +import { + OrderFulfilledEventSubscriptionFragment, + UntypedOrderFulfilledSubscriptionDocument, +} from "../../../../generated/graphql"; +import { saleorApp } from "../../../../saleor-app"; +import { createLogger } from "../../../lib/logger"; +import { getActiveTaxProvider } from "../../../modules/taxes/active-tax-provider"; +import { WebhookResponse } from "../../../modules/app/webhook-response"; +export const config = { + api: { + bodyParser: false, + }, +}; + +type OrderFulfilledPayload = Extract< + OrderFulfilledEventSubscriptionFragment, + { __typename: "OrderFulfilled" } +>; + +export const orderFulfilledAsyncWebhook = new SaleorAsyncWebhook({ + name: "OrderFulfilled", + apl: saleorApp.apl, + event: "ORDER_FULFILLED", + query: UntypedOrderFulfilledSubscriptionDocument, + webhookPath: "/api/webhooks/order-fulfilled", +}); + +export default orderFulfilledAsyncWebhook.createHandler(async (req, res, ctx) => { + const logger = createLogger({ event: ctx.event }); + const { payload } = ctx; + const webhookResponse = new WebhookResponse(res); + + logger.info({ payload }, "Handler called with payload"); + + try { + const appMetadata = payload.recipient?.privateMetadata ?? []; + const channelSlug = payload.order?.channel.slug; + const activeTaxProvider = getActiveTaxProvider(channelSlug, appMetadata); + + if (!activeTaxProvider.ok) { + logger.info("Returning no data"); + return webhookResponse.failureNoRetry(activeTaxProvider.error); + } + + logger.info({ activeTaxProvider }, "Fetched activeTaxProvider"); + const taxProvider = activeTaxProvider.data; + + // todo: figure out what fields are needed and add validation + if (!payload.order) { + return webhookResponse.failureNoRetry("Insufficient order data"); + } + const fulfilledOrder = await taxProvider.fulfillOrder(payload.order); + + logger.info({ fulfilledOrder }, "Order fulfilled"); + + return webhookResponse.success(); + } catch (error) { + return webhookResponse.failureRetry("Error while fulfilling tax provider order"); + } +}); diff --git a/packages/eslint-config-saleor/index.js b/packages/eslint-config-saleor/index.js index 9d625ee..90eddc4 100644 --- a/packages/eslint-config-saleor/index.js +++ b/packages/eslint-config-saleor/index.js @@ -4,6 +4,8 @@ module.exports = { "@next/next/no-html-link-for-pages": "off", "react/jsx-key": "off", "import/no-default-export": "error", + "newline-after-var": "warn", + "multiline-comment-style": ["warn", "starred-block"], }, parserOptions: { babelOptions: { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a9629ea..051b6ad 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -101,8 +101,8 @@ importers: specifier: ^9.0.0 version: 9.0.0 vite: - specifier: ^4.1.4 - version: 4.1.4(@types/node@18.13.0) + specifier: ^4.2.1 + version: 4.2.1(@types/node@18.13.0) zod: specifier: ^3.19.1 version: 3.20.2 @@ -148,7 +148,7 @@ importers: version: 8.3.4 '@vitejs/plugin-react': specifier: ^3.1.0 - version: 3.1.0(vite@4.1.4) + version: 3.1.0(vite@4.2.1) eslint: specifier: 8.25.0 version: 8.25.0 @@ -168,8 +168,8 @@ importers: specifier: '4.9' version: 4.9.5 vitest: - specifier: ^0.29.2 - version: 0.29.3(jsdom@20.0.3) + specifier: ^0.30.1 + version: 0.30.1(jsdom@20.0.3) apps/crm: dependencies: @@ -205,7 +205,7 @@ importers: version: 1.0.0(graphql@16.6.0) '@vitejs/plugin-react': specifier: ^3.0.1 - version: 3.1.0(vite@4.1.1) + version: 3.1.0(vite@4.2.1) clsx: specifier: ^1.2.1 version: 1.2.1 @@ -249,11 +249,11 @@ importers: specifier: ^2.9.1 version: 2.9.1(react-dom@18.2.0)(react@18.2.0) vite: - specifier: ^4.0.4 - version: 4.1.1(@types/node@18.13.0) + specifier: ^4.2.1 + version: 4.2.1(@types/node@18.13.0) vitest: - specifier: ^0.27.1 - version: 0.27.3(jsdom@20.0.3) + specifier: ^0.30.1 + version: 0.30.1(jsdom@20.0.3) zod: specifier: ^3.20.2 version: 3.20.2 @@ -347,7 +347,7 @@ importers: version: 1.0.0(graphql@16.6.0) '@vitejs/plugin-react': specifier: ^3.1.0 - version: 3.1.0(vite@4.1.1) + version: 3.1.0(vite@4.2.1) clsx: specifier: ^1.2.1 version: 1.2.1 @@ -385,11 +385,11 @@ importers: specifier: ^2.9.1 version: 2.9.1(react-dom@18.2.0)(react@18.2.0) vite: - specifier: ^4.1.1 - version: 4.1.1(@types/node@18.13.0) + specifier: ^4.2.1 + version: 4.2.1(@types/node@18.13.0) vitest: - specifier: ^0.28.4 - version: 0.28.4(jsdom@20.0.3) + specifier: ^0.30.1 + version: 0.30.1(jsdom@20.0.3) zod: specifier: ^3.20.2 version: 3.20.2 @@ -501,7 +501,7 @@ importers: version: 1.0.0(graphql@16.6.0) '@vitejs/plugin-react': specifier: ^3.0.1 - version: 3.1.0(vite@4.1.1) + version: 3.1.0(vite@4.2.1) clsx: specifier: ^1.2.1 version: 1.2.1 @@ -560,11 +560,11 @@ importers: specifier: ^2.9.1 version: 2.9.1(react-dom@18.2.0)(react@18.2.0) vite: - specifier: ^4.0.4 - version: 4.1.1(@types/node@18.13.0) + specifier: ^4.2.1 + version: 4.2.1(@types/node@18.13.0) vitest: - specifier: ^0.27.1 - version: 0.27.3(jsdom@20.0.3) + specifier: ^0.30.1 + version: 0.30.1(jsdom@20.0.3) zod: specifier: ^3.20.2 version: 3.20.2 @@ -776,7 +776,7 @@ importers: version: 7.3.13 '@vitejs/plugin-react': specifier: ^3.0.0 - version: 3.1.0(vite@4.1.1) + version: 3.1.0(vite@4.2.1) '@vitest/coverage-c8': specifier: ^0.28.4 version: 0.28.4(jsdom@20.0.3) @@ -796,11 +796,11 @@ importers: specifier: 4.9.5 version: 4.9.5 vite: - specifier: ^4.1.1 - version: 4.1.1(@types/node@18.13.0) + specifier: ^4.2.1 + version: 4.2.1(@types/node@18.13.0) vitest: - specifier: ^0.28.4 - version: 0.28.4(jsdom@20.0.3) + specifier: ^0.30.1 + version: 0.30.1(jsdom@20.0.3) apps/klaviyo: dependencies: @@ -945,7 +945,7 @@ importers: version: 1.0.0(graphql@16.6.0) '@vitejs/plugin-react': specifier: ^3.0.1 - version: 3.1.0(vite@4.1.1) + version: 3.1.0(vite@4.2.1) clsx: specifier: ^1.2.1 version: 1.2.1 @@ -974,11 +974,11 @@ importers: specifier: ^3.0.3 version: 3.0.3(graphql@16.6.0)(react@18.2.0) vite: - specifier: ^4.0.4 - version: 4.1.1(@types/node@18.13.0) + specifier: ^4.2.1 + version: 4.2.1(@types/node@18.13.0) vitest: - specifier: ^0.27.1 - version: 0.27.3(jsdom@20.0.3) + specifier: ^0.30.1 + version: 0.30.1(jsdom@20.0.3) devDependencies: '@graphql-codegen/cli': specifier: 3.2.2 @@ -1084,7 +1084,7 @@ importers: version: 1.0.0(graphql@16.6.0) '@vitejs/plugin-react': specifier: ^3.0.1 - version: 3.1.0(vite@4.1.1) + version: 3.1.0(vite@4.2.1) clsx: specifier: ^1.2.1 version: 1.2.1 @@ -1131,11 +1131,11 @@ importers: specifier: ^2.9.1 version: 2.9.1(react-dom@18.2.0)(react@18.2.0) vite: - specifier: ^4.0.4 - version: 4.1.1(@types/node@18.13.0) + specifier: ^4.2.1 + version: 4.2.1(@types/node@18.13.0) vitest: - specifier: ^0.27.1 - version: 0.27.3(jsdom@20.0.3) + specifier: ^0.30.1 + version: 0.30.1(jsdom@20.0.3) zod: specifier: ^3.20.2 version: 3.20.2 @@ -1497,8 +1497,8 @@ importers: specifier: ^1.0.1 version: 1.0.1(graphql@16.6.0) avatax: - specifier: ^23.2.0 - version: 23.2.0 + specifier: ^23.3.2 + version: 23.3.2 clsx: specifier: ^1.2.1 version: 1.2.1 @@ -1539,11 +1539,11 @@ importers: specifier: ^2.9.1 version: 2.9.1(react-dom@18.2.0)(react@18.2.0) vite: - specifier: ^4.0.1 - version: 4.1.1(@types/node@18.13.0) + specifier: ^4.2.1 + version: 4.2.1(@types/node@18.13.0) vitest: - specifier: ^0.25.8 - version: 0.25.8(jsdom@20.0.3) + specifier: ^0.30.1 + version: 0.30.1(jsdom@20.0.3) zod: specifier: ^3.20.2 version: 3.20.2 @@ -1591,8 +1591,8 @@ importers: specifier: ^18.0.6 version: 18.0.10 '@vitejs/plugin-react': - specifier: ^3.0.0 - version: 3.1.0(vite@4.1.1) + specifier: ^3.1.0 + version: 3.1.0(vite@4.2.1) eslint: specifier: 8.25.0 version: 8.25.0 @@ -1804,13 +1804,6 @@ packages: resolution: {integrity: sha512-a7mYHf/GVQfhAx/HRiMveKkFvHspQv/REdG+C/FIOosiSmNZxX7QebDwJkrGSmDWdXO12D0Qv1xn3AytFcEDlQ==} dev: false - /@ampproject/remapping@2.2.0: - resolution: {integrity: sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==} - engines: {node: '>=6.0.0'} - dependencies: - '@jridgewell/gen-mapping': 0.1.1 - '@jridgewell/trace-mapping': 0.3.17 - /@ampproject/remapping@2.2.1: resolution: {integrity: sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==} engines: {node: '>=6.0.0'} @@ -1824,13 +1817,13 @@ packages: peerDependencies: graphql: '*' dependencies: - '@babel/core': 7.20.12 - '@babel/generator': 7.20.14 - '@babel/parser': 7.20.15 + '@babel/core': 7.21.4 + '@babel/generator': 7.21.4 + '@babel/parser': 7.21.4 '@babel/runtime': 7.20.13 - '@babel/traverse': 7.20.13 - '@babel/types': 7.20.7 - babel-preset-fbjs: 3.4.0(@babel/core@7.20.12) + '@babel/traverse': 7.21.4 + '@babel/types': 7.21.4 + babel-preset-fbjs: 3.4.0(@babel/core@7.21.4) chalk: 4.1.2 fb-watchman: 2.0.2 fbjs: 3.0.4 @@ -1861,6 +1854,7 @@ packages: engines: {node: '>=6.9.0'} dependencies: '@babel/highlight': 7.18.6 + dev: true /@babel/code-frame@7.21.4: resolution: {integrity: sha512-LYvhNKfwWSPpocw8GI7gpK2nq3HSDuEPC/uSYaALSJu9xjsalaaYFOq0Pwt5KmVqwEbZlDu81aLXwBOmD/Fv9g==} @@ -1868,36 +1862,10 @@ packages: dependencies: '@babel/highlight': 7.18.6 - /@babel/compat-data@7.20.14: - resolution: {integrity: sha512-0YpKHD6ImkWMEINCyDAD0HLLUH/lPCefG8ld9it8DJB2wnApraKuhgYTvTY1z7UFIfBTGy5LwncZ+5HWWGbhFw==} - engines: {node: '>=6.9.0'} - /@babel/compat-data@7.21.4: resolution: {integrity: sha512-/DYyDpeCfaVinT40FPGdkkb+lYSKvsVuMjDAG7jPOWWiM1ibOaB9CXJAlc4d1QpP/U2q2P9jbrSlClKSErd55g==} engines: {node: '>=6.9.0'} - /@babel/core@7.20.12: - resolution: {integrity: sha512-XsMfHovsUYHFMdrIHkZphTN/2Hzzi78R08NuHfDBehym2VsPDL6Zn/JAD/JQdnRvbSsbQc4mVaU1m6JgtTEElg==} - engines: {node: '>=6.9.0'} - dependencies: - '@ampproject/remapping': 2.2.0 - '@babel/code-frame': 7.21.4 - '@babel/generator': 7.20.14 - '@babel/helper-compilation-targets': 7.20.7(@babel/core@7.20.12) - '@babel/helper-module-transforms': 7.20.11 - '@babel/helpers': 7.20.13 - '@babel/parser': 7.20.15 - '@babel/template': 7.20.7 - '@babel/traverse': 7.20.13 - '@babel/types': 7.20.7 - convert-source-map: 1.9.0 - debug: 4.3.4 - gensync: 1.0.0-beta.2 - json5: 2.2.3 - semver: 6.3.0 - transitivePeerDependencies: - - supports-color - /@babel/core@7.21.4: resolution: {integrity: sha512-qt/YV149Jman/6AfmlxJ04LMIu8bMoyl3RB91yTFrxQmgbrSvQMy7cI8Q62FHx1t8wJ8B5fu0UDoLwHAhUo1QA==} engines: {node: '>=6.9.0'} @@ -1924,9 +1892,10 @@ packages: resolution: {integrity: sha512-AEmuXHdcD3A52HHXxaTmYlb8q/xMEhoRP67B3T4Oq7lbmSoqroMZzjnGj3+i1io3pdnF8iBYVu4Ilj+c4hBxYg==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.20.7 - '@jridgewell/gen-mapping': 0.3.2 + '@babel/types': 7.21.4 + '@jridgewell/gen-mapping': 0.3.3 jsesc: 2.5.2 + dev: true /@babel/generator@7.21.4: resolution: {integrity: sha512-NieM3pVIYW2SwGzKoqfPrQsf4xGs9M9AIG3ThppsSRmO+m7eQhmI6amajKMUeIO37wFfsvnvcxQFx6x6iqxDnA==} @@ -1941,22 +1910,9 @@ packages: resolution: {integrity: sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.20.7 + '@babel/types': 7.21.4 dev: true - /@babel/helper-compilation-targets@7.20.7(@babel/core@7.20.12): - resolution: {integrity: sha512-4tGORmfQcrc+bvrjb5y3dG9Mx1IOZjsHqQVUz7XCNHO+iTmqxWnVg3KRygjGmpRLJGdQSKuvFinbIb0CnZwHAQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - dependencies: - '@babel/compat-data': 7.20.14 - '@babel/core': 7.20.12 - '@babel/helper-validator-option': 7.18.6 - browserslist: 4.21.5 - lru-cache: 5.1.1 - semver: 6.3.0 - /@babel/helper-compilation-targets@7.21.4(@babel/core@7.21.4): resolution: {integrity: sha512-Fa0tTuOXZ1iL8IeDFUWCzjZcn+sJGd9RZdH9esYVjEejGmzf+FFYQpMi/kZUk2kPy/q1H3/GPw7np8qar/stfg==} engines: {node: '>=6.9.0'} @@ -1970,16 +1926,16 @@ packages: lru-cache: 5.1.1 semver: 6.3.0 - /@babel/helper-create-class-features-plugin@7.20.12(@babel/core@7.20.12): + /@babel/helper-create-class-features-plugin@7.20.12(@babel/core@7.21.4): resolution: {integrity: sha512-9OunRkbT0JQcednL0UFvbfXpAsUXiGjUk0a7sN8fUXX7Mue79cUSMjHGDRRi/Vz9vYlpIhLV5fMD5dKoMhhsNQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 dependencies: - '@babel/core': 7.20.12 + '@babel/core': 7.21.4 '@babel/helper-annotate-as-pure': 7.18.6 '@babel/helper-environment-visitor': 7.18.9 - '@babel/helper-function-name': 7.19.0 + '@babel/helper-function-name': 7.21.0 '@babel/helper-member-expression-to-functions': 7.20.7 '@babel/helper-optimise-call-expression': 7.18.6 '@babel/helper-replace-supers': 7.20.7 @@ -1993,13 +1949,6 @@ packages: resolution: {integrity: sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==} engines: {node: '>=6.9.0'} - /@babel/helper-function-name@7.19.0: - resolution: {integrity: sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/template': 7.20.7 - '@babel/types': 7.20.7 - /@babel/helper-function-name@7.21.0: resolution: {integrity: sha512-HfK1aMRanKHpxemaY2gqBmL04iAPOPRj7DxtNbiDOrJK+gdwkiNRVpCpUJYbUT+aZyemKN8brqTOxzCaG6ExRg==} engines: {node: '>=6.9.0'} @@ -2011,42 +1960,21 @@ packages: resolution: {integrity: sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.20.7 + '@babel/types': 7.21.4 /@babel/helper-member-expression-to-functions@7.20.7: resolution: {integrity: sha512-9J0CxJLq315fEdi4s7xK5TQaNYjZw+nDVpVqr1axNGKzdrdwYBD5b4uKv3n75aABG0rCCTK8Im8Ww7eYfMrZgw==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.20.7 + '@babel/types': 7.21.4 dev: true - /@babel/helper-module-imports@7.18.6: - resolution: {integrity: sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': 7.20.7 - /@babel/helper-module-imports@7.21.4: resolution: {integrity: sha512-orajc5T2PsRYUN3ZryCEFeMDYwyw09c/pZeaQEZPH0MpKzSvn3e0uXsDBu3k03VI+9DBiRo+l22BfKTpKwa/Wg==} engines: {node: '>=6.9.0'} dependencies: '@babel/types': 7.21.4 - /@babel/helper-module-transforms@7.20.11: - resolution: {integrity: sha512-uRy78kN4psmji1s2QtbtcCSaj/LILFDp0f/ymhpQH5QY3nljUZCaNWz9X1dEj/8MBdBEFECs7yRhKn8i7NjZgg==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/helper-environment-visitor': 7.18.9 - '@babel/helper-module-imports': 7.18.6 - '@babel/helper-simple-access': 7.20.2 - '@babel/helper-split-export-declaration': 7.18.6 - '@babel/helper-validator-identifier': 7.19.1 - '@babel/template': 7.20.7 - '@babel/traverse': 7.20.13 - '@babel/types': 7.20.7 - transitivePeerDependencies: - - supports-color - /@babel/helper-module-transforms@7.21.2: resolution: {integrity: sha512-79yj2AR4U/Oqq/WOV7Lx6hUjau1Zfo4cI+JLAVYeMV5XIlbOhmjEk5ulbTc9fMpmlojzZHkUUxAiK+UKn+hNQQ==} engines: {node: '>=6.9.0'} @@ -2066,7 +1994,7 @@ packages: resolution: {integrity: sha512-HP59oD9/fEHQkdcbgFCnbmgH5vIQTJbxh2yf+CdM89/glUNnuzr87Q8GIjGEnOktTROemO0Pe0iPAYbqZuOUiA==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.20.7 + '@babel/types': 7.21.4 dev: true /@babel/helper-plugin-utils@7.20.2: @@ -2081,8 +2009,8 @@ packages: '@babel/helper-member-expression-to-functions': 7.20.7 '@babel/helper-optimise-call-expression': 7.18.6 '@babel/template': 7.20.7 - '@babel/traverse': 7.20.13 - '@babel/types': 7.20.7 + '@babel/traverse': 7.21.4 + '@babel/types': 7.21.4 transitivePeerDependencies: - supports-color dev: true @@ -2091,20 +2019,20 @@ packages: resolution: {integrity: sha512-+0woI/WPq59IrqDYbVGfshjT5Dmk/nnbdpcF8SnMhhXObpTq2KNBdLFRFrkVdbDOyUmHBCxzm5FHV1rACIkIbA==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.20.7 + '@babel/types': 7.21.4 /@babel/helper-skip-transparent-expression-wrappers@7.20.0: resolution: {integrity: sha512-5y1JYeNKfvnT8sZcK9DVRtpTbGiomYIHviSP3OQWmDPU3DeH4a1ZlT/N2lyQ5P8egjcRaT/Y9aNqUxK0WsnIIg==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.20.7 + '@babel/types': 7.21.4 dev: true /@babel/helper-split-export-declaration@7.18.6: resolution: {integrity: sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.20.7 + '@babel/types': 7.21.4 /@babel/helper-string-parser@7.19.4: resolution: {integrity: sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==} @@ -2114,24 +2042,10 @@ packages: resolution: {integrity: sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==} engines: {node: '>=6.9.0'} - /@babel/helper-validator-option@7.18.6: - resolution: {integrity: sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw==} - engines: {node: '>=6.9.0'} - /@babel/helper-validator-option@7.21.0: resolution: {integrity: sha512-rmL/B8/f0mKS2baE9ZpyTcTavvEuWhTTW8amjzXNvYG4AwBsqTLikfXsEofsJEfKHf+HQVQbFOHy6o+4cnC/fQ==} engines: {node: '>=6.9.0'} - /@babel/helpers@7.20.13: - resolution: {integrity: sha512-nzJ0DWCL3gB5RCXbUO3KIMMsBY2Eqbx8mBpKGE/02PgyRQFcPQLbkQ1vyy596mZLaP+dAfD+R4ckASzNVmW3jg==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/template': 7.20.7 - '@babel/traverse': 7.20.13 - '@babel/types': 7.20.7 - transitivePeerDependencies: - - supports-color - /@babel/helpers@7.21.0: resolution: {integrity: sha512-XXve0CBtOW0pd7MRzzmoyuSj0e3SEzj8pgyFxnTT1NJZL38BD1MK7yYrm8yefRPIDvNNe14xR4FdbHwpInD4rA==} engines: {node: '>=6.9.0'} @@ -2150,13 +2064,6 @@ packages: chalk: 2.4.2 js-tokens: 4.0.0 - /@babel/parser@7.20.15: - resolution: {integrity: sha512-DI4a1oZuf8wC+oAJA9RW6ga3Zbe8RZFt7kD9i4qAspz3I/yHet1VvC3DiSy/fsUvv5pvJuNPh0LPOdCcqinDPg==} - engines: {node: '>=6.0.0'} - hasBin: true - dependencies: - '@babel/types': 7.20.7 - /@babel/parser@7.21.4: resolution: {integrity: sha512-alVJj7k7zIxqBZ7BTRhz0IqJFxW1VJbm6N8JbcYhQ186df9ZBPbZBmWSqAMXwHGsCJdYks7z/voa3ibiS5bCIw==} engines: {node: '>=6.0.0'} @@ -2164,49 +2071,49 @@ packages: dependencies: '@babel/types': 7.21.4 - /@babel/plugin-proposal-class-properties@7.18.6(@babel/core@7.20.12): + /@babel/plugin-proposal-class-properties@7.18.6(@babel/core@7.21.4): resolution: {integrity: sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.20.12 - '@babel/helper-create-class-features-plugin': 7.20.12(@babel/core@7.20.12) + '@babel/core': 7.21.4 + '@babel/helper-create-class-features-plugin': 7.20.12(@babel/core@7.21.4) '@babel/helper-plugin-utils': 7.20.2 transitivePeerDependencies: - supports-color dev: true - /@babel/plugin-proposal-object-rest-spread@7.20.7(@babel/core@7.20.12): + /@babel/plugin-proposal-object-rest-spread@7.20.7(@babel/core@7.21.4): resolution: {integrity: sha512-d2S98yCiLxDVmBmE8UjGcfPvNEUbA1U5q5WxaWFUGRzJSVAZqm5W6MbPct0jxnegUZ0niLeNX+IOzEs7wYg9Dg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/compat-data': 7.20.14 - '@babel/core': 7.20.12 - '@babel/helper-compilation-targets': 7.20.7(@babel/core@7.20.12) + '@babel/compat-data': 7.21.4 + '@babel/core': 7.21.4 + '@babel/helper-compilation-targets': 7.21.4(@babel/core@7.21.4) '@babel/helper-plugin-utils': 7.20.2 - '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.20.12) - '@babel/plugin-transform-parameters': 7.20.7(@babel/core@7.20.12) + '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.21.4) + '@babel/plugin-transform-parameters': 7.20.7(@babel/core@7.21.4) dev: true - /@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.20.12): + /@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.21.4): resolution: {integrity: sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.20.12 + '@babel/core': 7.21.4 '@babel/helper-plugin-utils': 7.20.2 dev: true - /@babel/plugin-syntax-flow@7.18.6(@babel/core@7.20.12): + /@babel/plugin-syntax-flow@7.18.6(@babel/core@7.21.4): resolution: {integrity: sha512-LUbR+KNTBWCUAqRG9ex5Gnzu2IOkt8jRJbHHXFT9q+L9zm7M/QQbEqXyw1n1pohYvOyWC8CjeyjrSaIwiYjK7A==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.20.12 + '@babel/core': 7.21.4 '@babel/helper-plugin-utils': 7.20.2 dev: true @@ -2220,16 +2127,6 @@ packages: '@babel/helper-plugin-utils': 7.20.2 dev: true - /@babel/plugin-syntax-jsx@7.18.6(@babel/core@7.20.12): - resolution: {integrity: sha512-6mmljtAedFGTWu2p/8WIORGwy+61PLgOMPOdazc7YoJ9ZCWUyFy3A6CpPkRKLKD1ToAesxX8KGEViAiLo9N+7Q==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.20.12 - '@babel/helper-plugin-utils': 7.20.2 - dev: true - /@babel/plugin-syntax-jsx@7.18.6(@babel/core@7.21.4): resolution: {integrity: sha512-6mmljtAedFGTWu2p/8WIORGwy+61PLgOMPOdazc7YoJ9ZCWUyFy3A6CpPkRKLKD1ToAesxX8KGEViAiLo9N+7Q==} engines: {node: '>=6.9.0'} @@ -2238,58 +2135,57 @@ packages: dependencies: '@babel/core': 7.21.4 '@babel/helper-plugin-utils': 7.20.2 - dev: false - /@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.20.12): + /@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.21.4): resolution: {integrity: sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.20.12 + '@babel/core': 7.21.4 '@babel/helper-plugin-utils': 7.20.2 dev: true - /@babel/plugin-transform-arrow-functions@7.20.7(@babel/core@7.20.12): + /@babel/plugin-transform-arrow-functions@7.20.7(@babel/core@7.21.4): resolution: {integrity: sha512-3poA5E7dzDomxj9WXWwuD6A5F3kc7VXwIJO+E+J8qtDtS+pXPAhrgEyh+9GBwBgPq1Z+bB+/JD60lp5jsN7JPQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.20.12 + '@babel/core': 7.21.4 '@babel/helper-plugin-utils': 7.20.2 dev: true - /@babel/plugin-transform-block-scoped-functions@7.18.6(@babel/core@7.20.12): + /@babel/plugin-transform-block-scoped-functions@7.18.6(@babel/core@7.21.4): resolution: {integrity: sha512-ExUcOqpPWnliRcPqves5HJcJOvHvIIWfuS4sroBUenPuMdmW+SMHDakmtS7qOo13sVppmUijqeTv7qqGsvURpQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.20.12 + '@babel/core': 7.21.4 '@babel/helper-plugin-utils': 7.20.2 dev: true - /@babel/plugin-transform-block-scoping@7.20.15(@babel/core@7.20.12): + /@babel/plugin-transform-block-scoping@7.20.15(@babel/core@7.21.4): resolution: {integrity: sha512-Vv4DMZ6MiNOhu/LdaZsT/bsLRxgL94d269Mv4R/9sp6+Mp++X/JqypZYypJXLlM4mlL352/Egzbzr98iABH1CA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.20.12 + '@babel/core': 7.21.4 '@babel/helper-plugin-utils': 7.20.2 dev: true - /@babel/plugin-transform-classes@7.20.7(@babel/core@7.20.12): + /@babel/plugin-transform-classes@7.20.7(@babel/core@7.21.4): resolution: {integrity: sha512-LWYbsiXTPKl+oBlXUGlwNlJZetXD5Am+CyBdqhPsDVjM9Jc8jwBJFrKhHf900Kfk2eZG1y9MAG3UNajol7A4VQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.20.12 + '@babel/core': 7.21.4 '@babel/helper-annotate-as-pure': 7.18.6 - '@babel/helper-compilation-targets': 7.20.7(@babel/core@7.20.12) + '@babel/helper-compilation-targets': 7.21.4(@babel/core@7.21.4) '@babel/helper-environment-visitor': 7.18.9 - '@babel/helper-function-name': 7.19.0 + '@babel/helper-function-name': 7.21.0 '@babel/helper-optimise-call-expression': 7.18.6 '@babel/helper-plugin-utils': 7.20.2 '@babel/helper-replace-supers': 7.20.7 @@ -2299,197 +2195,197 @@ packages: - supports-color dev: true - /@babel/plugin-transform-computed-properties@7.20.7(@babel/core@7.20.12): + /@babel/plugin-transform-computed-properties@7.20.7(@babel/core@7.21.4): resolution: {integrity: sha512-Lz7MvBK6DTjElHAmfu6bfANzKcxpyNPeYBGEafyA6E5HtRpjpZwU+u7Qrgz/2OR0z+5TvKYbPdphfSaAcZBrYQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.20.12 + '@babel/core': 7.21.4 '@babel/helper-plugin-utils': 7.20.2 '@babel/template': 7.20.7 dev: true - /@babel/plugin-transform-destructuring@7.20.7(@babel/core@7.20.12): + /@babel/plugin-transform-destructuring@7.20.7(@babel/core@7.21.4): resolution: {integrity: sha512-Xwg403sRrZb81IVB79ZPqNQME23yhugYVqgTxAhT99h485F4f+GMELFhhOsscDUB7HCswepKeCKLn/GZvUKoBA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.20.12 + '@babel/core': 7.21.4 '@babel/helper-plugin-utils': 7.20.2 dev: true - /@babel/plugin-transform-flow-strip-types@7.19.0(@babel/core@7.20.12): + /@babel/plugin-transform-flow-strip-types@7.19.0(@babel/core@7.21.4): resolution: {integrity: sha512-sgeMlNaQVbCSpgLSKP4ZZKfsJVnFnNQlUSk6gPYzR/q7tzCgQF2t8RBKAP6cKJeZdveei7Q7Jm527xepI8lNLg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.20.12 + '@babel/core': 7.21.4 '@babel/helper-plugin-utils': 7.20.2 - '@babel/plugin-syntax-flow': 7.18.6(@babel/core@7.20.12) + '@babel/plugin-syntax-flow': 7.18.6(@babel/core@7.21.4) dev: true - /@babel/plugin-transform-for-of@7.18.8(@babel/core@7.20.12): + /@babel/plugin-transform-for-of@7.18.8(@babel/core@7.21.4): resolution: {integrity: sha512-yEfTRnjuskWYo0k1mHUqrVWaZwrdq8AYbfrpqULOJOaucGSp4mNMVps+YtA8byoevxS/urwU75vyhQIxcCgiBQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.20.12 + '@babel/core': 7.21.4 '@babel/helper-plugin-utils': 7.20.2 dev: true - /@babel/plugin-transform-function-name@7.18.9(@babel/core@7.20.12): + /@babel/plugin-transform-function-name@7.18.9(@babel/core@7.21.4): resolution: {integrity: sha512-WvIBoRPaJQ5yVHzcnJFor7oS5Ls0PYixlTYE63lCj2RtdQEl15M68FXQlxnG6wdraJIXRdR7KI+hQ7q/9QjrCQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.20.12 - '@babel/helper-compilation-targets': 7.20.7(@babel/core@7.20.12) - '@babel/helper-function-name': 7.19.0 + '@babel/core': 7.21.4 + '@babel/helper-compilation-targets': 7.21.4(@babel/core@7.21.4) + '@babel/helper-function-name': 7.21.0 '@babel/helper-plugin-utils': 7.20.2 dev: true - /@babel/plugin-transform-literals@7.18.9(@babel/core@7.20.12): + /@babel/plugin-transform-literals@7.18.9(@babel/core@7.21.4): resolution: {integrity: sha512-IFQDSRoTPnrAIrI5zoZv73IFeZu2dhu6irxQjY9rNjTT53VmKg9fenjvoiOWOkJ6mm4jKVPtdMzBY98Fp4Z4cg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.20.12 + '@babel/core': 7.21.4 '@babel/helper-plugin-utils': 7.20.2 dev: true - /@babel/plugin-transform-member-expression-literals@7.18.6(@babel/core@7.20.12): + /@babel/plugin-transform-member-expression-literals@7.18.6(@babel/core@7.21.4): resolution: {integrity: sha512-qSF1ihLGO3q+/g48k85tUjD033C29TNTVB2paCwZPVmOsjn9pClvYYrM2VeJpBY2bcNkuny0YUyTNRyRxJ54KA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.20.12 + '@babel/core': 7.21.4 '@babel/helper-plugin-utils': 7.20.2 dev: true - /@babel/plugin-transform-modules-commonjs@7.20.11(@babel/core@7.20.12): + /@babel/plugin-transform-modules-commonjs@7.20.11(@babel/core@7.21.4): resolution: {integrity: sha512-S8e1f7WQ7cimJQ51JkAaDrEtohVEitXjgCGAS2N8S31Y42E+kWwfSz83LYz57QdBm7q9diARVqanIaH2oVgQnw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.20.12 - '@babel/helper-module-transforms': 7.20.11 + '@babel/core': 7.21.4 + '@babel/helper-module-transforms': 7.21.2 '@babel/helper-plugin-utils': 7.20.2 '@babel/helper-simple-access': 7.20.2 transitivePeerDependencies: - supports-color dev: true - /@babel/plugin-transform-object-super@7.18.6(@babel/core@7.20.12): + /@babel/plugin-transform-object-super@7.18.6(@babel/core@7.21.4): resolution: {integrity: sha512-uvGz6zk+pZoS1aTZrOvrbj6Pp/kK2mp45t2B+bTDre2UgsZZ8EZLSJtUg7m/no0zOJUWgFONpB7Zv9W2tSaFlA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.20.12 + '@babel/core': 7.21.4 '@babel/helper-plugin-utils': 7.20.2 '@babel/helper-replace-supers': 7.20.7 transitivePeerDependencies: - supports-color dev: true - /@babel/plugin-transform-parameters@7.20.7(@babel/core@7.20.12): + /@babel/plugin-transform-parameters@7.20.7(@babel/core@7.21.4): resolution: {integrity: sha512-WiWBIkeHKVOSYPO0pWkxGPfKeWrCJyD3NJ53+Lrp/QMSZbsVPovrVl2aWZ19D/LTVnaDv5Ap7GJ/B2CTOZdrfA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.20.12 + '@babel/core': 7.21.4 '@babel/helper-plugin-utils': 7.20.2 dev: true - /@babel/plugin-transform-property-literals@7.18.6(@babel/core@7.20.12): + /@babel/plugin-transform-property-literals@7.18.6(@babel/core@7.21.4): resolution: {integrity: sha512-cYcs6qlgafTud3PAzrrRNbQtfpQ8+y/+M5tKmksS9+M1ckbH6kzY8MrexEM9mcA6JDsukE19iIRvAyYl463sMg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.20.12 + '@babel/core': 7.21.4 '@babel/helper-plugin-utils': 7.20.2 dev: true - /@babel/plugin-transform-react-display-name@7.18.6(@babel/core@7.20.12): + /@babel/plugin-transform-react-display-name@7.18.6(@babel/core@7.21.4): resolution: {integrity: sha512-TV4sQ+T013n61uMoygyMRm+xf04Bd5oqFpv2jAEQwSZ8NwQA7zeRPg1LMVg2PWi3zWBz+CLKD+v5bcpZ/BS0aA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.20.12 + '@babel/core': 7.21.4 '@babel/helper-plugin-utils': 7.20.2 dev: true - /@babel/plugin-transform-react-jsx-self@7.18.6(@babel/core@7.20.12): + /@babel/plugin-transform-react-jsx-self@7.18.6(@babel/core@7.21.4): resolution: {integrity: sha512-A0LQGx4+4Jv7u/tWzoJF7alZwnBDQd6cGLh9P+Ttk4dpiL+J5p7NSNv/9tlEFFJDq3kjxOavWmbm6t0Gk+A3Ig==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.20.12 + '@babel/core': 7.21.4 '@babel/helper-plugin-utils': 7.20.2 - /@babel/plugin-transform-react-jsx-source@7.19.6(@babel/core@7.20.12): + /@babel/plugin-transform-react-jsx-source@7.19.6(@babel/core@7.21.4): resolution: {integrity: sha512-RpAi004QyMNisst/pvSanoRdJ4q+jMCWyk9zdw/CyLB9j8RXEahodR6l2GyttDRyEVWZtbN+TpLiHJ3t34LbsQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.20.12 + '@babel/core': 7.21.4 '@babel/helper-plugin-utils': 7.20.2 - /@babel/plugin-transform-react-jsx@7.20.13(@babel/core@7.20.12): + /@babel/plugin-transform-react-jsx@7.20.13(@babel/core@7.21.4): resolution: {integrity: sha512-MmTZx/bkUrfJhhYAYt3Urjm+h8DQGrPrnKQ94jLo7NLuOU+T89a7IByhKmrb8SKhrIYIQ0FN0CHMbnFRen4qNw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.20.12 + '@babel/core': 7.21.4 '@babel/helper-annotate-as-pure': 7.18.6 - '@babel/helper-module-imports': 7.18.6 + '@babel/helper-module-imports': 7.21.4 '@babel/helper-plugin-utils': 7.20.2 - '@babel/plugin-syntax-jsx': 7.18.6(@babel/core@7.20.12) - '@babel/types': 7.20.7 + '@babel/plugin-syntax-jsx': 7.18.6(@babel/core@7.21.4) + '@babel/types': 7.21.4 dev: true - /@babel/plugin-transform-shorthand-properties@7.18.6(@babel/core@7.20.12): + /@babel/plugin-transform-shorthand-properties@7.18.6(@babel/core@7.21.4): resolution: {integrity: sha512-eCLXXJqv8okzg86ywZJbRn19YJHU4XUa55oz2wbHhaQVn/MM+XhukiT7SYqp/7o00dg52Rj51Ny+Ecw4oyoygw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.20.12 + '@babel/core': 7.21.4 '@babel/helper-plugin-utils': 7.20.2 dev: true - /@babel/plugin-transform-spread@7.20.7(@babel/core@7.20.12): + /@babel/plugin-transform-spread@7.20.7(@babel/core@7.21.4): resolution: {integrity: sha512-ewBbHQ+1U/VnH1fxltbJqDeWBU1oNLG8Dj11uIv3xVf7nrQu0bPGe5Rf716r7K5Qz+SqtAOVswoVunoiBtGhxw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.20.12 + '@babel/core': 7.21.4 '@babel/helper-plugin-utils': 7.20.2 '@babel/helper-skip-transparent-expression-wrappers': 7.20.0 dev: true - /@babel/plugin-transform-template-literals@7.18.9(@babel/core@7.20.12): + /@babel/plugin-transform-template-literals@7.18.9(@babel/core@7.21.4): resolution: {integrity: sha512-S8cOWfT82gTezpYOiVaGHrCbhlHgKhQt8XH5ES46P2XWmX92yisoZywf5km75wv5sYcXDUCLMmMxOLCtthDgMA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.20.12 + '@babel/core': 7.21.4 '@babel/helper-plugin-utils': 7.20.2 dev: true @@ -2502,27 +2398,10 @@ packages: /@babel/template@7.20.7: resolution: {integrity: sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw==} engines: {node: '>=6.9.0'} - dependencies: - '@babel/code-frame': 7.18.6 - '@babel/parser': 7.20.15 - '@babel/types': 7.20.7 - - /@babel/traverse@7.20.13: - resolution: {integrity: sha512-kMJXfF0T6DIS9E8cgdLCSAL+cuCK+YEZHWiLK0SXpTo8YRj5lpJu3CDNKiIBCne4m9hhTIqUg6SYTAI39tAiVQ==} - engines: {node: '>=6.9.0'} dependencies: '@babel/code-frame': 7.21.4 - '@babel/generator': 7.20.14 - '@babel/helper-environment-visitor': 7.18.9 - '@babel/helper-function-name': 7.19.0 - '@babel/helper-hoist-variables': 7.18.6 - '@babel/helper-split-export-declaration': 7.18.6 - '@babel/parser': 7.20.15 - '@babel/types': 7.20.7 - debug: 4.3.4 - globals: 11.12.0 - transitivePeerDependencies: - - supports-color + '@babel/parser': 7.21.4 + '@babel/types': 7.21.4 /@babel/traverse@7.21.4: resolution: {integrity: sha512-eyKrRHKdyZxqDm+fV1iqL9UAHMoIg0nDaGqfIOd8rKH17m5snv7Gn4qgjBoFfLz9APvjFU/ICT00NVCv1Epp8Q==} @@ -2548,6 +2427,7 @@ packages: '@babel/helper-string-parser': 7.19.4 '@babel/helper-validator-identifier': 7.19.1 to-fast-properties: 2.0.0 + dev: true /@babel/types@7.21.4: resolution: {integrity: sha512-rU2oY501qDxE8Pyo7i/Orqma4ziCOrby0/9mvbDUGEfvZjb279Nk9k19e2fiCxHbRRpY2ZyrgW1eq22mvmOIzA==} @@ -2776,7 +2656,7 @@ packages: '@babel/core': ^7.0.0 dependencies: '@babel/core': 7.21.4 - '@babel/helper-module-imports': 7.18.6 + '@babel/helper-module-imports': 7.21.4 '@babel/plugin-syntax-jsx': 7.18.6(@babel/core@7.21.4) '@babel/runtime': 7.20.13 '@emotion/hash': 0.9.0 @@ -2853,176 +2733,176 @@ packages: resolution: {integrity: sha512-AHPmaAx+RYfZz0eYu6Gviiagpmiyw98ySSlQvCUhVGDRtDFe4DBS0x1bSjdF3gqUDYOczB+yYvBTtEylYSdRhg==} dev: false - /@esbuild/android-arm64@0.16.17: - resolution: {integrity: sha512-MIGl6p5sc3RDTLLkYL1MyL8BMRN4tLMRCn+yRJJmEDvYZ2M7tmAf80hx1kbNEUX2KJ50RRtxZ4JHLvCfuB6kBg==} + /@esbuild/android-arm64@0.17.17: + resolution: {integrity: sha512-jaJ5IlmaDLFPNttv0ofcwy/cfeY4bh/n705Tgh+eLObbGtQBK3EPAu+CzL95JVE4nFAliyrnEu0d32Q5foavqg==} engines: {node: '>=12'} cpu: [arm64] os: [android] requiresBuild: true optional: true - /@esbuild/android-arm@0.16.17: - resolution: {integrity: sha512-N9x1CMXVhtWEAMS7pNNONyA14f71VPQN9Cnavj1XQh6T7bskqiLLrSca4O0Vr8Wdcga943eThxnVp3JLnBMYtw==} + /@esbuild/android-arm@0.17.17: + resolution: {integrity: sha512-E6VAZwN7diCa3labs0GYvhEPL2M94WLF8A+czO8hfjREXxba8Ng7nM5VxV+9ihNXIY1iQO1XxUU4P7hbqbICxg==} engines: {node: '>=12'} cpu: [arm] os: [android] requiresBuild: true optional: true - /@esbuild/android-x64@0.16.17: - resolution: {integrity: sha512-a3kTv3m0Ghh4z1DaFEuEDfz3OLONKuFvI4Xqczqx4BqLyuFaFkuaG4j2MtA6fuWEFeC5x9IvqnX7drmRq/fyAQ==} + /@esbuild/android-x64@0.17.17: + resolution: {integrity: sha512-446zpfJ3nioMC7ASvJB1pszHVskkw4u/9Eu8s5yvvsSDTzYh4p4ZIRj0DznSl3FBF0Z/mZfrKXTtt0QCoFmoHA==} engines: {node: '>=12'} cpu: [x64] os: [android] requiresBuild: true optional: true - /@esbuild/darwin-arm64@0.16.17: - resolution: {integrity: sha512-/2agbUEfmxWHi9ARTX6OQ/KgXnOWfsNlTeLcoV7HSuSTv63E4DqtAc+2XqGw1KHxKMHGZgbVCZge7HXWX9Vn+w==} + /@esbuild/darwin-arm64@0.17.17: + resolution: {integrity: sha512-m/gwyiBwH3jqfUabtq3GH31otL/0sE0l34XKpSIqR7NjQ/XHQ3lpmQHLHbG8AHTGCw8Ao059GvV08MS0bhFIJQ==} engines: {node: '>=12'} cpu: [arm64] os: [darwin] requiresBuild: true optional: true - /@esbuild/darwin-x64@0.16.17: - resolution: {integrity: sha512-2By45OBHulkd9Svy5IOCZt376Aa2oOkiE9QWUK9fe6Tb+WDr8hXL3dpqi+DeLiMed8tVXspzsTAvd0jUl96wmg==} + /@esbuild/darwin-x64@0.17.17: + resolution: {integrity: sha512-4utIrsX9IykrqYaXR8ob9Ha2hAY2qLc6ohJ8c0CN1DR8yWeMrTgYFjgdeQ9LIoTOfLetXjuCu5TRPHT9yKYJVg==} engines: {node: '>=12'} cpu: [x64] os: [darwin] requiresBuild: true optional: true - /@esbuild/freebsd-arm64@0.16.17: - resolution: {integrity: sha512-mt+cxZe1tVx489VTb4mBAOo2aKSnJ33L9fr25JXpqQqzbUIw/yzIzi+NHwAXK2qYV1lEFp4OoVeThGjUbmWmdw==} + /@esbuild/freebsd-arm64@0.17.17: + resolution: {integrity: sha512-4PxjQII/9ppOrpEwzQ1b0pXCsFLqy77i0GaHodrmzH9zq2/NEhHMAMJkJ635Ns4fyJPFOlHMz4AsklIyRqFZWA==} engines: {node: '>=12'} cpu: [arm64] os: [freebsd] requiresBuild: true optional: true - /@esbuild/freebsd-x64@0.16.17: - resolution: {integrity: sha512-8ScTdNJl5idAKjH8zGAsN7RuWcyHG3BAvMNpKOBaqqR7EbUhhVHOqXRdL7oZvz8WNHL2pr5+eIT5c65kA6NHug==} + /@esbuild/freebsd-x64@0.17.17: + resolution: {integrity: sha512-lQRS+4sW5S3P1sv0z2Ym807qMDfkmdhUYX30GRBURtLTrJOPDpoU0kI6pVz1hz3U0+YQ0tXGS9YWveQjUewAJw==} engines: {node: '>=12'} cpu: [x64] os: [freebsd] requiresBuild: true optional: true - /@esbuild/linux-arm64@0.16.17: - resolution: {integrity: sha512-7S8gJnSlqKGVJunnMCrXHU9Q8Q/tQIxk/xL8BqAP64wchPCTzuM6W3Ra8cIa1HIflAvDnNOt2jaL17vaW+1V0g==} + /@esbuild/linux-arm64@0.17.17: + resolution: {integrity: sha512-2+pwLx0whKY1/Vqt8lyzStyda1v0qjJ5INWIe+d8+1onqQxHLLi3yr5bAa4gvbzhZqBztifYEu8hh1La5+7sUw==} engines: {node: '>=12'} cpu: [arm64] os: [linux] requiresBuild: true optional: true - /@esbuild/linux-arm@0.16.17: - resolution: {integrity: sha512-iihzrWbD4gIT7j3caMzKb/RsFFHCwqqbrbH9SqUSRrdXkXaygSZCZg1FybsZz57Ju7N/SHEgPyaR0LZ8Zbe9gQ==} + /@esbuild/linux-arm@0.17.17: + resolution: {integrity: sha512-biDs7bjGdOdcmIk6xU426VgdRUpGg39Yz6sT9Xp23aq+IEHDb/u5cbmu/pAANpDB4rZpY/2USPhCA+w9t3roQg==} engines: {node: '>=12'} cpu: [arm] os: [linux] requiresBuild: true optional: true - /@esbuild/linux-ia32@0.16.17: - resolution: {integrity: sha512-kiX69+wcPAdgl3Lonh1VI7MBr16nktEvOfViszBSxygRQqSpzv7BffMKRPMFwzeJGPxcio0pdD3kYQGpqQ2SSg==} + /@esbuild/linux-ia32@0.17.17: + resolution: {integrity: sha512-IBTTv8X60dYo6P2t23sSUYym8fGfMAiuv7PzJ+0LcdAndZRzvke+wTVxJeCq4WgjppkOpndL04gMZIFvwoU34Q==} engines: {node: '>=12'} cpu: [ia32] os: [linux] requiresBuild: true optional: true - /@esbuild/linux-loong64@0.16.17: - resolution: {integrity: sha512-dTzNnQwembNDhd654cA4QhbS9uDdXC3TKqMJjgOWsC0yNCbpzfWoXdZvp0mY7HU6nzk5E0zpRGGx3qoQg8T2DQ==} + /@esbuild/linux-loong64@0.17.17: + resolution: {integrity: sha512-WVMBtcDpATjaGfWfp6u9dANIqmU9r37SY8wgAivuKmgKHE+bWSuv0qXEFt/p3qXQYxJIGXQQv6hHcm7iWhWjiw==} engines: {node: '>=12'} cpu: [loong64] os: [linux] requiresBuild: true optional: true - /@esbuild/linux-mips64el@0.16.17: - resolution: {integrity: sha512-ezbDkp2nDl0PfIUn0CsQ30kxfcLTlcx4Foz2kYv8qdC6ia2oX5Q3E/8m6lq84Dj/6b0FrkgD582fJMIfHhJfSw==} + /@esbuild/linux-mips64el@0.17.17: + resolution: {integrity: sha512-2kYCGh8589ZYnY031FgMLy0kmE4VoGdvfJkxLdxP4HJvWNXpyLhjOvxVsYjYZ6awqY4bgLR9tpdYyStgZZhi2A==} engines: {node: '>=12'} cpu: [mips64el] os: [linux] requiresBuild: true optional: true - /@esbuild/linux-ppc64@0.16.17: - resolution: {integrity: sha512-dzS678gYD1lJsW73zrFhDApLVdM3cUF2MvAa1D8K8KtcSKdLBPP4zZSLy6LFZ0jYqQdQ29bjAHJDgz0rVbLB3g==} + /@esbuild/linux-ppc64@0.17.17: + resolution: {integrity: sha512-KIdG5jdAEeAKogfyMTcszRxy3OPbZhq0PPsW4iKKcdlbk3YE4miKznxV2YOSmiK/hfOZ+lqHri3v8eecT2ATwQ==} engines: {node: '>=12'} cpu: [ppc64] os: [linux] requiresBuild: true optional: true - /@esbuild/linux-riscv64@0.16.17: - resolution: {integrity: sha512-ylNlVsxuFjZK8DQtNUwiMskh6nT0vI7kYl/4fZgV1llP5d6+HIeL/vmmm3jpuoo8+NuXjQVZxmKuhDApK0/cKw==} + /@esbuild/linux-riscv64@0.17.17: + resolution: {integrity: sha512-Cj6uWLBR5LWhcD/2Lkfg2NrkVsNb2sFM5aVEfumKB2vYetkA/9Uyc1jVoxLZ0a38sUhFk4JOVKH0aVdPbjZQeA==} engines: {node: '>=12'} cpu: [riscv64] os: [linux] requiresBuild: true optional: true - /@esbuild/linux-s390x@0.16.17: - resolution: {integrity: sha512-gzy7nUTO4UA4oZ2wAMXPNBGTzZFP7mss3aKR2hH+/4UUkCOyqmjXiKpzGrY2TlEUhbbejzXVKKGazYcQTZWA/w==} + /@esbuild/linux-s390x@0.17.17: + resolution: {integrity: sha512-lK+SffWIr0XsFf7E0srBjhpkdFVJf3HEgXCwzkm69kNbRar8MhezFpkIwpk0qo2IOQL4JE4mJPJI8AbRPLbuOQ==} engines: {node: '>=12'} cpu: [s390x] os: [linux] requiresBuild: true optional: true - /@esbuild/linux-x64@0.16.17: - resolution: {integrity: sha512-mdPjPxfnmoqhgpiEArqi4egmBAMYvaObgn4poorpUaqmvzzbvqbowRllQ+ZgzGVMGKaPkqUmPDOOFQRUFDmeUw==} + /@esbuild/linux-x64@0.17.17: + resolution: {integrity: sha512-XcSGTQcWFQS2jx3lZtQi7cQmDYLrpLRyz1Ns1DzZCtn898cWfm5Icx/DEWNcTU+T+tyPV89RQtDnI7qL2PObPg==} engines: {node: '>=12'} cpu: [x64] os: [linux] requiresBuild: true optional: true - /@esbuild/netbsd-x64@0.16.17: - resolution: {integrity: sha512-/PzmzD/zyAeTUsduZa32bn0ORug+Jd1EGGAUJvqfeixoEISYpGnAezN6lnJoskauoai0Jrs+XSyvDhppCPoKOA==} + /@esbuild/netbsd-x64@0.17.17: + resolution: {integrity: sha512-RNLCDmLP5kCWAJR+ItLM3cHxzXRTe4N00TQyQiimq+lyqVqZWGPAvcyfUBM0isE79eEZhIuGN09rAz8EL5KdLA==} engines: {node: '>=12'} cpu: [x64] os: [netbsd] requiresBuild: true optional: true - /@esbuild/openbsd-x64@0.16.17: - resolution: {integrity: sha512-2yaWJhvxGEz2RiftSk0UObqJa/b+rIAjnODJgv2GbGGpRwAfpgzyrg1WLK8rqA24mfZa9GvpjLcBBg8JHkoodg==} + /@esbuild/openbsd-x64@0.17.17: + resolution: {integrity: sha512-PAXswI5+cQq3Pann7FNdcpSUrhrql3wKjj3gVkmuz6OHhqqYxKvi6GgRBoaHjaG22HV/ZZEgF9TlS+9ftHVigA==} engines: {node: '>=12'} cpu: [x64] os: [openbsd] requiresBuild: true optional: true - /@esbuild/sunos-x64@0.16.17: - resolution: {integrity: sha512-xtVUiev38tN0R3g8VhRfN7Zl42YCJvyBhRKw1RJjwE1d2emWTVToPLNEQj/5Qxc6lVFATDiy6LjVHYhIPrLxzw==} + /@esbuild/sunos-x64@0.17.17: + resolution: {integrity: sha512-V63egsWKnx/4V0FMYkr9NXWrKTB5qFftKGKuZKFIrAkO/7EWLFnbBZNM1CvJ6Sis+XBdPws2YQSHF1Gqf1oj/Q==} engines: {node: '>=12'} cpu: [x64] os: [sunos] requiresBuild: true optional: true - /@esbuild/win32-arm64@0.16.17: - resolution: {integrity: sha512-ga8+JqBDHY4b6fQAmOgtJJue36scANy4l/rL97W+0wYmijhxKetzZdKOJI7olaBaMhWt8Pac2McJdZLxXWUEQw==} + /@esbuild/win32-arm64@0.17.17: + resolution: {integrity: sha512-YtUXLdVnd6YBSYlZODjWzH+KzbaubV0YVd6UxSfoFfa5PtNJNaW+1i+Hcmjpg2nEe0YXUCNF5bkKy1NnBv1y7Q==} engines: {node: '>=12'} cpu: [arm64] os: [win32] requiresBuild: true optional: true - /@esbuild/win32-ia32@0.16.17: - resolution: {integrity: sha512-WnsKaf46uSSF/sZhwnqE4L/F89AYNMiD4YtEcYekBt9Q7nj0DiId2XH2Ng2PHM54qi5oPrQ8luuzGszqi/veig==} + /@esbuild/win32-ia32@0.17.17: + resolution: {integrity: sha512-yczSLRbDdReCO74Yfc5tKG0izzm+lPMYyO1fFTcn0QNwnKmc3K+HdxZWLGKg4pZVte7XVgcFku7TIZNbWEJdeQ==} engines: {node: '>=12'} cpu: [ia32] os: [win32] requiresBuild: true optional: true - /@esbuild/win32-x64@0.16.17: - resolution: {integrity: sha512-y+EHuSchhL7FjHgvQL/0fnnFmO4T1bhvWANX6gcnqTjtnKWbTvUMCpGnv2+t+31d7RzyEAYAd4u2fnIhHL6N/Q==} + /@esbuild/win32-x64@0.17.17: + resolution: {integrity: sha512-FNZw7H3aqhF9OyRQbDDnzUApDXfC1N6fgBhkqEO2jvYCJ+DxMTfZVqg3AX0R1khg1wHTBRD5SdcibSJ+XF6bFg==} engines: {node: '>=12'} cpu: [x64] os: [win32] @@ -3897,10 +3777,10 @@ packages: peerDependencies: graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 dependencies: - '@babel/parser': 7.20.15 + '@babel/parser': 7.21.4 '@babel/plugin-syntax-import-assertions': 7.20.0(@babel/core@7.21.4) - '@babel/traverse': 7.20.13 - '@babel/types': 7.20.7 + '@babel/traverse': 7.21.4 + '@babel/types': 7.21.4 '@graphql-tools/utils': 9.2.1(graphql@16.6.0) graphql: 16.6.0 tslib: 2.5.0 @@ -4243,21 +4123,6 @@ packages: engines: {node: '>=8'} dev: true - /@jridgewell/gen-mapping@0.1.1: - resolution: {integrity: sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==} - engines: {node: '>=6.0.0'} - dependencies: - '@jridgewell/set-array': 1.1.2 - '@jridgewell/sourcemap-codec': 1.4.14 - - /@jridgewell/gen-mapping@0.3.2: - resolution: {integrity: sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==} - engines: {node: '>=6.0.0'} - dependencies: - '@jridgewell/set-array': 1.1.2 - '@jridgewell/sourcemap-codec': 1.4.14 - '@jridgewell/trace-mapping': 0.3.17 - /@jridgewell/gen-mapping@0.3.3: resolution: {integrity: sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==} engines: {node: '>=6.0.0'} @@ -4280,12 +4145,6 @@ packages: /@jridgewell/sourcemap-codec@1.4.15: resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==} - /@jridgewell/trace-mapping@0.3.17: - resolution: {integrity: sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==} - dependencies: - '@jridgewell/resolve-uri': 3.1.0 - '@jridgewell/sourcemap-codec': 1.4.14 - /@jridgewell/trace-mapping@0.3.18: resolution: {integrity: sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==} dependencies: @@ -6630,6 +6489,13 @@ packages: resolution: {integrity: sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==} dev: false + /@types/node-fetch@2.6.3: + resolution: {integrity: sha512-ETTL1mOEdq/sxUtgtOhKjyB2Irra4cjxksvcMUR5Zr4n+PxVhsCD9WS46oPbHL3et9Zde7CNRr+WUNlcHvsX+w==} + dependencies: + '@types/node': 18.13.0 + form-data: 3.0.1 + dev: false + /@types/node@12.20.55: resolution: {integrity: sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==} @@ -7063,37 +6929,21 @@ packages: graphql: 16.6.0 dev: true - /@vitejs/plugin-react@3.1.0(vite@4.1.1): + /@vitejs/plugin-react@3.1.0(vite@4.2.1): resolution: {integrity: sha512-AfgcRL8ZBhAlc3BFdigClmTUMISmmzHn7sB2h9U1odvc5U/MjWXsAaz18b/WoppUTDBzxOJwo2VdClfUcItu9g==} engines: {node: ^14.18.0 || >=16.0.0} peerDependencies: vite: ^4.1.0-beta.0 dependencies: - '@babel/core': 7.20.12 - '@babel/plugin-transform-react-jsx-self': 7.18.6(@babel/core@7.20.12) - '@babel/plugin-transform-react-jsx-source': 7.19.6(@babel/core@7.20.12) + '@babel/core': 7.21.4 + '@babel/plugin-transform-react-jsx-self': 7.18.6(@babel/core@7.21.4) + '@babel/plugin-transform-react-jsx-source': 7.19.6(@babel/core@7.21.4) magic-string: 0.27.0 react-refresh: 0.14.0 - vite: 4.1.1(@types/node@18.13.0) + vite: 4.2.1(@types/node@18.13.0) transitivePeerDependencies: - supports-color - /@vitejs/plugin-react@3.1.0(vite@4.1.4): - resolution: {integrity: sha512-AfgcRL8ZBhAlc3BFdigClmTUMISmmzHn7sB2h9U1odvc5U/MjWXsAaz18b/WoppUTDBzxOJwo2VdClfUcItu9g==} - engines: {node: ^14.18.0 || >=16.0.0} - peerDependencies: - vite: ^4.1.0-beta.0 - dependencies: - '@babel/core': 7.20.12 - '@babel/plugin-transform-react-jsx-self': 7.18.6(@babel/core@7.20.12) - '@babel/plugin-transform-react-jsx-source': 7.19.6(@babel/core@7.20.12) - magic-string: 0.27.0 - react-refresh: 0.14.0 - vite: 4.1.4(@types/node@18.13.0) - transitivePeerDependencies: - - supports-color - dev: true - /@vitest/coverage-c8@0.28.4(jsdom@20.0.3): resolution: {integrity: sha512-btelLBxaWhHnywXRQxDlrvPhGdnuIaD3XulsxcZRIcnpLPbFu39dNTT0IYu2QWP2ZZrV0AmNtdLIfD4c77zMAg==} dependencies: @@ -7121,41 +6971,49 @@ packages: '@vitest/spy': 0.28.4 '@vitest/utils': 0.28.4 chai: 4.3.7 - - /@vitest/expect@0.29.3: - resolution: {integrity: sha512-z/0JqBqqrdtrT/wzxNrWC76EpkOHdl+SvuNGxWulLaoluygntYyG5wJul5u/rQs5875zfFz/F+JaDf90SkLUIg==} - dependencies: - '@vitest/spy': 0.29.3 - '@vitest/utils': 0.29.3 - chai: 4.3.7 dev: true + /@vitest/expect@0.30.1: + resolution: {integrity: sha512-c3kbEtN8XXJSeN81iDGq29bUzSjQhjES2WR3aColsS4lPGbivwLtas4DNUe0jD9gg/FYGIteqOenfU95EFituw==} + dependencies: + '@vitest/spy': 0.30.1 + '@vitest/utils': 0.30.1 + chai: 4.3.7 + /@vitest/runner@0.28.4: resolution: {integrity: sha512-Q8UV6GjDvBSTfUoq0QXVCNpNOUrWu4P2qvRq7ssJWzn0+S0ojbVOxEjMt+8a32X6SdkhF8ak+2nkppsqV0JyNQ==} dependencies: '@vitest/utils': 0.28.4 p-limit: 4.0.0 pathe: 1.1.0 + dev: true - /@vitest/runner@0.29.3: - resolution: {integrity: sha512-XLi8ctbvOWhUWmuvBUSIBf8POEDH4zCh6bOuVxm/KGfARpgmVF1ku+vVNvyq85va+7qXxtl+MFmzyXQ2xzhAvw==} + /@vitest/runner@0.30.1: + resolution: {integrity: sha512-W62kT/8i0TF1UBCNMRtRMOBWJKRnNyv9RrjIgdUryEe0wNpGZvvwPDLuzYdxvgSckzjp54DSpv1xUbv4BQ0qVA==} dependencies: - '@vitest/utils': 0.29.3 + '@vitest/utils': 0.30.1 + concordance: 5.0.4 p-limit: 4.0.0 pathe: 1.1.0 - dev: true + + /@vitest/snapshot@0.30.1: + resolution: {integrity: sha512-fJZqKrE99zo27uoZA/azgWyWbFvM1rw2APS05yB0JaLwUIg9aUtvvnBf4q7JWhEcAHmSwbrxKFgyBUga6tq9Tw==} + dependencies: + magic-string: 0.30.0 + pathe: 1.1.0 + pretty-format: 27.5.1 /@vitest/spy@0.28.4: resolution: {integrity: sha512-8WuhfXLlvCXpNXEGJW6Gc+IKWI32435fQJLh43u70HnZ1otJOa2Cmg2Wy2Aym47ZnNCP4NolF+8cUPwd0MigKQ==} dependencies: tinyspy: 1.1.1 - - /@vitest/spy@0.29.3: - resolution: {integrity: sha512-LLpCb1oOCOZcBm0/Oxbr1DQTuKLRBsSIHyLYof7z4QVE8/v8NcZKdORjMUq645fcfX55+nLXwU/1AQ+c2rND+w==} - dependencies: - tinyspy: 1.1.1 dev: true + /@vitest/spy@0.30.1: + resolution: {integrity: sha512-YfJeIf37GvTZe04ZKxzJfnNNuNSmTEGnla2OdL60C8od16f3zOfv9q9K0nNii0NfjDJRt/CVN/POuY5/zTS+BA==} + dependencies: + tinyspy: 2.1.0 + /@vitest/utils@0.28.4: resolution: {integrity: sha512-l2QztOLdc2LkR+w/lP52RGh8hW+Ul4KESmCAgVE8q737I7e7bQoAfkARKpkPJ4JQtGpwW4deqlj1732VZD7TFw==} dependencies: @@ -7164,15 +7022,14 @@ packages: loupe: 2.3.6 picocolors: 1.0.0 pretty-format: 27.5.1 + dev: true - /@vitest/utils@0.29.3: - resolution: {integrity: sha512-hg4Ff8AM1GtUnLpUJlNMxrf9f4lZr/xRJjh3uJ0QFP+vjaW82HAxKrmeBmLnhc8Os2eRf+f+VBu4ts7TafPPkA==} + /@vitest/utils@0.30.1: + resolution: {integrity: sha512-/c8Xv2zUVc+rnNt84QF0Y0zkfxnaGhp87K2dYJMLtLOIckPzuxLVzAtFCicGFdB4NeBHNzTRr1tNn7rCtQcWFA==} dependencies: - cli-truncate: 3.1.0 - diff: 5.1.0 + concordance: 5.0.4 loupe: 2.3.6 pretty-format: 27.5.1 - dev: true /@web-std/blob@3.0.4: resolution: {integrity: sha512-+dibyiw+uHYK4dX5cJ7HA+gtDAaUUe6JsOryp2ZpAC7h4ICsh49E34JwHoEKPlPvP0llCrNzz45vvD+xX5QDBg==} @@ -7449,6 +7306,7 @@ packages: /ansi-regex@6.0.1: resolution: {integrity: sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==} engines: {node: '>=12'} + dev: true /ansi-styles@3.2.1: resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} @@ -7469,6 +7327,7 @@ packages: /ansi-styles@6.2.1: resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} engines: {node: '>=12'} + dev: true /anymatch@3.1.3: resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} @@ -7693,9 +7552,12 @@ packages: resolution: {integrity: sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==} engines: {node: '>= 0.4'} - /avatax@23.2.0: - resolution: {integrity: sha512-Onnm62xsVHQUDOmmqJ6xyguWiKMN9vJisz9eY0xmE4K/Tp0fnR/lkoC/uLdVykFpQ3pFxuvodDed0/ZF0YqLtw==} + /avatax@23.3.2: + resolution: {integrity: sha512-WGjSIQNvdM0XSyukjb+QIxB1z4xaEOdaGmPhXwLJYgxIuJlDw7Uq1nHUHo+yeHKZk3dKGzCyrFaa8N1Wx9nSbQ==} dependencies: + '@types/node-fetch': 2.6.3 + form-data: 4.0.0 + json2typescript: 1.5.1 node-fetch: 2.6.9 transitivePeerDependencies: - encoding @@ -7743,38 +7605,38 @@ packages: resolution: {integrity: sha512-Xj9XuRuz3nTSbaTXWv3itLOcxyF4oPD8douBBmj7U9BBC6nEBYfyOJYQMf/8PJAFotC62UY5dFfIGEPr7WswzQ==} dev: true - /babel-preset-fbjs@3.4.0(@babel/core@7.20.12): + /babel-preset-fbjs@3.4.0(@babel/core@7.21.4): resolution: {integrity: sha512-9ywCsCvo1ojrw0b+XYk7aFvTH6D9064t0RIL1rtMf3nsa02Xw41MS7sZw216Im35xj/UY0PDBQsa1brUDDF1Ow==} peerDependencies: '@babel/core': ^7.0.0 dependencies: - '@babel/core': 7.20.12 - '@babel/plugin-proposal-class-properties': 7.18.6(@babel/core@7.20.12) - '@babel/plugin-proposal-object-rest-spread': 7.20.7(@babel/core@7.20.12) - '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.20.12) - '@babel/plugin-syntax-flow': 7.18.6(@babel/core@7.20.12) - '@babel/plugin-syntax-jsx': 7.18.6(@babel/core@7.20.12) - '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.20.12) - '@babel/plugin-transform-arrow-functions': 7.20.7(@babel/core@7.20.12) - '@babel/plugin-transform-block-scoped-functions': 7.18.6(@babel/core@7.20.12) - '@babel/plugin-transform-block-scoping': 7.20.15(@babel/core@7.20.12) - '@babel/plugin-transform-classes': 7.20.7(@babel/core@7.20.12) - '@babel/plugin-transform-computed-properties': 7.20.7(@babel/core@7.20.12) - '@babel/plugin-transform-destructuring': 7.20.7(@babel/core@7.20.12) - '@babel/plugin-transform-flow-strip-types': 7.19.0(@babel/core@7.20.12) - '@babel/plugin-transform-for-of': 7.18.8(@babel/core@7.20.12) - '@babel/plugin-transform-function-name': 7.18.9(@babel/core@7.20.12) - '@babel/plugin-transform-literals': 7.18.9(@babel/core@7.20.12) - '@babel/plugin-transform-member-expression-literals': 7.18.6(@babel/core@7.20.12) - '@babel/plugin-transform-modules-commonjs': 7.20.11(@babel/core@7.20.12) - '@babel/plugin-transform-object-super': 7.18.6(@babel/core@7.20.12) - '@babel/plugin-transform-parameters': 7.20.7(@babel/core@7.20.12) - '@babel/plugin-transform-property-literals': 7.18.6(@babel/core@7.20.12) - '@babel/plugin-transform-react-display-name': 7.18.6(@babel/core@7.20.12) - '@babel/plugin-transform-react-jsx': 7.20.13(@babel/core@7.20.12) - '@babel/plugin-transform-shorthand-properties': 7.18.6(@babel/core@7.20.12) - '@babel/plugin-transform-spread': 7.20.7(@babel/core@7.20.12) - '@babel/plugin-transform-template-literals': 7.18.9(@babel/core@7.20.12) + '@babel/core': 7.21.4 + '@babel/plugin-proposal-class-properties': 7.18.6(@babel/core@7.21.4) + '@babel/plugin-proposal-object-rest-spread': 7.20.7(@babel/core@7.21.4) + '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.21.4) + '@babel/plugin-syntax-flow': 7.18.6(@babel/core@7.21.4) + '@babel/plugin-syntax-jsx': 7.18.6(@babel/core@7.21.4) + '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.21.4) + '@babel/plugin-transform-arrow-functions': 7.20.7(@babel/core@7.21.4) + '@babel/plugin-transform-block-scoped-functions': 7.18.6(@babel/core@7.21.4) + '@babel/plugin-transform-block-scoping': 7.20.15(@babel/core@7.21.4) + '@babel/plugin-transform-classes': 7.20.7(@babel/core@7.21.4) + '@babel/plugin-transform-computed-properties': 7.20.7(@babel/core@7.21.4) + '@babel/plugin-transform-destructuring': 7.20.7(@babel/core@7.21.4) + '@babel/plugin-transform-flow-strip-types': 7.19.0(@babel/core@7.21.4) + '@babel/plugin-transform-for-of': 7.18.8(@babel/core@7.21.4) + '@babel/plugin-transform-function-name': 7.18.9(@babel/core@7.21.4) + '@babel/plugin-transform-literals': 7.18.9(@babel/core@7.21.4) + '@babel/plugin-transform-member-expression-literals': 7.18.6(@babel/core@7.21.4) + '@babel/plugin-transform-modules-commonjs': 7.20.11(@babel/core@7.21.4) + '@babel/plugin-transform-object-super': 7.18.6(@babel/core@7.21.4) + '@babel/plugin-transform-parameters': 7.20.7(@babel/core@7.21.4) + '@babel/plugin-transform-property-literals': 7.18.6(@babel/core@7.21.4) + '@babel/plugin-transform-react-display-name': 7.18.6(@babel/core@7.21.4) + '@babel/plugin-transform-react-jsx': 7.20.13(@babel/core@7.21.4) + '@babel/plugin-transform-shorthand-properties': 7.18.6(@babel/core@7.21.4) + '@babel/plugin-transform-spread': 7.20.7(@babel/core@7.21.4) + '@babel/plugin-transform-template-literals': 7.18.9(@babel/core@7.21.4) babel-plugin-syntax-trailing-function-commas: 7.0.0-beta.0 transitivePeerDependencies: - supports-color @@ -7842,6 +7704,9 @@ packages: resolution: {integrity: sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==} dev: false + /blueimp-md5@2.19.0: + resolution: {integrity: sha512-DRQrD6gJyy8FbiE4s+bDoXS9hiW3Vbx5uCdwvcCf3zLHL+Iv7LtGHLpr+GZV8rHG8tK766FGYBwRbu8pELTt+w==} + /boolbase@1.0.0: resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} dev: false @@ -7913,6 +7778,7 @@ packages: /buffer-from@1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + dev: true /buffer-indexof-polyfill@1.0.2: resolution: {integrity: sha512-I7wzHwA3t1/lwXQh+A5PbNvJxgfo5r3xulgpYDB5zckTu/Z9oUK9biouBKQUjEqzaz3HnAT6TYoovmE+GqSf7A==} @@ -8252,6 +8118,7 @@ packages: dependencies: slice-ansi: 5.0.0 string-width: 5.1.2 + dev: true /cli-width@3.0.0: resolution: {integrity: sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==} @@ -8398,6 +8265,19 @@ packages: /concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + /concordance@5.0.4: + resolution: {integrity: sha512-OAcsnTEYu1ARJqWVGwf4zh4JDfHZEaSNlNccFmt8YjB2l/n19/PF2viLINHc57vO4FKIAFl2FWASIGZZWZ2Kxw==} + engines: {node: '>=10.18.0 <11 || >=12.14.0 <13 || >=14'} + dependencies: + date-time: 3.1.0 + esutils: 2.0.3 + fast-diff: 1.2.0 + js-string-escape: 1.0.1 + lodash: 4.17.21 + md5-hex: 3.0.1 + semver: 7.3.8 + well-known-symbols: 2.0.0 + /config-chain@1.1.13: resolution: {integrity: sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==} dependencies: @@ -8633,6 +8513,12 @@ packages: engines: {node: '>=0.11'} dev: false + /date-time@3.1.0: + resolution: {integrity: sha512-uqCUKXE5q1PNBXjPqvwhwJf9SwMoAHBgWJ6DcrnS5o+W2JOiIILl0JEdVD8SGujrNS02GGxgwAg2PN2zONgtjg==} + engines: {node: '>=6'} + dependencies: + time-zone: 1.0.0 + /dateformat@4.6.3: resolution: {integrity: sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==} @@ -8939,6 +8825,7 @@ packages: /eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + dev: true /ecc-jsbn@0.1.2: resolution: {integrity: sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==} @@ -9081,34 +8968,34 @@ packages: is-date-object: 1.0.5 is-symbol: 1.0.4 - /esbuild@0.16.17: - resolution: {integrity: sha512-G8LEkV0XzDMNwXKgM0Jwu3nY3lSTwSGY6XbxM9cr9+s0T/qSV1q1JVPBGzm3dcjhCic9+emZDmMffkwgPeOeLg==} + /esbuild@0.17.17: + resolution: {integrity: sha512-/jUywtAymR8jR4qsa2RujlAF7Krpt5VWi72Q2yuLD4e/hvtNcFQ0I1j8m/bxq238pf3/0KO5yuXNpuLx8BE1KA==} engines: {node: '>=12'} hasBin: true requiresBuild: true optionalDependencies: - '@esbuild/android-arm': 0.16.17 - '@esbuild/android-arm64': 0.16.17 - '@esbuild/android-x64': 0.16.17 - '@esbuild/darwin-arm64': 0.16.17 - '@esbuild/darwin-x64': 0.16.17 - '@esbuild/freebsd-arm64': 0.16.17 - '@esbuild/freebsd-x64': 0.16.17 - '@esbuild/linux-arm': 0.16.17 - '@esbuild/linux-arm64': 0.16.17 - '@esbuild/linux-ia32': 0.16.17 - '@esbuild/linux-loong64': 0.16.17 - '@esbuild/linux-mips64el': 0.16.17 - '@esbuild/linux-ppc64': 0.16.17 - '@esbuild/linux-riscv64': 0.16.17 - '@esbuild/linux-s390x': 0.16.17 - '@esbuild/linux-x64': 0.16.17 - '@esbuild/netbsd-x64': 0.16.17 - '@esbuild/openbsd-x64': 0.16.17 - '@esbuild/sunos-x64': 0.16.17 - '@esbuild/win32-arm64': 0.16.17 - '@esbuild/win32-ia32': 0.16.17 - '@esbuild/win32-x64': 0.16.17 + '@esbuild/android-arm': 0.17.17 + '@esbuild/android-arm64': 0.17.17 + '@esbuild/android-x64': 0.17.17 + '@esbuild/darwin-arm64': 0.17.17 + '@esbuild/darwin-x64': 0.17.17 + '@esbuild/freebsd-arm64': 0.17.17 + '@esbuild/freebsd-x64': 0.17.17 + '@esbuild/linux-arm': 0.17.17 + '@esbuild/linux-arm64': 0.17.17 + '@esbuild/linux-ia32': 0.17.17 + '@esbuild/linux-loong64': 0.17.17 + '@esbuild/linux-mips64el': 0.17.17 + '@esbuild/linux-ppc64': 0.17.17 + '@esbuild/linux-riscv64': 0.17.17 + '@esbuild/linux-s390x': 0.17.17 + '@esbuild/linux-x64': 0.17.17 + '@esbuild/netbsd-x64': 0.17.17 + '@esbuild/openbsd-x64': 0.17.17 + '@esbuild/sunos-x64': 0.17.17 + '@esbuild/win32-arm64': 0.17.17 + '@esbuild/win32-ia32': 0.17.17 + '@esbuild/win32-x64': 0.17.17 /escalade@3.1.1: resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==} @@ -10280,6 +10167,9 @@ packages: /fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + /fast-diff@1.2.0: + resolution: {integrity: sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==} + /fast-glob@3.2.12: resolution: {integrity: sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==} engines: {node: '>=8.6.0'} @@ -10517,7 +10407,6 @@ packages: asynckit: 0.4.0 combined-stream: 1.0.8 mime-types: 2.1.35 - dev: true /form-data@4.0.0: resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==} @@ -11394,6 +11283,7 @@ packages: /is-fullwidth-code-point@4.0.0: resolution: {integrity: sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==} engines: {node: '>=12'} + dev: true /is-generator-function@1.0.10: resolution: {integrity: sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==} @@ -11683,6 +11573,10 @@ packages: resolution: {integrity: sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==} dev: false + /js-string-escape@1.0.1: + resolution: {integrity: sha512-Smw4xcfIQ5LVjAOuJCvN/zIodzA/BBSsluuoSykP+lUvScIi4U6RJLfwHet5cxFnCswUjISV8oAXaqaJDY3chg==} + engines: {node: '>= 0.8'} + /js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} @@ -11783,6 +11677,10 @@ packages: remove-trailing-spaces: 1.0.8 dev: true + /json2typescript@1.5.1: + resolution: {integrity: sha512-mkuZR1O+rMPnWW1ROJJlY56aWWa71jYEmF5eIp52PlvcbNC9dWsFf8xAByRHWMbOTVbQYIb2vfndA66ibihPZg==} + dev: false + /json5@1.0.2: resolution: {integrity: sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==} hasBin: true @@ -12273,7 +12171,13 @@ packages: resolution: {integrity: sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA==} engines: {node: '>=12'} dependencies: - '@jridgewell/sourcemap-codec': 1.4.14 + '@jridgewell/sourcemap-codec': 1.4.15 + + /magic-string@0.30.0: + resolution: {integrity: sha512-LA+31JYDJLs82r2ScLrlz1GjSgu66ZV518eyWT+S8VhyQn/JL0u9MeBOvQMGYiPk1DBiSN9DDMOcXvigJZaViQ==} + engines: {node: '>=12'} + dependencies: + '@jridgewell/sourcemap-codec': 1.4.15 /make-dir@3.1.0: resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==} @@ -12308,6 +12212,12 @@ packages: css-mediaquery: 0.1.2 dev: false + /md5-hex@3.0.1: + resolution: {integrity: sha512-BUiRtTtV39LIJwinWBjqVsU9xhdnz7/i889V859IBFpuqGAj6LuOvHv5XLbgZ2R7ptJoJaEcxkv88/h25T7Ciw==} + engines: {node: '>=8'} + dependencies: + blueimp-md5: 2.19.0 + /mdast-util-definitions@5.1.2: resolution: {integrity: sha512-8SVPMuHqlPME/z3gqVwWY4zVXn8lqKv/pAhC57FuJ40ImXyBpmO5ukh98zB2v7Blql2FiHjHv9LVztSIqjY+MA==} dependencies: @@ -13075,13 +12985,13 @@ packages: hasBin: true dev: true - /mlly@1.1.0: - resolution: {integrity: sha512-cwzBrBfwGC1gYJyfcy8TcZU1f+dbH/T+TuOhtYP2wLv/Fb51/uV7HJQfBPtEupZ2ORLRU1EKFS/QfS3eo9+kBQ==} + /mlly@1.2.0: + resolution: {integrity: sha512-+c7A3CV0KGdKcylsI6khWyts/CYrGTrRVo4R/I7u/cUsy0Conxa6LUhiEzVKIw14lc2L5aiO4+SeVe4TeGRKww==} dependencies: acorn: 8.8.2 pathe: 1.1.0 - pkg-types: 1.0.1 - ufo: 1.0.1 + pkg-types: 1.0.2 + ufo: 1.1.1 /moment@2.29.4: resolution: {integrity: sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==} @@ -13760,10 +13670,6 @@ packages: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} - /pathe@0.2.0: - resolution: {integrity: sha512-sTitTPYnn23esFR3RlqYBWn4c45WGeLcsKzQiUpXJAyfcWkolvlYpV8FLo7JishK946oQwMFUCHXQ9AjGPKExw==} - dev: false - /pathe@1.1.0: resolution: {integrity: sha512-ODbEPR0KKHqECXW1GoxdDb+AZvULmXjVPy4rt+pGo2+TnjJTIPJQSVS6N63n8T2Ip+syHhbn52OewKicV0373w==} @@ -13860,11 +13766,11 @@ packages: dependencies: find-up: 4.1.0 - /pkg-types@1.0.1: - resolution: {integrity: sha512-jHv9HB+Ho7dj6ItwppRDDl0iZRYBD0jsakHXtFgoLr+cHSF6xC+QL54sJmWxyGxOLYSHm0afhXhXcQDQqH9z8g==} + /pkg-types@1.0.2: + resolution: {integrity: sha512-hM58GKXOcj8WTqUXnsQyJYXdeAPbythQgEF3nTcEo+nkD49chjQ9IKm/QJy9xf6JakXptz86h7ecP2024rrLaQ==} dependencies: jsonc-parser: 3.2.0 - mlly: 1.1.0 + mlly: 1.2.0 pathe: 1.1.0 /png-js@1.0.0: @@ -14820,8 +14726,8 @@ packages: fsevents: 2.3.2 dev: false - /rollup@3.14.0: - resolution: {integrity: sha512-o23sdgCLcLSe3zIplT9nQ1+r97okuaiR+vmAPZPTDYB7/f3tgWIYNyiQveMsZwshBT0is4eGax/HH83Q7CG+/Q==} + /rollup@3.20.4: + resolution: {integrity: sha512-n7J4tuctZXUErM9Uc916httwqmTc63zzCr2+TLCiSCpfO/Xuk3g/marGN1IlRJZi+QF3XMYx75PxXRfZDVgaRw==} engines: {node: '>=14.18.0', npm: '>=8.0.0'} hasBin: true optionalDependencies: @@ -15059,6 +14965,7 @@ packages: dependencies: ansi-styles: 6.2.1 is-fullwidth-code-point: 4.0.0 + dev: true /slick@1.12.2: resolution: {integrity: sha512-4qdtOGcBjral6YIBCWJ0ljFSKNLz9KkhbWtuGvUyRowl1kxfuE1x/Z/aJcaiilpb3do9bl5K7/1h9XC5wWpY/A==} @@ -15097,6 +15004,7 @@ packages: dependencies: buffer-from: 1.1.2 source-map: 0.6.1 + dev: true /source-map@0.5.6: resolution: {integrity: sha512-MjZkVp0NHr5+TPihLcadqnlVoGIoWo4IBHptutGh9wI3ttUYvCG26HkSuDi+K6lsZ25syXJXcctwgyVCt//xqA==} @@ -15280,6 +15188,7 @@ packages: eastasianwidth: 0.2.0 emoji-regex: 9.2.2 strip-ansi: 7.0.1 + dev: true /string.prototype.matchall@4.0.8: resolution: {integrity: sha512-6zOCOcJ+RJAQshcTvXPHoxoQGONa3e/Lqx90wUA+wEzX78sg5Bo+1tQo4N0pohS0erG9qtCqJDjNCQBjeWVxyg==} @@ -15336,6 +15245,7 @@ packages: engines: {node: '>=12'} dependencies: ansi-regex: 6.0.1 + dev: true /strip-bom@2.0.0: resolution: {integrity: sha512-kwrX1y7czp1E69n2ajbG65mIo9dqvJ+8aBQXOGVxqwvNbsXdFM6Lq37dLAY3mknUwru8CfcCbfOLL/gMo+fi3g==} @@ -15509,6 +15419,10 @@ packages: resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} dev: true + /time-zone@1.0.0: + resolution: {integrity: sha512-TIsDdtKo6+XrPtiTm1ssmMngN1sAhyKnTO2kunQWqNPWIVvCm15Wmw4SWInwTVgJ5u/Tr04+8Ei9TNcw4x4ONA==} + engines: {node: '>=4'} + /tiny-emitter@2.1.0: resolution: {integrity: sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==} dev: false @@ -15531,16 +15445,26 @@ packages: /tiny-warning@1.0.3: resolution: {integrity: sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==} - /tinybench@2.3.1: - resolution: {integrity: sha512-hGYWYBMPr7p4g5IarQE7XhlyWveh1EKhy4wUBS1LrHXCKYgvz+4/jCqgmJqZxxldesn05vccrtME2RLLZNW7iA==} + /tinybench@2.4.0: + resolution: {integrity: sha512-iyziEiyFxX4kyxSp+MtY1oCH/lvjH3PxFN8PGCDeqcZWAJ/i+9y+nL85w99PxVzrIvew/GSkSbDYtiGVa85Afg==} /tinypool@0.3.1: resolution: {integrity: sha512-zLA1ZXlstbU2rlpA4CIeVaqvWq41MTWqLY3FfsAXgC8+f7Pk7zroaJQxDgxn1xNudKW6Kmj4808rPFShUlIRmQ==} engines: {node: '>=14.0.0'} + dev: true + + /tinypool@0.4.0: + resolution: {integrity: sha512-2ksntHOKf893wSAH4z/+JbPpi92esw8Gn9N2deXX+B0EO92hexAVI9GIZZPx7P5aYo5KULfeOSt3kMOmSOy6uA==} + engines: {node: '>=14.0.0'} /tinyspy@1.1.1: resolution: {integrity: sha512-UVq5AXt/gQlti7oxoIg5oi/9r0WpF7DGEVwXgqWSMmyN16+e3tl5lIvTaOpJ3TAtu5xFzWccFRM4R5NaWHF+4g==} engines: {node: '>=14.0.0'} + dev: true + + /tinyspy@2.1.0: + resolution: {integrity: sha512-7eORpyqImoOvkQJCSkL0d0mB4NHHIFAy4b1u8PHdDa7SjGS2njzl6/lyGoZLm+eyYEtlUmFGE0rFj66SWxZgQQ==} + engines: {node: '>=14.0.0'} /title-case@3.0.3: resolution: {integrity: sha512-e1zGYRvbffpcHIrnuqT0Dh+gEJtDaxDSoG4JAIpq4oDFyooziLBIiYQv0GBT4FUAnUop5uZ1hiIAj7oAF6sOCA==} @@ -15868,8 +15792,8 @@ packages: resolution: {integrity: sha512-RqshF7TPTE0XLYAqmjlu5cLLuGdKrNu9O1KLA/qp39QtbZwuzwv1dT46DZSopoUMsYgXpB3Cv8a03FI8b74oFQ==} dev: false - /ufo@1.0.1: - resolution: {integrity: sha512-boAm74ubXHY7KJQZLlXrtMz52qFvpsbOxDcZOnw/Wf+LS4Mmyu7JxmzD4tDLtUQtmZECypJ0FrCz4QIe6dvKRA==} + /ufo@1.1.1: + resolution: {integrity: sha512-MvlCc4GHrmZdAllBc0iUDowff36Q9Ndw/UzqmEKyrfSzokTd9ZCy1i+IIk5hrYKkjoYVQyNbrw7/F8XJ2rEwTg==} /uglify-js@3.17.4: resolution: {integrity: sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==} @@ -16188,7 +16112,7 @@ packages: resolution: {integrity: sha512-74Y4LqY74kLE6IFyIjPtkSTWzUZmj8tdHT9Ii/26dvQ6K9Dl2NbEfj0XgU2sHCtKgt5VupqhlO/5aWuqS+IY1w==} engines: {node: '>=10.12.0'} dependencies: - '@jridgewell/trace-mapping': 0.3.17 + '@jridgewell/trace-mapping': 0.3.18 '@types/istanbul-lib-coverage': 2.0.4 convert-source-map: 1.9.0 dev: true @@ -16234,29 +16158,6 @@ packages: vfile-message: 3.1.4 dev: false - /vite-node@0.27.3(@types/node@18.13.0): - resolution: {integrity: sha512-eyJYOO64o5HIp8poc4bJX+ZNBwMZeI3f6/JdiUmJgW02Mt7LnoCtDMRVmLaY9S05SIsjGe339ZK4uo2wQ+bF9g==} - engines: {node: '>=v14.16.0'} - hasBin: true - dependencies: - cac: 6.7.14 - debug: 4.3.4 - mlly: 1.1.0 - pathe: 0.2.0 - picocolors: 1.0.0 - source-map: 0.6.1 - source-map-support: 0.5.21 - vite: 4.1.1(@types/node@18.13.0) - transitivePeerDependencies: - - '@types/node' - - less - - sass - - stylus - - sugarss - - supports-color - - terser - dev: false - /vite-node@0.28.4(@types/node@18.13.0): resolution: {integrity: sha512-KM0Q0uSG/xHHKOJvVHc5xDBabgt0l70y7/lWTR7Q0pR5/MrYxadT+y32cJOE65FfjGmJgxpVEEY+69btJgcXOQ==} engines: {node: '>=v14.16.0'} @@ -16264,32 +16165,12 @@ packages: dependencies: cac: 6.7.14 debug: 4.3.4 - mlly: 1.1.0 + mlly: 1.2.0 pathe: 1.1.0 picocolors: 1.0.0 source-map: 0.6.1 source-map-support: 0.5.21 - vite: 4.1.1(@types/node@18.13.0) - transitivePeerDependencies: - - '@types/node' - - less - - sass - - stylus - - sugarss - - supports-color - - terser - - /vite-node@0.29.3(@types/node@18.13.0): - resolution: {integrity: sha512-QYzYSA4Yt2IiduEjYbccfZQfxKp+T1Do8/HEpSX/G5WIECTFKJADwLs9c94aQH4o0A+UtCKU61lj1m5KvbxxQA==} - engines: {node: '>=v14.16.0'} - hasBin: true - dependencies: - cac: 6.7.14 - debug: 4.3.4 - mlly: 1.1.0 - pathe: 1.1.0 - picocolors: 1.0.0 - vite: 4.1.4(@types/node@18.13.0) + vite: 4.2.1(@types/node@18.13.0) transitivePeerDependencies: - '@types/node' - less @@ -16300,168 +16181,58 @@ packages: - terser dev: true - /vite@4.1.1(@types/node@18.13.0): - resolution: {integrity: sha512-LM9WWea8vsxhr782r9ntg+bhSFS06FJgCvvB0+8hf8UWtvaiDagKYWXndjfX6kGl74keHJUcpzrQliDXZlF5yg==} - engines: {node: ^14.18.0 || >=16.0.0} + /vite-node@0.30.1(@types/node@18.13.0): + resolution: {integrity: sha512-vTikpU/J7e6LU/8iM3dzBo8ZhEiKZEKRznEMm+mJh95XhWaPrJQraT/QsT2NWmuEf+zgAoMe64PKT7hfZ1Njmg==} + engines: {node: '>=v14.18.0'} hasBin: true - peerDependencies: - '@types/node': '>= 14' - less: '*' - sass: '*' - stylus: '*' - sugarss: '*' - terser: ^5.4.0 - peerDependenciesMeta: - '@types/node': - optional: true - less: - optional: true - sass: - optional: true - stylus: - optional: true - sugarss: - optional: true - terser: - optional: true dependencies: - '@types/node': 18.13.0 - esbuild: 0.16.17 - postcss: 8.4.21 - resolve: 1.22.1 - rollup: 3.14.0 - optionalDependencies: - fsevents: 2.3.2 - - /vite@4.1.4(@types/node@18.13.0): - resolution: {integrity: sha512-3knk/HsbSTKEin43zHu7jTwYWv81f8kgAL99G5NWBcA1LKvtvcVAC4JjBH1arBunO9kQka+1oGbrMKOjk4ZrBg==} - engines: {node: ^14.18.0 || >=16.0.0} - hasBin: true - peerDependencies: - '@types/node': '>= 14' - less: '*' - sass: '*' - stylus: '*' - sugarss: '*' - terser: ^5.4.0 - peerDependenciesMeta: - '@types/node': - optional: true - less: - optional: true - sass: - optional: true - stylus: - optional: true - sugarss: - optional: true - terser: - optional: true - dependencies: - '@types/node': 18.13.0 - esbuild: 0.16.17 - postcss: 8.4.21 - resolve: 1.22.1 - rollup: 3.14.0 - optionalDependencies: - fsevents: 2.3.2 - - /vitest@0.25.8(jsdom@20.0.3): - resolution: {integrity: sha512-X75TApG2wZTJn299E/TIYevr4E9/nBo1sUtZzn0Ci5oK8qnpZAZyhwg0qCeMSakGIWtc6oRwcQFyFfW14aOFWg==} - engines: {node: '>=v14.16.0'} - hasBin: true - peerDependencies: - '@edge-runtime/vm': '*' - '@vitest/browser': '*' - '@vitest/ui': '*' - happy-dom: '*' - jsdom: '*' - peerDependenciesMeta: - '@edge-runtime/vm': - optional: true - '@vitest/browser': - optional: true - '@vitest/ui': - optional: true - happy-dom: - optional: true - jsdom: - optional: true - dependencies: - '@types/chai': 4.3.4 - '@types/chai-subset': 1.3.3 - '@types/node': 18.13.0 - acorn: 8.8.2 - acorn-walk: 8.2.0 - chai: 4.3.7 - debug: 4.3.4 - jsdom: 20.0.3 - local-pkg: 0.4.3 - source-map: 0.6.1 - strip-literal: 1.0.1 - tinybench: 2.3.1 - tinypool: 0.3.1 - tinyspy: 1.1.1 - vite: 4.1.1(@types/node@18.13.0) - transitivePeerDependencies: - - less - - sass - - stylus - - sugarss - - supports-color - - terser - dev: false - - /vitest@0.27.3(jsdom@20.0.3): - resolution: {integrity: sha512-Ld3UVgRVhJUtqvQ3dW89GxiApFAgBsWJZBCWzK+gA3w2yG68csXlGZZ4WDJURf+8ecNfgrScga6xY+8YSOpiMg==} - engines: {node: '>=v14.16.0'} - hasBin: true - peerDependencies: - '@edge-runtime/vm': '*' - '@vitest/browser': '*' - '@vitest/ui': '*' - happy-dom: '*' - jsdom: '*' - peerDependenciesMeta: - '@edge-runtime/vm': - optional: true - '@vitest/browser': - optional: true - '@vitest/ui': - optional: true - happy-dom: - optional: true - jsdom: - optional: true - dependencies: - '@types/chai': 4.3.4 - '@types/chai-subset': 1.3.3 - '@types/node': 18.13.0 - acorn: 8.8.2 - acorn-walk: 8.2.0 cac: 6.7.14 - chai: 4.3.7 debug: 4.3.4 - jsdom: 20.0.3 - local-pkg: 0.4.3 + mlly: 1.2.0 + pathe: 1.1.0 picocolors: 1.0.0 - source-map: 0.6.1 - std-env: 3.3.2 - strip-literal: 1.0.1 - tinybench: 2.3.1 - tinypool: 0.3.1 - tinyspy: 1.1.1 - vite: 4.1.1(@types/node@18.13.0) - vite-node: 0.27.3(@types/node@18.13.0) - why-is-node-running: 2.2.2 + vite: 4.2.1(@types/node@18.13.0) transitivePeerDependencies: + - '@types/node' - less - sass - stylus - sugarss - supports-color - terser - dev: false + + /vite@4.2.1(@types/node@18.13.0): + resolution: {integrity: sha512-7MKhqdy0ISo4wnvwtqZkjke6XN4taqQ2TBaTccLIpOKv7Vp2h4Y+NpmWCnGDeSvvn45KxvWgGyb0MkHvY1vgbg==} + engines: {node: ^14.18.0 || >=16.0.0} + hasBin: true + peerDependencies: + '@types/node': '>= 14' + less: '*' + sass: '*' + stylus: '*' + sugarss: '*' + terser: ^5.4.0 + peerDependenciesMeta: + '@types/node': + optional: true + less: + optional: true + sass: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + dependencies: + '@types/node': 18.13.0 + esbuild: 0.17.17 + postcss: 8.4.21 + resolve: 1.22.1 + rollup: 3.20.4 + optionalDependencies: + fsevents: 2.3.2 /vitest@0.28.4(jsdom@20.0.3): resolution: {integrity: sha512-sfWIy0AdlbyGRhunm+TLQEJrFH9XuRPdApfubsyLcDbCRrUX717BRQKInTgzEfyl2Ipi1HWoHB84Nqtcwxogcg==} @@ -16504,10 +16275,10 @@ packages: source-map: 0.6.1 std-env: 3.3.2 strip-literal: 1.0.1 - tinybench: 2.3.1 + tinybench: 2.4.0 tinypool: 0.3.1 tinyspy: 1.1.1 - vite: 4.1.1(@types/node@18.13.0) + vite: 4.2.1(@types/node@18.13.0) vite-node: 0.28.4(@types/node@18.13.0) why-is-node-running: 2.2.2 transitivePeerDependencies: @@ -16517,10 +16288,11 @@ packages: - sugarss - supports-color - terser + dev: true - /vitest@0.29.3(jsdom@20.0.3): - resolution: {integrity: sha512-muMsbXnZsrzDGiyqf/09BKQsGeUxxlyLeLK/sFFM4EXdURPQRv8y7dco32DXaRORYP0bvyN19C835dT23mL0ow==} - engines: {node: '>=v14.16.0'} + /vitest@0.30.1(jsdom@20.0.3): + resolution: {integrity: sha512-y35WTrSTlTxfMLttgQk4rHcaDkbHQwDP++SNwPb+7H8yb13Q3cu2EixrtHzF27iZ8v0XCciSsLg00RkPAzB/aA==} + engines: {node: '>=v14.18.0'} hasBin: true peerDependencies: '@edge-runtime/vm': '*' @@ -16528,6 +16300,9 @@ packages: '@vitest/ui': '*' happy-dom: '*' jsdom: '*' + playwright: '*' + safaridriver: '*' + webdriverio: '*' peerDependenciesMeta: '@edge-runtime/vm': optional: true @@ -16539,31 +16314,39 @@ packages: optional: true jsdom: optional: true + playwright: + optional: true + safaridriver: + optional: true + webdriverio: + optional: true dependencies: '@types/chai': 4.3.4 '@types/chai-subset': 1.3.3 '@types/node': 18.13.0 - '@vitest/expect': 0.29.3 - '@vitest/runner': 0.29.3 - '@vitest/spy': 0.29.3 - '@vitest/utils': 0.29.3 + '@vitest/expect': 0.30.1 + '@vitest/runner': 0.30.1 + '@vitest/snapshot': 0.30.1 + '@vitest/spy': 0.30.1 + '@vitest/utils': 0.30.1 acorn: 8.8.2 acorn-walk: 8.2.0 cac: 6.7.14 chai: 4.3.7 + concordance: 5.0.4 debug: 4.3.4 jsdom: 20.0.3 local-pkg: 0.4.3 + magic-string: 0.30.0 pathe: 1.1.0 picocolors: 1.0.0 source-map: 0.6.1 std-env: 3.3.2 strip-literal: 1.0.1 - tinybench: 2.3.1 - tinypool: 0.3.1 - tinyspy: 1.1.1 - vite: 4.1.4(@types/node@18.13.0) - vite-node: 0.29.3(@types/node@18.13.0) + tinybench: 2.4.0 + tinypool: 0.4.0 + vite: 4.2.1(@types/node@18.13.0) + vite-node: 0.30.1(@types/node@18.13.0) why-is-node-running: 2.2.2 transitivePeerDependencies: - less @@ -16572,7 +16355,6 @@ packages: - sugarss - supports-color - terser - dev: true /void-elements@3.1.0: resolution: {integrity: sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==} @@ -16647,6 +16429,10 @@ packages: engines: {node: '>=10.13.0'} dev: false + /well-known-symbols@2.0.0: + resolution: {integrity: sha512-ZMjC3ho+KXo0BfJb7JgtQ5IBuvnShdlACNkKkdsqBmYw3bPAaJfPeYUo6tLUaT5tG/Gkh7xkpBhKRQ9e7pyg9Q==} + engines: {node: '>=6'} + /whatwg-encoding@2.0.0: resolution: {integrity: sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==} engines: {node: '>=12'}